diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -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
+.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..162e174
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,112 @@
+GIT2LOG := $(shell if [ -x ./git2log ] ; then echo ./git2log --update ; else echo true ; fi)
+GITDEPS := $(shell [ -d .git ] && echo .git/HEAD .git/refs/heads .git/refs/tags)
+VERSION := $(shell $(GIT2LOG) --version VERSION ; cat VERSION)
+BRANCH := $(shell git branch | perl -ne 'print $$_ if s/^\*\s*//')
+PREFIX := gfxboot2-$(VERSION)
+
+ifdef 32BIT
+ OPT_32BIT = -m32
+endif
+
+CC = gcc
+CFLAGS = -g -O2 $(OPT_32BIT) -I. -Wall -Wno-pointer-sign -Wsign-conversion -Wsign-compare
+LDFLAGS = $(OPT_32BIT)
+
+GFXBOOT_LIB_SRC = gfxboot.c gfxboot_main.c \
+ gfxboot_array.c gfxboot_canvas.c gfxboot_draw.c gfxboot_font.c gfxboot_hash.c gfxboot_context.c \
+ gfxboot_lib.c gfxboot_malloc.c gfxboot_gstate.c gfxboot_jpeg.c \
+ gfxboot_mem.c gfxboot_num.c gfxboot_obj.c gfxboot_olist.c gfxboot_prim.c gfxboot_debug.c
+GFXBOOT_LIB_OBJ = $(GFXBOOT_LIB_SRC:.c=.o)
+
+GFXBOOT_BIN_SRC = gfxboot-compile.c gfxboot-x11.c
+GFXBOOT_BIN_OBJ = $(GFXBOOT_BIN_SRC:.c=.o)
+
+GFXBOOT_HEADER = gfxboot.h vocabulary.h
+
+GRUB_MODULE_BIOS = $(shell . ./config_vars ; echo $$grub_module_bios)
+GRUB_MODULE_EFI = $(shell . ./config_vars ; echo $$grub_module_efi)
+GRUB_FILES = $(shell . ./config_vars ; echo $$grub_files)
+GRUB_ISO = $(shell . ./config_vars ; echo $$grub_iso)
+
+ifneq "$(vm)" ""
+ VM = --$(vm)
+endif
+
+.PHONY: all grub-bios grub-efi grub-iso test-bios test-efi test-x11 archive clean tests
+
+all: gfxboot-x11 gfxboot-compile gfxboot-font
+
+changelog: $(GITDEPS)
+ $(GIT2LOG) --changelog changelog
+
+doc: doc/reference.adoc
+
+doc/reference.adoc: vocabulary.def gfxboot_prim.c doc/reference_template
+ ./mk_reference vocabulary.def gfxboot_prim.c doc/reference_template $@
+
+grub-bios: $(GRUB_MODULE_BIOS)
+
+grub-efi: $(GRUB_MODULE_EFI)
+
+grub-iso: $(GRUB_ISO)
+
+test-bios: grub-iso
+ vm --cdrom $(GRUB_ISO) $(VM) --serial
+
+test-efi: grub-iso
+ vm --cdrom $(GRUB_ISO) $(VM) --serial --efi
+
+test-x11: gfxboot-x11 gfxboot-compile
+ ./mk_x11_test
+ ./gfxboot-x11 x11
+
+test-cons: gfxboot-x11 gfxboot-compile
+ ./mk_x11_test
+ ./gfxboot-x11 --no-x11 --file - x11
+
+test-console: gfxboot-x11 gfxboot-compile
+ ./mk_x11_test
+ ./gfxboot-x11 --no-x11 --file - x11
+
+vocabulary.h: vocabulary.def types.def
+ ./mk_vocabulary vocabulary.def types.def $@
+
+$(GFXBOOT_LIB_OBJ): %.o: %.c $(GFXBOOT_HEADER)
+ $(CC) -c $(CFLAGS) -ffreestanding $<
+
+$(GFXBOOT_BIN_OBJ): %.o: %.c $(GFXBOOT_HEADER)
+ $(CC) -c $(CFLAGS) $<
+
+gfxboot-x11: gfxboot-x11.o $(GFXBOOT_LIB_OBJ)
+ $(CC) $< $(GFXBOOT_LIB_OBJ) $(LDFLAGS) -lX11 -o $@
+
+gfxboot-compile: gfxboot-compile.o
+ $(CC) $< $(LDFLAGS) -o $@
+
+gfxboot-font: gfxboot-font.c
+ $(CC) $(CFLAGS) -I /usr/include/freetype2 -lfreetype $< -o $@
+
+$(GRUB_MODULE_BIOS): $(GRUB_FILES)
+ ./grub_build --bios
+
+$(GRUB_MODULE_EFI): $(GRUB_FILES)
+ ./grub_build --efi
+
+$(GRUB_ISO): $(GRUB_MODULE_BIOS) $(GRUB_MODULE_EFI)
+ ./mk_grub_test
+
+tests: gfxboot-x11 gfxboot-compile
+ @./run_tests
+
+archive: changelog
+ @if [ ! -d .git ] ; then echo no git repo ; false ; fi
+ mkdir -p package
+ git archive --prefix=$(PREFIX)/ $(BRANCH) > package/$(PREFIX).tar
+ tar -r -f package/$(PREFIX).tar --mode=0664 --owner=root --group=root --mtime="`git show -s --format=%ci`" --transform='s:^:$(PREFIX)/:' VERSION changelog
+ xz -f package/$(PREFIX).tar
+
+clean:
+ rm -f changelog VERSION vocabulary.h
+ rm -f $(GRUB_ISO) screenlog.0 *~ *.o gfxboot-{x11,font,compile} sample *.log files/*~ *.gc doc/*~
+ rm -f tests/*~ tests/*/{*.log,*~,*.gc,gc.log.ref,opt*.log.ref}
+ rm -rf x11 grub package
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..34400e0
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,91 @@
+= gfxboot2
+
+A graphical interface to bootloaders.
+
+__This is still in a very early stage.__
+
+It's essentially a rework of https://github.com/openSUSE/gfxboot[gfxboot]
+but written in C.
+
+The implemented scripting language is again a
+https://en.wikipedia.org/wiki/Stack-oriented_programming[stack-based language] - similar
+to what gfxboot uses but with integrated memory management and a hash data type.
+
+=== Status
+
+__The code is not yet ready to be used.__
+
+The scripting language is basically implemented, including the graphics primitves.
+
+The connection to `grub` is still a bit awkward. The patches extend grub to
+expose a link to the graphics framebuffer.
+
+This is not strictly needed. gfxboot does never read the video memory.
+An`update` function that can update a rectangular screen area would suffice.
+
+AFAICS grub does not exactly have that. There is
+`doublebuf_blit_update_screen` in
+https://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/video/fb/video_fb.c[grub-core/video/fb/video_fb.c],
+though. But this updates continuous memory ranges, not rectangular areas.
+
+=== A first look
+
+.A cat
+image::doc/screen_01.png[A cat]
+
+.The source code
+[%collapsible]
+====
+[source]
+----
+/cfont getconsolegstate getfont def
+/foo "foo.fnt" readfile newfont def
+/bar "bar.fnt" readfile newfont def
+
+/text "ABC 12345 xyz # * % & § öäüß €" def
+
+/image gstate def
+image "katze_800.jpg" readfile unpackimage setcanvas
+
+0 0 setpos
+image getgstate exch blt
+0x90000000 setcolor
+image dim fillrect
+
+0xffff00 setcolor
+
+getgstate cfont setfont
+50 50 setpos "Some font samples" show
+
+0x00ffffff setcolor
+
+getgstate cfont setfont
+50 100 setpos text show
+
+getgstate bar setfont
+50 130 setpos text show
+
+getgstate foo setfont
+50 180 setpos text show
+----
+====
+
+The prompt in the lower part of the screen is the debug console.
+
+=== Next steps
+
+- get a basic boot menu working
+- fine-tune language definition
+- more systematic debug console
+- add de-fragmentation to memory management
+- work on documentation
+
+=== Some documentation
+
+For details check out the link:doc/reference.adoc#add[language reference].
+
+To get started, read the link:doc/building.adoc#add[build instructions].
+
+The code is covered by a link:doc/testing.adoc#add[test suite].
+
+The link:doc/internals.adoc#add[binary format] is also documented.
diff --git a/config_vars b/config_vars
new file mode 100644
index 0000000..684f213
--- /dev/null
+++ b/config_vars
@@ -0,0 +1,18 @@
+# this config file is sourced in build and test scripts
+
+su="sw 0"
+
+grub_root=~/g
+grub_build=b
+grub_dir="$grub_root/$grub_build"
+
+grub_module=gfxboot
+grub_files="gfxboot.c gfxboot.h gfxboot_*.c vocabulary.h"
+
+grub_module_bios=$grub_dir/build/grub-core/$grub_module.mod
+grub_module_efi=$grub_dir/build-efi/grub-core/$grub_module.mod
+
+grub_iso_src=files/cd
+grub_iso_dir=$PWD/grub
+grub_iso=$PWD/grub.iso
+
diff --git a/doc/building.adoc b/doc/building.adoc
new file mode 100644
index 0000000..e5989d1
--- /dev/null
+++ b/doc/building.adoc
@@ -0,0 +1,46 @@
+== Prepare grub builds
+
+=== check out grub2 package
+osc co openSUSE:Factory/grub2
+cd openSUSE:Factory/grub2
+
+=== build it locally
+osc build --clean --download-api-only standard x86_64
+
+=== prepare $HOME/g and $HOME/g/b symlinks pointing to grub2 build directory
+ln -snf $OBS_BUILD_DIR/obs-openSUSE:Factory-standard-grub2 ~/g
+cd ~g
+ln -snf .build.packages/BUILD/grub-2.04 b
+
+=== apply gfxboot2 patches
+cd ~g/b
+patch -p1 < $GFXBOOT2_DIR/patches/grub-2.04.diff
+
+== required packages
+
+- mksusecd
+- qemu
+- vm (TBD)
+- freetype2-devel
+- libX11-devel
+
+== building 32bit binaries
+- packages
+ - glibc-devel-32bit
+ - freetype2-devel-32bit
+ - libX11-devel-32bit
+ - gcc-32bit
+
+- run 'make 32BIT=1 ...'
+
+== make targets
+- (default target): build gfxboot tools: gfxboot-compile, gfxboot-font, gfxboot-x11
+- tests: run test suite
+- grub-bios: legacy grub
+- grub-efi: efi grub
+- grub-iso: build grub bootable iso
+- test-x11: build & run x11 variant
+- test-console: build & run text console variant (no gfx shown)
+- test-bios: build & legacy boot iso with qemu
+- test-efi: build & efi boot iso with qemu
+- test-XXX builds use the files in the 'files' subdirectory - main.gs contains the main program
diff --git a/doc/internals.adoc b/doc/internals.adoc
new file mode 100644
index 0000000..63c2099
--- /dev/null
+++ b/doc/internals.adoc
@@ -0,0 +1,68 @@
+== Internal data structures
+
+=== Binary format
+
+The binary code is encoded as follows:
+
+1 opcode byte followed by 0 or more data bytes
+
+[source]
+----
+opcode [data] ...
+opcode [data] ...
+...
+
+opcode bits 0..3: type
+opcode bits 4..7: arg
+----
+
+There are 2 variants of opcodes:
+
+- the 1st decodes into type + signed/unsigned 64 bit value (arg1)
+- the 2nd decodes into type + unsigned 32 bit value (arg1, the size of the
+ following byte array) + byte array (arg2)
+
+The 1st group is for number-like data, the 2nd group for string-like data. Note that strings are not
+zero-terminated.
+
+[source]
+----
+if type < 5
+ # variant 1
+ if arg < 8
+ arg1 = arg
+ else # arg >= 8
+ arg1 = decode_num(arg - 7) # 1..8 bytes
+ # arg1 will be decoded as signed if type is 1 (integer), else unsigned
+ # (if signed, bit 7 of the last byte is the sign bit)
+ endif
+else # type >= 5
+ # variant 2
+ if arg < 12
+ arg1 = arg
+ else # arg >= 12
+ arg1 = decode_num(arg - 11) # 1..4 bytes
+ # arg1 will always be decoded as unsigned
+ endif
+ arg2 = the following arg1 bytes # arg1 may be 0
+endif
+
+decode_num(n): read next n bytes as little-endian number
+----
+
+For type codes see `types.def`.
+
+The file has to start with the magic byte sequence `0x75 0x51 0x12 0xa9 0x42 0x7a 0xad 0x60`.
+(Which decodes as a 7 char long special comment.)
+
+Sample decoded byte sequences:
+
+[source]
+----
+# hex [type] decoded value
+00 [nil] nil
+51 [int] 5
+81 ff [int] -1
+37 61 62 63 [str] "abc"
+c7 0e 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e [str] "abcdefghijklmn"
+----
diff --git a/doc/reference.adoc b/doc/reference.adoc
new file mode 100644
index 0000000..be1c5a6
--- /dev/null
+++ b/doc/reference.adoc
@@ -0,0 +1,2226 @@
+## Language reference
+
+Comments start with '#' and extend to the end of the line.
+
+To include some other source file, use the special `include` comment (note the double-`#`, and no quotes around the file name):
+
+.Examples
+```
+## include foo.gs
+```
+
+Numbers are 64 bit signed integers. Numerical and string constants are given in a C-like way.
+
+.Examples
+```
+123
+-456
+0x4567
+"Hi there\n"
+'\033'
+'\x1b'
+'\u20ac'
+```
+
+Strings are not zero-terminated and can contain any data (including zeros). Use `\xNN` to set arbitray binary values.
+Use `\uNNNN` or `\UNNNNNNNN` to include UTF8-encoded Unicode characters.
+
+.Examples
+```
+"1 Euro = 1 €\n"
+"1 Euro = 1 \u20ac\n"
+"1 Euro = 1 \xe2\x82\xac\n"
+```
+
+Logical operations return values of type bool. They are not identical with integers.
+
+.Examples
+```
+true
+false
+```
+
+There is a special value nil. It is not the same as 0.
+
+.Examples
+```
+nil
+```
+
+### Primitive words
+
+* **+{+** - start code block [[code_start]]
++
+--
+** ( -- __code~1~__ )
+--
++
+Put a reference to the following code block on the stack. The code block
+starts after the opening `{` and extends to (and including) the matching
+closing `}`.
++
+This special word is handled while converting the source code into binary code with `gfxboot-compile`.
+For this reason, this is the only word that cannot be redefined.
++
+.Examples
+```
+
+{ "Hello!" show }
+```
++
+See also: xref:code_end[+}+], xref:hash_start[+(+], xref:hash_end[+)+], xref:array_start[+[+], xref:array_end[+]+], xref:getdict[+getdict+], xref:getparent[+getparent+], xref:setdict[+setdict+], xref:setparent[+setparent+]
+
+* **+}+** - finish code block [[code_end]]
++
+--
+** ( -- )
+--
++
+This marks the end of a code block. When the code is executed, the
+interpreter leaves the current execution context and returns to the
+parent context.
++
+.Examples
+```
+
+/hello { "Hello!" show } def
+hello # print "Hello!"
+```
++
+See also: xref:code_start[+{+], xref:hash_start[+(+], xref:hash_end[+)+], xref:array_start[+[+], xref:array_end[+]+], xref:getdict[+getdict+], xref:getparent[+getparent+], xref:setdict[+setdict+], xref:setparent[+setparent+]
+
+* **+(+** - start hash definition [[hash_start]]
++
+--
+** ( -- __mark~1~__ )
++
+__mark~1~__: hash start marker
+--
++
+Put hash start marker on stack. Hash definition is completed with ).
++
+.Examples
+```
+
+( "foo" 10 "bar" 20 ) # hash with 2 keys "foo" and "bar"
+```
++
+See also: xref:code_start[+{+], xref:code_end[+}+], xref:hash_end[+)+], xref:array_start[+[+], xref:array_end[+]+], xref:getdict[+getdict+], xref:getparent[+getparent+], xref:setdict[+setdict+], xref:setparent[+setparent+]
+
+* **+)+** - finish hash definition [[hash_end]]
++
+--
+** ( __mark~1~__ __any~1~__ ... __any~n~__ -- __hash~1~__ )
++
+__mark~1~__: array start marker
++
+__any~1~__ ... __any~n~__: some key - value pairs
++
+__hash~1~__: new hash
+--
++
+Search for __mark~1~__ on the stack and put everything between __mark~1~__ and TOS
+into a hash. The elements are interpreted alternatingly as key and value.
+If there's an odd number of elements on the stack, the last value is nil.
++
+.Examples
+```
+
+( "foo" 10 "bar" 20 ) # hash with 2 keys "foo" and "bar"
+```
++
+See also: xref:code_start[+{+], xref:code_end[+}+], xref:hash_start[+(+], xref:array_start[+[+], xref:array_end[+]+], xref:getdict[+getdict+], xref:getparent[+getparent+], xref:setdict[+setdict+], xref:setparent[+setparent+]
+
+* **+[+** - start array definition [[array_start]]
++
+--
+** ( -- __mark~1~__ )
++
+__mark~1~__: array start marker
+--
++
+Put array start marker on stack. Array definition is completed with ].
++
+.Examples
+```
+
+[ 1 2 3 ] # array with 3 elements
+```
++
+See also: xref:code_start[+{+], xref:code_end[+}+], xref:hash_start[+(+], xref:hash_end[+)+], xref:array_end[+]+], xref:getdict[+getdict+], xref:getparent[+getparent+], xref:setdict[+setdict+], xref:setparent[+setparent+]
+
+* **+]+** - finish array definition [[array_end]]
++
+--
+** ( __mark~1~__ __any~1~__ ... __any~n~__ -- __array~1~__ )
++
+__mark~1~__: array start marker
++
+__any~1~__ ... __any~n~__: some elements
++
+__array~1~__: new array
+--
++
+Search for __mark~1~__ on the stack and put everything between __mark~1~__ and TOS
+into an array.
++
+.Examples
+```
+
+[ 10 20 "some" "text" ] # array with 4 elements
+```
++
+See also: xref:code_start[+{+], xref:code_end[+}+], xref:hash_start[+(+], xref:hash_end[+)+], xref:array_start[+[+], xref:getdict[+getdict+], xref:getparent[+getparent+], xref:setdict[+setdict+], xref:setparent[+setparent+]
+
+* **+abs+** - absolute value [[abs]]
++
+--
+** ( __int~1~__ -- __int~2~__ )
++
+__int~2~__: |__int~1~__|
++
+** ( __bool~1~__ -- __bool~2~__ )
++
+__bool~2~__: __bool~1~__
+--
++
+Absolute value of __int~1~__ (change sign if __int~1~__ is negative).
++
+.Examples
+```
+
+For boolean 1 bit arithmetic the value is unchanged.
+
+-20 abs # 20
+true abs # true
+```
++
+See also: xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+add+** - addition [[add]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ + __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ xor __bool~2~__
++
+** ( __array~1~__ __array~2~__ -- __array~3~__ )
++
+__array~3~__: __array~2~__ appended to __array~1~__
++
+** ( __hash~1~__ __hash~2~__ -- __hash~3~__ )
++
+__hash~3~__: joined __hash~1~__ and __hash~2~__
++
+** ( __string~1~__ __string~2~__ -- __string~3~__ )
++
+__string~3~__: __string~2~__ appended to __string~1~__
+--
++
+Add two numbers, or concatenate two arrays, or join two hashes, or concatenate two strings.
++
+For boolean 1 bit arithmetic this is equivalent to 'xor'.
++
+.Examples
+```
+
+10 20 add # 30
+true true add # false
+[ 10 20 ] [ 30 40 ] add # [ 10 20 30 40 ]
+( "foo" 10 ) ( "bar" 20 ) add # ( "bar" 20 "foo" 10 )
+"abc" "def" add # "abcdef"
+```
++
+See also: xref:abs[+abs+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+and+** - and [[and]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ and __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ and __bool~2~__
++
+--
++
+.Examples
+```
+
+15 4 and # 4
+true false and # false
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+blt+** - copy rectangular region [[blt]]
++
+--
+** ( __gstate~1~__ __gstate~2~__ -- )
+--
++
+Copy from the drawing region of __gstate~2~__ to the drawing region of __gstate~1~__, at the drawing pos of __gstate~1~__.
++
+.Examples
+```
+
+/cat_pic gstate def
+cat_pic "cat.jpg" readfile unpackimage setcanvas
+0 0 setpos getgstate cat_pic blt # show cat picture
+```
++
+See also: xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+canvas+** - create canvas [[canvas]]
++
+--
+** ( __int~1~__ __int~2~__ -- __canvas~1~__ )
++
+__int~1~__: width
++
+__int~2~__: height
+--
++
+Create a new empty canvas of the specified size.
++
+.Examples
+```
+
+800 600 canvas
+```
++
+See also: xref:blt[+blt+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+cmp+** - compare [[cmp]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ <=> __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __int~4~__ )
++
+__int~4~__: __bool~1~__ <=> __bool~2~__
++
+** ( __string~1~__ __string~2~__ -- __int~5~__ )
++
+__int~5~__: __string~1~__ <=> __string~2~__
++
+** ( __any~1~__ __any~2~__ -- __int~6~__ )
++
+__int~6~__: __any~1~__ <=> __any~2~__
+--
++
+For pairs of booleans, integers, and strings the values are compared. For
+all other combinations the internal object id is compared.
++
+The result is -1, 1, 0 if the first argument is less than, greater than,
+or equal to the second argument, respectively.
++
+.Examples
+```
+
+10 20 cmp # -1
+true false cmp # 1
+"abc" "abc" cmp # 0
+[ 10 20 ] [ 10 20 ] cmp # varies
+0 false cmp # varies
+0 nil cmp # varies
+"abc" [ 10 ] cmp # varies
+
+/foo [ 10 20 ] def
+/bar foo def
+foo bar cmp # 0
+```
++
+See also: xref:eq[+eq+], xref:ge[+ge+], xref:gt[+gt+], xref:le[+le+], xref:lt[+lt+], xref:ne[+ne+]
+
+* **+debug+** - start debug console [[debug]]
++
+--
+** ( -- )
+--
++
+Stop code execution and start debug console.
++
+You can leave (and re-enter) the debug console with `^D` but note that this
+doesn't resume program execution. Use the `run` (or `r`) console command for this.
++
+.Examples
+```
+
+/foo { debug 10 20 } def
+foo # activate debug console when 'foo' is run
+```
++
+See also: xref:def[+def+], xref:gdef[+gdef+], xref:ldef[+ldef+]
+
+* **+def+** - define new word [[def]]
++
+--
+** ( __word~1~__ __any~1~__ -- )
+--
++
+If __word~1~__ does not exist, define __word~1~__ in the current context.
++
+If __word~1~__ does already exist, redefine __word~1~__ in the context in which it is defined.
++
+.Examples
+```
+/x 100 def # define x as 100
+/neg { -1 mul } def # define a function that negates its argument
+```
++
+See also: xref:debug[+debug+], xref:gdef[+gdef+], xref:ldef[+ldef+]
+
+* **+delete+** - delete an array, hash, or string element [[delete]]
++
+--
+** ( __array~1~__ __int~1~__ -- )
++
+__array~1~__: array to modify
++
+__int~1~__: element index
++
+** ( __hash~1~__ __string~1~__ -- )
++
+__hash~1~__: hash to modify
++
+__string~1~__: key
++
+** ( __string~2~__ __int~2~__ -- )
++
+__string~2~__: string to modify
++
+__int~2~__: element index
+--
++
+Delete the respective element of __array~1~__, __hash~1~__, or __string~2~__. The length
+of __array~1~__ andstring_2 will be reduced by 1.
++
+Note that string constants are read-only and cannot be modified.
++
+.Examples
+```
+
+/x [ 10 20 30 ] def
+x 1 delete # x is now [ 10 30 ]
+
+/y ( "foo" 10 "bar" 20 ) def
+y "foo" delete # y is now ( "bar" 20 )
+
+/z "ABC" mem def # mem is needed to create a writable copy
+z 1 delete # z is now "AC"
+```
++
+See also: xref:get[+get+], xref:length[+length+], xref:put[+put+]
+
+* **+dim+** - get graphics object dimension [[dim]]
++
+--
+** ( __canvas~1~__ -- __int~1~__ __int~2~__ )
+** ( __font~1~__ -- __int~1~__ __int~2~__ )
+** ( __gstate~1~__ -- __int~1~__ __int~2~__ )
++
+__int~1~__: width
++
+__int~2~__: height
+--
++
+Get dimension of graphics object. For a canvas it is its size, for a
+graphics state it is the size of the associated region, for a fixed size
+font it is its glyph size, for proportional font the width is 0 and the
+height is the font height.
++
+.Examples
+```
+
+getconsolegstate getcanvas dim # 800 600
+getconsolegstate dim # 640 480
+getconsolegstate getfont dim # 8 16
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+div+** - division [[div]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ / __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ / __bool~2~__
+--
++
+Divide __int~1~__ by __int~2~__.
++
+You can do a 1 bit division with boolean values. Note that this will run
+into a division by zero exception if __bool~2~__ is false.
++
+.Examples
+```
+
+200 30 div # 6
+true true div # true
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+dup+** - duplicate TOS [[dup]]
++
+--
+** ( __any~1~__ -- __any~1~__ __any~1~__ )
+--
++
+Duplicate the top-of-stack element.
++
+.Examples
+```
+
+10 dup # 10 10
+```
++
+See also: xref:exch[+exch+], xref:index[+index+], xref:over[+over+], xref:pop[+pop+], xref:roll[+roll+], xref:rot[+rot+]
+
+* **+eq+** - equal [[eq]]
++
+--
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ == __bool~2~__
++
+** ( __int~1~__ __int~2~__ -- __bool~4~__ )
++
+__bool~4~__: __int~1~__ == __int~2~__
++
+** ( __string~1~__ __string~2~__ -- __bool~5~__ )
++
+__bool~5~__: __string~1~__ == __string~2~__
++
+** ( __any~1~__ __any~2~__ -- __bool~6~__ )
++
+__bool~6~__: __any~1~__ == __any~2~__
+--
++
+For pairs of booleans, integers, and strings the values are compared. For all
+other combinations the internal object id is compared.
++
+.Examples
+```
+
+10 20 eq # false
+true false eq # false
+"abc" "abc" eq # true
+[ 10 20 ] [ 10 20 ] eq # false
+0 false eq # false
+0 nil eq # false
+"abc" [ 10 ] eq # false
+
+/foo [ 10 20 ] def
+/bar foo def
+foo bar eq # true
+```
++
+See also: xref:cmp[+cmp+], xref:ge[+ge+], xref:gt[+gt+], xref:le[+le+], xref:lt[+lt+], xref:ne[+ne+]
+
+* **+exch+** - swap upper two stack elements [[exch]]
++
+--
+** ( __any~1~__ __any~2~__ -- __any~2~__ __any~1~__ )
+--
++
+Swap the two topmost stack elements.
++
+.Examples
+```
+
+10 20 exch # 20 10
+```
++
+See also: xref:dup[+dup+], xref:index[+index+], xref:over[+over+], xref:pop[+pop+], xref:roll[+roll+], xref:rot[+rot+]
+
+* **+exec+** - execute object [[exec]]
++
+--
+** ( __ref~1~__ -- )
++
+__ref~1~__: word reference
++
+** ( __code~1~__ -- )
++
+__code~1~__: code block
+--
++
+Executes the given code block or looks up and executes the word reference.
++
+.Examples
+```
+
+{ 10 20 } exec # 10 20
+
+/foo "abc" def
+foo # "abc"
+/foo exec # "abc"
+```
++
+See also: xref:exit[+exit+], xref:for[+for+], xref:forall[+forall+], xref:loop[+loop+], xref:repeat[+repeat+], xref:return[+return+], xref:run[+run+]
+
+* **+exit+** - leave loop/repeat/for/forall loop [[exit]]
++
+--
+** ( -- )
+--
++
+Exit from current loop.
++
+.Examples
+```
+
+0 1 10 { dup 4 eq { exit } if } for # 0 1 2 3 4
+```
++
+See also: xref:exec[+exec+], xref:for[+for+], xref:forall[+forall+], xref:if[+if+], xref:ifelse[+ifelse+], xref:loop[+loop+], xref:repeat[+repeat+], xref:return[+return+], xref:run[+run+]
+
+* **+fillrect+** - draw filled rectangle [[fillrect]]
++
+--
+** ( __int~1~__ __int~2~__ -- )
++
+__int~1~__: width
++
+__int~2~__: height
+--
++
+Draw filled rectangle (using current color) at current position. The
+rectangle is clipped at the current drawing region.
++
+.Examples
+```
+
+200 100 fillrect
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+for+** - run code block repeatedly, with counter [[for]]
++
+--
+** ( __int~1~__ __int~2~__ __int~3~__ __code~1~__ -- )
++
+__int~1~__: start value
++
+__int~2~__: increment value
++
+__int~3~__: maximum value (inclusive)
+--
++
+Run __code~1~__ repeatedly and put the current counter value on the stack in every iteration.
++
+The counter starts with __int~1~__ and is incremented by __int~2~__ until it
+reaches __int~3~__. The code block is executed with the start value and then
+as long as the counter is less than or equal to the maximum value.
++
+The increment may be negative. In that case the loop is executed as long as the counter
+is greater than or equal to the maximum value.
++
+If the increment is 0, the loop is not executed.
++
+.Examples
+```
+
+0 1 4 { } for # 0 1 2 3 4
+0 -2 -5 { } for # 0 -2 -4
+```
++
+See also: xref:exec[+exec+], xref:exit[+exit+], xref:forall[+forall+], xref:if[+if+], xref:ifelse[+ifelse+], xref:loop[+loop+], xref:repeat[+repeat+], xref:return[+return+], xref:run[+run+]
+
+* **+forall+** - loop over all elements [[forall]]
++
+--
+** ( __array~1~__ __code~1~__ -- )
+** ( __hash~1~__ __code~1~__ -- )
+** ( __string~1~__ __code~1~__ -- )
+--
++
+Run __code~1~__ for each element of __array~1~__, __hash~1~__, or __string~1~__.
++
+For __array~1~__ and __string~1~__, each element is put on the stack and __code~1~__ is run.
++
+For __hash~1~__, each key and value pair are put on the stack and __code~1~__ is run.
+The hash keys are sorted.
++
+Note that __string~1~__ is interpreted as a sequence of bytes, not UTF8-encoded characters.
++
+.Examples
+```
+
+[ 10 20 30 ] { } forall # 10 20 30
+( "foo" 10 "bar" 20 ) { } forall # "bar" 20 "foo" 10
+"ABC" { } forall # 65 66 67
+```
++
+See also: xref:exec[+exec+], xref:exit[+exit+], xref:for[+for+], xref:if[+if+], xref:ifelse[+ifelse+], xref:loop[+loop+], xref:repeat[+repeat+], xref:return[+return+], xref:run[+run+]
+
+* **+freeze+** - make object read-only [[freeze]]
++
+--
+** ( __any~1~__ -- __any~1~__ )
+--
++
+Make any object read-only. A read-only object cannot be modified.
++
+Note that string constants are read-only by default.
++
+.Examples
+```
+
+[ 10 20 30 ] freeze # [ 10 20 30 ]
+0 delete # raises 'readonly' exception
+```
++
+See also: xref:readfile[+readfile+], xref:string[+string+], xref:utf8decode[+utf8decode+], xref:utf8encode[+utf8encode+]
+
+* **+gdef+** - define new global word [[gdef]]
++
+--
+** ( __word~1~__ __any~1~__ -- )
+--
++
+Define __word~1~__ in the global context.
++
+.Examples
+```
+/foo 300 gdef # define global word foo as 300
+```
++
+See also: xref:debug[+debug+], xref:def[+def+], xref:ldef[+ldef+]
+
+* **+ge+** - greater or equal [[ge]]
++
+--
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ >= __bool~2~__
++
+** ( __int~1~__ __int~2~__ -- __bool~4~__ )
++
+__bool~4~__: __int~1~__ >= __int~2~__
++
+** ( __string~1~__ __string~2~__ -- __bool~5~__ )
++
+__bool~5~__: __string~1~__ >= __string~2~__
++
+** ( __any~1~__ __any~2~__ -- __bool~6~__ )
++
+__bool~6~__: __any~1~__ >= __any~2~__
+--
++
+For pairs of booleans, integers, and strings the values are compared. For all
+other combinations the internal object id is compared.
++
+.Examples
+```
+
+10 20 ge # false
+true false ge # true
+"abd" "abc" ge # true
+[ 10 20 ] [ 10 20 ] ge # varies
+0 false ge # varies
+0 nil ge # varies
+"abc" [ 10 ] ge # varies
+```
++
+See also: xref:cmp[+cmp+], xref:eq[+eq+], xref:gt[+gt+], xref:le[+le+], xref:lt[+lt+], xref:ne[+ne+]
+
+* **+get+** - get array, hash, or string element [[get]]
++
+--
+** ( __array~1~__ __int~1~__ -- )
++
+__array~1~__: array to modify
++
+__int~1~__: element index
++
+** ( __hash~1~__ __string~1~__ -- )
++
+__hash~1~__: hash to modify
++
+__string~1~__: key
++
+** ( __string~2~__ __int~2~__ -- )
++
+__string~2~__: string to modify
++
+__int~2~__: element index
+--
++
+Read the respective element of __array~1~__, __hash~1~__, or __string~2~__.
++
+.Examples
+```
+
+[ 10 20 30 ] 2 get # 30
+( "foo" 10 "bar" 20 ) "foo" get # 10
+"ABC" 1 get # 66
+```
++
+See also: xref:delete[+delete+], xref:length[+length+], xref:put[+put+]
+
+* **+getbgcolor+** - get background color [[getbgcolor]]
++
+--
+** ( -- __int~1~__ )
++
+__int~1~__: color
+--
++
+Return current background color.
++
+A color is a RGB value with red in bits 16-23, green in bits 8-15 and
+blue in bits 0-7. This is independent of what the graphics card is actually using.
++
+.Examples
+```
+
+getcolor # 0 (black)
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+getcanvas+** - get canvas [[getcanvas]]
++
+--
+** ( __gstate~1~__ -- __canvas~1~__ )
+** ( __gstate~1~__ -- __nil__ )
++
+__gstate~1~__: graphics state
++
+__canvas~1~__: canvas object
+--
++
+Get canvas object from graphics state. A canvas is a memory area with
+associated width and height. All drawing operations are done on canvas
+objects. If no canvas is associated with the graphics state, return __nil__.
++
+.Examples
+```
+
+getgstate getcanvas dim # 800 600
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+getcolor+** - get drawing color [[getcolor]]
++
+--
+** ( -- __int~1~__ )
++
+__int~1~__: color
+--
++
+Return current drawing color.
++
+A color is a RGB value with red in bits 16-23, green in bits 8-15 and
+blue in bits 0-7. This is independent of what the graphics card is actually using.
++
+.Examples
+```
+
+getcolor # 0xffffff (white)
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+getconsolegstate+** - get graphics state of the debug console [[getconsolegstate]]
++
+--
+** ( -- __gstate~1~__ )
+** ( -- __nil__ )
+--
++
+Get graphics state of the debug console. If none has been set, return __nil__.
++
+.Examples
+```
+
+/saved_state getconsolegstate def # save current debug console state
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+getdict+** - get active dictionary [[getdict]]
++
+--
+** ( -- __hash~1~__ )
+** ( -- __nil__ )
++
+__hash~1~__: dictionary
+--
++
+Return the currently active dictionary or __nil__, if the current context
+does not (yet) have a dictionary.
++
+A dictionary will only be created on demand - that is, the first time a
+word is defined in the current context.
++
+When a program is started the global context is created containing a
+dictionary with all primitive words.
++
+.Examples
+```
+
+/foo { getdict } def
+foo # nil
+
+/bar { /x 10 ldef getdict } def
+bar # ( /x 10 )
+```
++
+See also: xref:code_start[+{+], xref:code_end[+}+], xref:hash_start[+(+], xref:hash_end[+)+], xref:array_start[+[+], xref:array_end[+]+], xref:getparent[+getparent+], xref:setdict[+setdict+], xref:setparent[+setparent+]
+
+* **+getfont+** - get font [[getfont]]
++
+--
+** ( __gstate~1~__ -- __font~1~__ )
+** ( __gstate~1~__ -- __nil__ )
++
+__gstate~1~__: graphics state
++
+__font~1~__: font
+--
++
+Get font from graphics state.
++
+.Examples
+```
+
+getgstate getfont # current font
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+getgstate+** - get graphics state [[getgstate]]
++
+--
+** ( -- __gstate~1~__ )
+** ( -- __nil__ )
+--
++
+Get current graphics state. If none has been set, return __nil__.
++
+The graphics state consists of a canvas to draw into, a region describing
+a rectangular drawing and clipping area in that canvas, a drawing
+position (relative to the drawing region), drawing color, background
+color (for text), and a text font.
++
+.Examples
+```
+
+/saved_state getgstate def # save current graphics state
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+getparent+** - get parent of context, font, or hash [[getparent]]
++
+--
+** ( __context~1~__ -- __context~2~__ )
++
+__context~2~__: parent of __context~1~__ or nil
++
+** ( __font~1~__ -- __font~2~__ )
++
+__font~2~__: parent of __font~1~__ or nil
++
+** ( __hash~1~__ -- __hash~2~__ )
++
+__hash~2~__: parent of __hash~1~__ or nil
+--
++
+If a word lookup fails in a context, the lookup continues in the parent
+context.
++
+If a glyph lookup fails in a font, the lookup continues in the parent
+font.
++
+If a key cannot be found in a hash, the lookup continues in the parent
+hash.
++
+.Examples
+```
+
+/x ( "foo" 10 "bar" 20 ) def
+/y ( "zap" 30 ) def
+x getparent # nil
+x y setparent
+x getparent # ( "zap" 30 )
+```
++
+See also: xref:code_start[+{+], xref:code_end[+}+], xref:hash_start[+(+], xref:hash_end[+)+], xref:array_start[+[+], xref:array_end[+]+], xref:getdict[+getdict+], xref:setdict[+setdict+], xref:setparent[+setparent+]
+
+* **+getpixel+** - read pixel [[getpixel]]
++
+--
+** ( -- __int~1~__ )
+** ( -- __nil__ )
++
+__int~1~__: color
+--
++
+Read pixel at drawing position from canvas in current graphics state. If
+the position is outside the drawing region, return __nil__.
++
+.Examples
+```
+
+getpixel
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+getpos+** - get drawing position [[getpos]]
++
+--
+** ( -- __int~1~__ __int~2~__ )
++
+__int~1~__: x
++
+__int~2~__: y
+--
++
+Return current drawing position. The position is relative to the drawing region in the graphics state.
++
+.Examples
+```
+
+getpos # 0 0
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+getregion+** - get drawing region [[getregion]]
++
+--
+** ( __gstate~1~__ -- __int~1~__ __int~2~__ __int~3~__ __int~4~__ )
++
+__gstate~1~__: graphics state
++
+__int~1~__: x
++
+__int~2~__: y
++
+__int~3~__: width
++
+__int~4~__: height
+--
++
+Get drawing region associated with graphics state. Any drawing operation
+will be relative to this region. Graphics output will be clipped at the
+region boundaries.
++
+.Examples
+```
+
+getgstate getregion # 0 0 800 600
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+gstate+** - create graphics state [[gstate]]
++
+--
+** ( -- __gstate~1~__ )
+--
++
+Create a new empty graphics state gate_1.
++
+.Examples
+```
+
+gstate
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+gt+** - greater than [[gt]]
++
+--
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ > __bool~2~__
++
+** ( __int~1~__ __int~2~__ -- __bool~4~__ )
++
+__bool~4~__: __int~1~__ > __int~2~__
++
+** ( __string~1~__ __string~2~__ -- __bool~5~__ )
++
+__bool~5~__: __string~1~__ > __string~2~__
++
+** ( __any~1~__ __any~2~__ -- __bool~6~__ )
++
+__bool~6~__: __any~1~__ > __any~2~__
+--
++
+For pairs of booleans, integers, and strings the values are compared. For all
+other combinations the internal object id is compared.
++
+.Examples
+```
+
+10 20 gt # false
+true false gt # true
+"abd" "abc" gt # true
+[ 10 20 ] [ 10 20 ] gt # varies
+0 false gt # varies
+0 nil gt # varies
+"abc" [ 10 ] gt # varies
+```
++
+See also: xref:cmp[+cmp+], xref:eq[+eq+], xref:ge[+ge+], xref:le[+le+], xref:lt[+lt+], xref:ne[+ne+]
+
+* **+if+** - conditional execution [[if]]
++
+--
+** (__bool~1~__ __code~1~__ -- )
+** (__int~1~__ __code~1~__ -- )
+** (__nil__ __code~1~__ -- )
+** (__any~1~__ __code~1~__ -- )
++
+__code~1~__: code block to run if condition evaluates to 'true'
+--
++
+The condition is false for: boolean false, integer 0, or __nil__. In all other cases it is true.
++
+.Examples
+```
+
+true { "ok" show } if # "ok"
+50 { "ok" show } if # "ok"
+nil { "ok" show } if # shows nothing
+"" { "ok" show } if # "ok"
+```
++
+See also: xref:exit[+exit+], xref:for[+for+], xref:forall[+forall+], xref:ifelse[+ifelse+], xref:loop[+loop+], xref:repeat[+repeat+], xref:return[+return+]
+
+* **+ifelse+** - conditional execution [[ifelse]]
++
+--
+** (__bool~1~__ __code~1~__ __code~2~__ -- )
+** (__int~1~__ __code~1~__ __code~2~__ -- )
+** (__nil__ __code~1~__ __code~2~__ -- )
+** (__any~1~__ __code~1~__ __code~2~__ -- )
++
+__code~1~__: code block to run if condition evaluates to 'true'
++
+__code~2~__: code block to run if condition evaluates to 'false'
+--
++
+The condition is false for: boolean false, integer 0, or __nil__. In all other cases it is true.
++
+.Examples
+```
+
+false { "ok" } { "bad" } ifelse show # "bad"
+20 { "ok" } { "bad" } ifelse show # "ok"
+nil { "ok" } { "bad" } ifelse sho # "bad"
+"" { "ok" } { "bad" } ifelse show # "ok"
+```
++
+See also: xref:exit[+exit+], xref:for[+for+], xref:forall[+forall+], xref:if[+if+], xref:loop[+loop+], xref:repeat[+repeat+], xref:return[+return+]
+
+* **+index+** - copy stack element [[index]]
++
+--
+** ( __any~n~__ ... __any~0~__ __int~1~__ -- __any~n~__ ... __any~0~__ __any~n~__ )
++
+__int~1~__: element position on stack (n is equal to __int~1~__)
+--
++
+Copy the __int~1~__-th-from-top element on the top-of-stack.
++
+.Examples
+```
+
+10 20 30 40 3 index # 10 20 30 40 10
+
+/dup { 0 index } def # definition of 'dup'
+
+/over { 1 index } def # definition of 'over'
+```
++
+See also: xref:dup[+dup+], xref:exch[+exch+], xref:over[+over+], xref:pop[+pop+], xref:roll[+roll+], xref:rot[+rot+]
+
+* **+ldef+** - define new local word [[ldef]]
++
+--
+** ( __word~1~__ __any~1~__ -- )
+--
++
+Define __word~1~__ in the current local context.
++
+.Examples
+```
+/foo 200 ldef # define local word foo as 200
+```
++
+See also: xref:debug[+debug+], xref:def[+def+], xref:gdef[+gdef+]
+
+* **+le+** - less or equal [[le]]
++
+--
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ <= __bool~2~__
++
+** ( __int~1~__ __int~2~__ -- __bool~4~__ )
++
+__bool~4~__: __int~1~__ <= __int~2~__
++
+** ( __string~1~__ __string~2~__ -- __bool~5~__ )
++
+__bool~5~__: __string~1~__ <= __string~2~__
++
+** ( __any~1~__ __any~2~__ -- __bool~6~__ )
++
+__bool~6~__: __any~1~__ <= __any~2~__
+--
++
+For pairs of booleans, integers, and strings the values are compared. For all
+other combinations the internal object id is compared.
++
+.Examples
+```
+
+10 20 le # true
+true false le # false
+"abd" "abc" le # false
+[ 10 20 ] [ 10 20 ] le # varies
+0 false le # varies
+0 nil le # varies
+"abc" [ 10 ] le # varies
+```
++
+See also: xref:cmp[+cmp+], xref:eq[+eq+], xref:ge[+ge+], xref:gt[+gt+], xref:lt[+lt+], xref:ne[+ne+]
+
+* **+length+** - get size of array, hash, or string [[length]]
++
+--
+** ( __array~1~__ -- __int~1~__ )
++
+__int~1~__: number of elements in __array~1~__
++
+** ( __hash~1~__ -- __int~2~__ )
++
+__int~2~__: number of key - value pairs in __hash~1~__
++
+** ( __string~1~__ -- __int~3~__ )
++
+__int~3~__: number of bytes in __string~1~__
+--
++
+Put the length of __array~1~__, __hash~1~__, or __string~1~__ on the stack.
++
+.Examples
+```
+
+[ 10 20 30 ] length # 3
+( "foo" 10 "bar" 20 ) length # 2
+"ABC" length # 3
+```
++
+See also: xref:delete[+delete+], xref:get[+get+], xref:put[+put+]
+
+* **+lineto+** - draw line [[lineto]]
++
+--
+** ( __int~1~__ __int~2~__ -- )
++
+__int~1~__: x
++
+__int~2~__: y
+--
++
+Draw line from current position to the specified x and y coordinates
+using the current color. The drawing position is updated to the end
+position. Line segments outside the drawing region are not drawn.
++
+.Examples
+```
+
+100 200 lineto
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+loop+** - endless loop [[loop]]
++
+--
+** ( __code~1~__ -- )
+--
++
+Repeat __code~1~__ forever until you exit the loop explicitly.
++
+.Examples
+```
+
+{ "Help!" show } loop
+```
++
+See also: xref:exec[+exec+], xref:exit[+exit+], xref:for[+for+], xref:forall[+forall+], xref:if[+if+], xref:ifelse[+ifelse+], xref:repeat[+repeat+], xref:return[+return+], xref:run[+run+]
+
+* **+lt+** - less than [[lt]]
++
+--
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ < __bool~2~__
++
+** ( __int~1~__ __int~2~__ -- __bool~4~__ )
++
+__bool~4~__: __int~1~__ < __int~2~__
++
+** ( __string~1~__ __string~2~__ -- __bool~5~__ )
++
+__bool~5~__: __string~1~__ < __string~2~__
++
+** ( __any~1~__ __any~2~__ -- __bool~6~__ )
++
+__bool~6~__: __any~1~__ < __any~2~__
+--
++
+For pairs of booleans, integers, and strings the values are compared. For all
+other combinations the internal object id is compared.
++
+.Examples
+```
+
+10 20 lt # true
+true false lt # false
+"abd" "abc" lt # false
+[ 10 20 ] [ 10 20 ] lt # varies
+0 false lt # varies
+0 nil lt # varies
+"abc" [ 10 ] lt # varies
+```
++
+See also: xref:cmp[+cmp+], xref:eq[+eq+], xref:ge[+ge+], xref:gt[+gt+], xref:le[+le+], xref:ne[+ne+]
+
+* **+max+** - maximum [[max]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: maximum(__int~1~__, __int~2~__)
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ or __bool~2~__
++
+__int~3~__ is the larger value of __int~1~__ and __int~2~__.
+--
++
+For boolean 1 bit arithmetic this is equivalent to 'or'
++
+.Examples
+```
+
+10 20 max # 20
+true false max # true
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+min+** - minimum [[min]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: minimum(__int~1~__, __int~2~__)
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ and __bool~2~__
++
+__int~3~__ is the smaller value of __int~1~__ and __int~2~__.
+--
++
+For boolean 1 bit arithmetic this is equivalent to 'and'
++
+.Examples
+```
+
+10 20 min # 10
+true false min # false
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+mod+** - remainder [[mod]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ % __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ / __bool~2~__
++
+__int~3~__ is the remainder dividing __int~1~__ by __int~2~__.
+--
++
+You can get the remainder from a 1 bit division with boolean values. Note
+that this will run into a division by zero exception if __bool~2~__ is false.
++
+.Examples
+```
+
+200 30 mod # 20
+true true mod # false
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+mul+** - multiplication [[mul]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ * __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ and __bool~2~__
+--
++
+Multiply __int~1~__ by __int~2~__.
++
+For boolean 1 bit arithmetic this is equivalent to 'and'.
++
+.Examples
+```
+
+20 30 mul # 600
+true false mul # false
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+ne+** - not equal [[ne]]
++
+--
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ != __bool~2~__
++
+** ( __int~1~__ __int~2~__ -- __bool~4~__ )
++
+__bool~4~__: __int~1~__ != __int~2~__
++
+** ( __string~1~__ __string~2~__ -- __bool~5~__ )
++
+__bool~5~__: __string~1~__ != __string~2~__
++
+** ( __any~1~__ __any~2~__ -- __bool~6~__ )
++
+__bool~6~__: __any~1~__ != __any~2~__
+--
++
+For pairs of booleans, integers, and strings the values are compared. For all
+other combinations the internal object id is compared.
++
+.Examples
+```
+
+10 20 ne # true
+true false ne # true
+"abc" "abc" ne # false
+[ 10 20 ] [ 10 20 ] ne # true
+0 false ne # true
+0 nil ne # true
+"abc" [ 10 ] ne # true
+
+/foo [ 10 20 ] def
+/bar foo def
+foo bar ne # false
+```
++
+See also: xref:cmp[+cmp+], xref:eq[+eq+], xref:ge[+ge+], xref:gt[+gt+], xref:le[+le+], xref:lt[+lt+]
+
+* **+neg+** - negation [[neg]]
++
+--
+** ( __int~1~__ -- __int~2~__ )
++
+__int~2~__: -__int~1~__
++
+** ( __bool~1~__ -- __bool~2~__ )
++
+__bool~2~__: -__bool~1~__
+--
++
+Negate __int~1~__ (change sign).
++
+For boolean 1 bit arithmetic the value is unchanged (this is not a 'not' operation).
++
+.Examples
+```
+
+20 neg # -20
+true neg # true
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+newfont+** - create font object [[newfont]]
++
+--
+** ( __string~1~__ -- __font~1~__ )
+** ( __string~1~__ -- __nil__ )
++
+__string~1~__: font data
++
+__font~1~__: font object
+--
++
+Parse font data in __string~1~__ and create font object. If __string~1~__ does not
+contain valid font data, return __nil__.
++
+.Examples
+```
+
+/foo_font "foo.fnt" readfile newfont def # create font from file "foo.fnt"
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+not+** - not [[not]]
++
+--
+** ( __int~1~__ -- __int~2~__ )
++
+__int~2~__: -__int~1~__ - 1
++
+** ( __bool~1~__ -- __bool~2~__ )
++
+__bool~2~__: !__bool~1~__
++
+--
++
+.Examples
+```
+
+20 not # -21
+true not # false
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+or+** - or [[or]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ or __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ or __bool~2~__
++
+--
++
+.Examples
+```
+
+15 4 or # 15
+true false or # true
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+over+** - copy TOS-1 to TOS [[over]]
++
+--
+** ( __any~1~__ __any~2~__ -- __any~1~__ __any~2~__ __any~1~__ )
+--
++
+Put a copy of the second-from-top element on the top-of-stack.
++
+.Examples
+```
+
+10 20 over # 10 20 10
+```
++
+See also: xref:dup[+dup+], xref:exch[+exch+], xref:index[+index+], xref:pop[+pop+], xref:roll[+roll+], xref:rot[+rot+]
+
+* **+pop+** - remove TOS [[pop]]
++
+--
+** ( __any~1~__ -- )
+--
++
+Remove the top-of-stack element.
++
+.Examples
+```
+
+10 20 pop # 10
+```
++
+See also: xref:dup[+dup+], xref:exch[+exch+], xref:index[+index+], xref:over[+over+], xref:roll[+roll+], xref:rot[+rot+]
+
+* **+put+** - set array, hash, or string element [[put]]
++
+--
+** ( __array~1~__ __int~1~__ __any~1~__ -- )
++
+__array~1~__: array to modify
++
+__int~1~__: element index
++
+__any~1~__: new value
++
+** ( __hash~1~__ __string~1~__ __any~2~__ -- )
++
+__hash~1~__: hash to modify
++
+__string~1~__: key
++
+__any~2~__: new value
++
+** ( __string~2~__ __int~2~__ __int~3~__ -- )
++
+__string~2~__: string to modify
++
+__int~2~__: element index
++
+__int~3~__: new value
+--
++
+Set the respective element of __array~1~__, __hash~1~__, or __string~2~__.
++
+Note that string constants are read-only and cannot be modified.
++
+.Examples
+```
+
+/x [ 10 20 30 ] def
+x 2 40 put # x is now [ 10 20 40 ]
+
+/y ( "foo" 10 "bar" 20 ) def
+y "bar" 40 put # y is now ( "foo" 10 "bar" 40 )
+
+/z "ABC" mem def # mem is needed to create a writable copy
+z 1 68 put # z is now "ADC"
+```
++
+See also: xref:delete[+delete+], xref:get[+get+], xref:length[+length+]
+
+* **+putpixel+** - set pixel [[putpixel]]
++
+--
+** ( -- )
+--
++
+Set pixel with current color at drawing position in canvas in current
+graphics state. If the position is outside the drawing region, nothing is
+drawn.
++
+.Examples
+```
+
+setpixel
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+readfile+** - read file [[readfile]]
++
+--
+** ( __string~1~__ -- __string~2~__ )
+** ( __string~1~__ -- __nil__ )
++
+__string~1~__: file name
++
+__string~2~__: file content
+--
++
+Read entire file and return its content. If the file could not be read, return __nil__.
++
+.Examples
+```
+
+"foo" readfile
+```
++
+See also: xref:freeze[+freeze+], xref:string[+string+], xref:utf8decode[+utf8decode+], xref:utf8encode[+utf8encode+]
+
+* **+repeat+** - repeat code block [[repeat]]
++
+--
+** ( __int~1~__ __code~1~__ -- )
+--
++
+Repeat __code~1~__ __int~1~__ times. If __int~1~__ is less or equal to 0, __code~1~__ is not run.
++
+.Examples
+```
+
+3 { "Help!" show } repeat # "Help!Help!Help!"
+```
++
+See also: xref:exec[+exec+], xref:exit[+exit+], xref:for[+for+], xref:forall[+forall+], xref:if[+if+], xref:ifelse[+ifelse+], xref:loop[+loop+], xref:return[+return+], xref:run[+run+]
+
+* **+return+** - leave current function [[return]]
++
+Exit from currently running function.
++
+.Examples
+```
+
+/foo { dup nil eq { return } if show } def
+"abc" foo # shows "abc"
+nil foo # does nothing
+```
++
+See also: xref:exec[+exec+], xref:exit[+exit+], xref:for[+for+], xref:forall[+forall+], xref:if[+if+], xref:ifelse[+ifelse+], xref:loop[+loop+], xref:repeat[+repeat+], xref:run[+run+]
+
+* **+roll+** - rotate stack elements [[roll]]
++
+--
+** ( __any~1~__ ... __any~n~__ __int~1~__ __int~2~__ -- __any~x~__ ... __any~y~__ )
++
+__int~1~__: number of stack elements to rotate (equal to index n)
++
+__int~2~__: rotation amount
+--
++
+Rotate the n elements __any~1~__ ... __any~n~__. The new positions are calculated as follows:
++
+x = (1 - __int~2~__) mod __int~1~__
++
+y = (n - __int~2~__) mod __int~1~__
++
+This can be seen as rotating __int~1~__ elements up by __int~2~__ resp. down by -__int~2~__.
++
+.Examples
+```
+
+10 20 30 40 50 5 2 roll # 40 50 10 20 30
+
+/rot { 3 -1 roll } def # definition of 'rot'
+```
++
+See also: xref:dup[+dup+], xref:exch[+exch+], xref:index[+index+], xref:over[+over+], xref:pop[+pop+], xref:rot[+rot+]
+
+* **+rot+** - rotate upper three stack elements [[rot]]
++
+--
+** ( __any~1~__ __any~2~__ __any~3~__ -- __any~2~__ __any~3~__ __any~1~__ )
+--
++
+Rotate __any~1~__ to the top-of-stack.
++
+.Examples
+```
+
+10 20 30 rot # 20 30 10
+```
++
+See also: xref:dup[+dup+], xref:exch[+exch+], xref:index[+index+], xref:over[+over+], xref:pop[+pop+], xref:roll[+roll+]
+
+* **+run+** - run code [[run]]
++
+--
+** ( __string~1~__ -- )
++
+__string~1~__: binary code
+--
++
+Load binary code and run it.
++
+Note: unlike 'exec' this does not open a new context but replaces the
+currently running code with the new one.
++
+.Examples
+```
+
+"new_program" readfile run
+```
++
+See also: xref:exec[+exec+], xref:exit[+exit+], xref:for[+for+], xref:forall[+forall+], xref:loop[+loop+], xref:repeat[+repeat+], xref:return[+return+]
+
+* **+setbgcolor+** - set background color [[setbgcolor]]
++
+--
+** ( __int~1~__ -- )
++
+__int~1~__: color
+--
++
+Set current background color.
++
+A color is a RGB value with red in bits 16-23, green in bits 8-15 and
+blue in bits 0-7. This is independent of what the graphics card is actually using.
++
+.Examples
+```
+
+0xff00 setcolor # green
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+setcanvas+** - set canvas [[setcanvas]]
++
+--
+** ( __gstate~1~__ __canvas~1~__ -- )
+** ( __gstate~1~__ __nil__ -- )
++
+__gstate~1~__: graphics state
++
+__canvas~1~__: canvas object
+--
++
+Set canvas of graphics state. A canvas is a memory area with
+associated width and height. All drawing operations are done on canvas
+objects. If __nil__ is passed, the canvas is removed from the graphics state.
++
+The drawing region of __gstate~1~__ is adjusted to match the canvas size. The
+drawing position is reset to x = 0, y = 0.
++
+.Examples
+```
+
+getgstate 800 600 canvas setcanvas
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+setcolor+** - set drawing color [[setcolor]]
++
+--
+** ( __int~1~__ -- )
++
+__int~1~__: color
+--
++
+Set current drawing color.
++
+A color is a RGB value with red in bits 16-23, green in bits 8-15 and
+blue in bits 0-7. This is independent of what the graphics card is actually using.
++
+.Examples
+```
+
+0xff0000 setcolor # red
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+setconsolegstate+** - set graphics state of the debug console [[setconsolegstate]]
++
+--
+** ( __gstate~1~__ -- )
+** ( __nil__ -- )
+--
++
+Set graphics state of the debug console. If __nil__ is passed, the current state is removed.
++
+.Examples
+```
+
+/saved_state getconsolegstate def
+...
+saved_state setconsolegstate # restore saved debug console state
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+setdict+** - set active dictionary [[setdict]]
++
+--
+** ( __hash~1~__ -- )
+** ( __nil__ -- )
++
+__hash~1~__: new active dictionary
+--
++
+Set the currently active dictionary. With __nil__, the dictionary is removed
+from the current context.
++
+.Examples
+```
+
+/foo { /x 10 ldef x } def
+foo # 10
+
+/bar { ( /x 10 ) setdict x } def
+bar # 10
+```
++
+See also: xref:code_start[+{+], xref:code_end[+}+], xref:hash_start[+(+], xref:hash_end[+)+], xref:array_start[+[+], xref:array_end[+]+], xref:getdict[+getdict+], xref:getparent[+getparent+], xref:setparent[+setparent+]
+
+* **+setfont+** - set font [[setfont]]
++
+--
+** ( __gstate~1~__ __font~1~__ -- )
+** ( __gstate~1~__ __nil__ -- )
++
+__gstate~1~__: graphics state
++
+__font~1~__: font
+--
++
+Set font in graphics state. If __nil__ is passed, any font is removed from the graphics state.
++
+.Examples
+```
+
+/foo_font "foo.fnt" readfile newfont def
+getgstate foo_font setfont # use "foo.fnt"
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+setgstate+** - set graphics state [[setgstate]]
++
+--
+** ( __gstate~1~__ -- )
+** ( __nil__ -- )
+--
++
+Set current graphics state. If __nil__ is passed, the current state is removed.
++
+The graphics state consists of a canvas to draw into, a region describing
+a rectangular drawing and clipping area in that canvas, a drawing
+position (relative to the drawing region), drawing color, background
+color (for text), and a text font.
++
+.Examples
+```
+
+/saved_state getgstate def # save current graphics state
+...
+saved_state setgstate # restore saved graphics state
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+setparent+** - set parent of context, font, or hash [[setparent]]
++
+--
+** ( __context~1~__ __context~2~__ -- )
+** ( __context~1~__ __nil__ -- )
++
+__context~2~__: new parent of __context~1~__
++
+** ( __font~1~__ __font~2~__ -- )
+** ( __font~1~__ __nil__ -- )
++
+__font~2~__: new parent of __font~1~__
++
+** ( __hash~1~__ __hash~2~__ -- )
+** ( __hash~1~__ __nil__ -- )
++
+__hash~2~__: new parent of __hash~1~__
+--
++
+If __nil__ is used as second argument, any existing parent link is removed.
++
+If a word lookup fails in a context, the lookup continues in the parent
+context.
++
+If a glyph lookup fails in a font, the lookup continues in the parent
+font.
++
+If a key cannot be found in a hash, the lookup continues in the parent
+hash.
++
+.Examples
+```
+
+/x ( "foo" 10 "bar" 20 ) def
+/y ( "zap" 30 ) def
+x "zap" get # nil
+x y setparent
+x "zap" get # 30
+x nil setparent
+x "zap" get # nil
+```
++
+See also: xref:code_start[+{+], xref:code_end[+}+], xref:hash_start[+(+], xref:hash_end[+)+], xref:array_start[+[+], xref:array_end[+]+], xref:getdict[+getdict+], xref:getparent[+getparent+], xref:setdict[+setdict+]
+
+* **+setpos+** - set drawing position [[setpos]]
++
+--
+** ( __int~1~__ __int~2~__ -- )
++
+__int~1~__: x
++
+__int~2~__: y
+--
++
+Set drawing position. The position is relative to the drawing region in the graphics state.
++
+.Examples
+```
+
+20 30 setpos
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setregion[+setregion+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+setregion+** - set drawing region [[setregion]]
++
+--
+** ( __gstate~1~__ __int~1~__ __int~2~__ __int~3~__ __int~4~__ -- )
++
+__gstate~1~__: graphics state
++
+__int~1~__: x
++
+__int~2~__: y
++
+__int~3~__: width
++
+__int~4~__: height
+--
++
+Set drawing region associated with graphics state. Any drawing operation
+will be relative to this region. Graphics output will be clipped at the
+region boundaries.
++
+.Examples
+```
+
+getgstate 10 10 200 100 setregion
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:show[+show+], xref:unpackimage[+unpackimage+]
+
+* **+shl+** - shift left [[shl]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ << __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ and !__bool~2~__
++
+--
++
+.Examples
+```
+
+1 4 shl # 16
+true false shl # true
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shr[+shr+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+show+** - show [[show]]
++
+--
+** ( __string~1~__ -- )
+--
++
+Print __string~1~__ at current cursor position in canvas associated with
+current graphics state.
++
+The cursor position is advanced to point at the end of the printed text.
+Newline ('\x0a') and carriage return ('\x0d') characters are interpreted
+and the cursor position is adjusted relative to the starting position.
++
+.Examples
+```
+
+"Hello!" show # print "Hello!"
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:unpackimage[+unpackimage+]
+
+* **+shr+** - shift right [[shr]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ >> __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ and !__bool~2~__
++
+--
++
+.Examples
+```
+
+16 4 shr # 1
+true false shr # true
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:sub[+sub+], xref:xor[+xor+]
+
+* **+string+** - create or duplicate string [[string]]
++
+--
+** ( __int~1~__ -- __string~1~__ )
++
+__int~1~__: length
++
+__string~1~__: new string with length __int~1~__
+** ( __string~2~__ -- __string~3~__ )
++
+__string~2~__: string to duplicate
++
+__string~3~__: copy of __string~2~__
+--
++
+There are two variants: given a number, a string of that length is
+created and initialized with zeros; given a string, a copy of that string is created.
++
+__int~1~__ may be 0 to create a zero-length string.
++
+Note: duplication works for all string-like objects. For example for word references and even code blocks.
++
+.Examples
+```
+
+2 string # creates an empty string of length 2: "\x00\x00"
+"abc" string # creates a copy of "abc"
+
+# even this works:
+/abc mem # a copy of /abc
+{ 10 20 } mem # a copy of the code block { 10 20 }
+```
++
+See also: xref:freeze[+freeze+], xref:readfile[+readfile+], xref:utf8decode[+utf8decode+], xref:utf8encode[+utf8encode+]
+
+* **+sub+** - subtraction [[sub]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ - __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ xor __bool~2~__
+--
++
+Subtract __int~2~__ from __int~1~__.
++
+For boolean 1 bit arithmetic this is equivalent to 'xor'.
++
+.Examples
+```
+
+100 30 sub # 70
+false true sub # true
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:xor[+xor+]
+
+* **+unpackimage+** - unpack image [[unpackimage]]
++
+--
+** ( __string~1~__ -- __canvas~1~__ )
+** ( __string~1~__ -- __nil__ )
++
+__string~1~__: image file data
+--
++
+Unpacks image and returns a canvas object with the image or __nil__ if the
+data dos not contain image data.
++
+.Examples
+```
+
+"foo.jpg" readfile unpackimage
+```
++
+See also: xref:blt[+blt+], xref:canvas[+canvas+], xref:dim[+dim+], xref:fillrect[+fillrect+], xref:getbgcolor[+getbgcolor+], xref:getcanvas[+getcanvas+], xref:getcolor[+getcolor+], xref:getconsolegstate[+getconsolegstate+], xref:getfont[+getfont+], xref:getgstate[+getgstate+], xref:getpixel[+getpixel+], xref:getpos[+getpos+], xref:getregion[+getregion+], xref:gstate[+gstate+], xref:lineto[+lineto+], xref:newfont[+newfont+], xref:putpixel[+putpixel+], xref:setbgcolor[+setbgcolor+], xref:setcanvas[+setcanvas+], xref:setcolor[+setcolor+], xref:setconsolegstate[+setconsolegstate+], xref:setfont[+setfont+], xref:setgstate[+setgstate+], xref:setpos[+setpos+], xref:setregion[+setregion+], xref:show[+show+]
+
+* **+utf8decode+** - decode Unicode string [[utf8decode]]
++
+--
+** (__string~1~__ -- __array~1~__ )
++
+__string~1~__: UTF8-encoded string
++
+__array~1~__: array with decoded chars
+--
++
+The array contains one element for each UTF8-encoded char. If __string~1~__
+contains non-UTF8-chars they are represented as the negated 8-bit value.
++
+.Examples
+```
+
+"ABC" utf8decode # [ 65 66 67 ]
+"Ä €" utf8decode # [ 196 32 8364 ]
+"A\xf0B" utf8decode # [ 65 -240 66 ]
+```
++
+See also: xref:freeze[+freeze+], xref:readfile[+readfile+], xref:string[+string+], xref:utf8encode[+utf8encode+]
+
+* **+utf8encode+** - encode Unicode string [[utf8encode]]
++
+--
+** (__array~1~__ -- __string~1~__ )
++
+__array~1~__: array with decoded chars
++
+__string~1~__: UTF8-encoded string
+--
++
+The array contains one element for each UTF8-encoded char. If __string~1~__
+should contain non-UTF8-chars they are represented as the negated 8-bit
+value in __array~1~__.
++
+.Examples
+```
+
+[ 65 66 67 ] utf8encode # "ABC"
+[ 196 32 8364 ] utf8encode # "Ä €"
+[ 65 -240 66 ] utf8encode # "A\xf0B"
+```
++
+See also: xref:freeze[+freeze+], xref:readfile[+readfile+], xref:string[+string+], xref:utf8decode[+utf8decode+]
+
+* **+xor+** - exclusive or [[xor]]
++
+--
+** ( __int~1~__ __int~2~__ -- __int~3~__ )
++
+__int~3~__: __int~1~__ xor __int~2~__
++
+** ( __bool~1~__ __bool~2~__ -- __bool~3~__ )
++
+__bool~3~__: __bool~1~__ xor __bool~2~__
++
+--
++
+.Examples
+```
+
+15 4 xor # 11
+true false or # true
+```
++
+See also: xref:abs[+abs+], xref:add[+add+], xref:and[+and+], xref:div[+div+], xref:max[+max+], xref:min[+min+], xref:mod[+mod+], xref:mul[+mul+], xref:neg[+neg+], xref:not[+not+], xref:or[+or+], xref:shl[+shl+], xref:shr[+shr+], xref:sub[+sub+]
+
+
diff --git a/doc/reference_template b/doc/reference_template
new file mode 100644
index 0000000..fd747a1
--- /dev/null
+++ b/doc/reference_template
@@ -0,0 +1,52 @@
+## Language reference
+
+Comments start with '#' and extend to the end of the line.
+
+To include some other source file, use the special `include` comment (note the double-`#`, and no quotes around the file name):
+
+.Examples
+```
+## include foo.gs
+```
+
+Numbers are 64 bit signed integers. Numerical and string constants are given in a C-like way.
+
+.Examples
+```
+123
+-456
+0x4567
+"Hi there\n"
+'\033'
+'\x1b'
+'\u20ac'
+```
+
+Strings are not zero-terminated and can contain any data (including zeros). Use `\xNN` to set arbitray binary values.
+Use `\uNNNN` or `\UNNNNNNNN` to include UTF8-encoded Unicode characters.
+
+.Examples
+```
+"1 Euro = 1 €\n"
+"1 Euro = 1 \u20ac\n"
+"1 Euro = 1 \xe2\x82\xac\n"
+```
+
+Logical operations return values of type bool. They are not identical with integers.
+
+.Examples
+```
+true
+false
+```
+
+There is a special value nil. It is not the same as 0.
+
+.Examples
+```
+nil
+```
+
+### Primitive words
+
+PRIMITIVE_WORD_LIST
diff --git a/doc/screen_01.png b/doc/screen_01.png
new file mode 100644
index 0000000..852af0d
Binary files /dev/null and b/doc/screen_01.png differ
diff --git a/doc/testing.adoc b/doc/testing.adoc
new file mode 100644
index 0000000..cde0880
--- /dev/null
+++ b/doc/testing.adoc
@@ -0,0 +1,74 @@
+== Tests
+
+Individual tests are subdirectories in the `tests` directory. Each test has
+a code snippet `main.gs` that is run using the debug script
+`tests/test_script` and leaves the result in `test.log`. That log is then additionally post-processed into
+other `+*.log+` files. `+*.log.ref+` are corresponding reference results.
+
+Run tests with `./run_tests`. Re-generate reference output with `./run_tests -r`.
+
+Each test runs a sample code snippet and compares the result in several ways with a stored reference.
+
+=== Test details
+
+Test results should be stable but for core language changes. Tests compare
+results in varying degrees of detail (e.g. 'mem' - 'trace' - 'basic') to
+make it easier to decide whether a change is to be expected or not.
+
+Basically every core language change will invalidate 'mem' results but leave
+'basic' intact. 'trace' changes but not that often.
+
+'code1' and 'code2' are the same as 'code' but with code optimizations enabled.
+
+'opt1' and 'opt2' are the same level of detail as 'trace' but with code optimizations enabled.
+
+==== 1. code
+
+Check the compiled binary code (no optimizations).
+
+==== 2. mem
+
+Check a full execution log including binary data content and memory layout.
+
+==== 3. trace
+
+Check a full execution log including variable values and internal object ids.
+
+==== 4. basic
+
+Check a simplified execution log including variable values but no internal object ids.
+
+==== 5. code1
+
+Check the compiled binary code (optimization with -O1).
+
+==== 6. opt1
+
+Check a full execution log including variable values and internal object ids (like
+'trace' but optimization with -O1).
+
+==== 7. code2
+
+Check the compiled binary code (optimization with -O2).
+
+==== 8. opt2
+
+Check a full execution log including variable values and internal object ids (like
+'trace' but optimization with -O2).
+
+==== 9. gc
+
+Check the garbage collector. This test is a bit different. It does not
+compare against a stored reference. After releasing all object references,
+the test verifies that the memory is in the initial state again.
+
+==== 10. screen
+
+Check the final screen (the drawing frame buffer). This includes a
+simplified graphical representation as well as a checksum over the frame
+buffer content.
+
+=== 64 bit vs. 32 bit code
+
+Tests will succeeed for both 64 bit and 32 bit compiled variants. Except for
+the `mem` tests as these tests are data structure size dependent.
diff --git a/files/bar.fnt b/files/bar.fnt
new file mode 100644
index 0000000..d976cc0
Binary files /dev/null and b/files/bar.fnt differ
diff --git a/files/cd/boot/x86_64/efi b/files/cd/boot/x86_64/efi
new file mode 100644
index 0000000..14c9e5b
Binary files /dev/null and b/files/cd/boot/x86_64/efi differ
diff --git a/files/cd/boot/x86_64/loader/initrd b/files/cd/boot/x86_64/loader/initrd
new file mode 100644
index 0000000..8c6b107
Binary files /dev/null and b/files/cd/boot/x86_64/loader/initrd differ
diff --git a/files/cd/boot/x86_64/loader/linux b/files/cd/boot/x86_64/loader/linux
new file mode 100644
index 0000000..e69de29
diff --git a/files/font1.psfu b/files/font1.psfu
new file mode 100644
index 0000000..c0459dd
Binary files /dev/null and b/files/font1.psfu differ
diff --git a/files/font2.psfu b/files/font2.psfu
new file mode 100644
index 0000000..8f8b665
Binary files /dev/null and b/files/font2.psfu differ
diff --git a/files/foo.fnt b/files/foo.fnt
new file mode 100644
index 0000000..b04d628
Binary files /dev/null and b/files/foo.fnt differ
diff --git a/files/grub.cfg b/files/grub.cfg
new file mode 100644
index 0000000..f1c3ab7
--- /dev/null
+++ b/files/grub.cfg
@@ -0,0 +1,54 @@
+with_gfx=1
+
+gfxmode=auto
+locale_dir=$prefix/locale
+lang=en_US
+
+search --no-floppy --file /boot/x86_64/efi --set
+prefix=($root)/boot/x86_64/grub2-efi
+
+insmod gzio
+insmod gettext
+insmod png
+insmod jpeg
+
+color_normal=light-gray/black
+color_highlight=white/light-gray
+
+gfxmode=800x600
+if [ "$with_gfx" = 1 ] ; then
+ insmod gfxterm
+ insmod gfxboot
+
+ theme=$prefix/themes/openSUSE/theme.txt
+ export theme
+
+ terminal_output gfxterm
+fi
+
+timeout=10
+
+menuentry 'Installation 0' --class opensuse --class gnu-linux --class gnu --class os {
+ set gfxpayload=keep
+ echo 'Loading kernel 0 ...'
+ linuxefi /boot/x86_64/loader/linux splash=silent
+ echo 'Loading initrd 0 ...'
+ initrdefi /boot/x86_64/loader/initrd
+}
+
+menuentry 'Installation 1' --class opensuse --class gnu-linux --class gnu --class os {
+ set gfxpayload=keep
+ echo 'Loading kernel 1 ...'
+ linuxefi /boot/x86_64/loader/linux splash=silent
+ echo 'Loading initrd 1 ...'
+ initrdefi /boot/x86_64/loader/initrd
+}
+
+menuentry 'Installation 2' --class opensuse --class gnu-linux --class gnu --class os {
+ set gfxpayload=keep
+ echo 'Loading kernel 2 ...'
+ linuxefi /boot/x86_64/loader/linux splash=silent
+ echo 'Loading initrd 2 ...'
+ initrdefi /boot/x86_64/loader/initrd
+}
+
diff --git a/files/katze_1024.jpg b/files/katze_1024.jpg
new file mode 100644
index 0000000..7c14d10
Binary files /dev/null and b/files/katze_1024.jpg differ
diff --git a/files/katze_800.jpg b/files/katze_800.jpg
new file mode 100644
index 0000000..06f0a94
Binary files /dev/null and b/files/katze_800.jpg differ
diff --git a/files/main.gs b/files/main.gs
new file mode 100644
index 0000000..2833fb2
--- /dev/null
+++ b/files/main.gs
@@ -0,0 +1,29 @@
+/cfont getconsolegstate getfont def
+/foo "foo.fnt" readfile newfont def
+/bar "bar.fnt" readfile newfont def
+
+/text "ABC 12345 xyz # * % & § öäüß €" def
+
+/image gstate def
+image "katze_800.jpg" readfile unpackimage setcanvas
+
+0 0 setpos
+image getgstate exch blt
+0x90000000 setcolor
+image dim fillrect
+
+0xffff00 setcolor
+
+getgstate cfont setfont
+50 50 setpos "Some font samples" show
+
+0x00ffffff setcolor
+
+getgstate cfont setfont
+50 100 setpos text show
+
+getgstate bar setfont
+50 130 setpos text show
+
+getgstate foo setfont
+50 180 setpos text show
diff --git a/files/main_01.gs b/files/main_01.gs
new file mode 100644
index 0000000..10f2ea6
--- /dev/null
+++ b/files/main_01.gs
@@ -0,0 +1,34 @@
+/font1 getconsolegstate getfont def
+/font2 "font2.psfu" readfile newfont def
+font2 font1 setparent
+getgstate font1 setfont
+
+-50 -20 setpos
+
+getgstate 20 20 155 100 setregion
+
+"XXX adasdas X\nXX€\n" show
+"--\x00\x00--\n" show
+"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." show
+
+/istate2 newgstate def
+istate2 "katze_800.jpg" readfile unpackimage setcanvas
+istate2 200 100 300 200 setregion
+
+getgstate 0 0 1000 1000 setregion
+
+50 50 setpos getgstate istate2 blt
+
+getgstate 10 10 200 200 setregion
+
+0xc0ff0000 setcolor
+
+60 1 80 {
+ /y exch def
+ 50 1 100 {
+ y setpos putpixel getpixel pop
+ } for
+} for
\ No newline at end of file
diff --git a/files/main_02.gs b/files/main_02.gs
new file mode 100644
index 0000000..714ae2e
--- /dev/null
+++ b/files/main_02.gs
@@ -0,0 +1,11 @@
+/foo_2 {
+ true { 30 return 40 } if
+} def
+
+/foo_3 {
+ 3 { 7 debug } repeat
+} def
+
+foo_2
+
+foo_3
diff --git a/files/main_03.gs b/files/main_03.gs
new file mode 100644
index 0000000..dd93a84
--- /dev/null
+++ b/files/main_03.gs
@@ -0,0 +1,62 @@
+/image "katze_800.jpg" readfile unpackimage def
+/istate newgstate def
+getgstate getcanvas setcanvas istate setgstate image setcanvas setgstate
+
+10 10 200 200 setregion
+getgstate istate blt
+0x00ff0000 setcolor
+
+/rlineto {
+ getpos
+ 4 1 roll add
+ 3 1 roll add exch
+ lineto
+} def
+
+100 100 setpos 100 0 rlineto
+
+100 100 setpos 100 30 rlineto
+100 100 setpos 100 50 rlineto
+100 100 setpos 100 80 rlineto
+
+100 100 setpos 100 100 rlineto
+
+100 100 setpos 80 100 rlineto
+100 100 setpos 50 100 rlineto
+100 100 setpos 30 100 rlineto
+
+100 100 setpos 0 100 rlineto
+
+100 100 setpos -30 100 rlineto
+100 100 setpos -50 100 rlineto
+100 100 setpos -80 100 rlineto
+
+100 100 setpos -100 100 rlineto
+
+100 100 setpos -100 80 rlineto
+100 100 setpos -100 50 rlineto
+100 100 setpos -100 30 rlineto
+
+100 100 setpos -100 0 rlineto
+
+100 100 setpos -100 -30 rlineto
+100 100 setpos -100 -50 rlineto
+100 100 setpos -100 -80 rlineto
+
+100 100 setpos -100 -100 rlineto
+
+100 100 setpos -80 -100 rlineto
+100 100 setpos -50 -100 rlineto
+100 100 setpos -30 -100 rlineto
+
+100 100 setpos 0 -100 rlineto
+
+100 100 setpos 30 -100 rlineto
+100 100 setpos 50 -100 rlineto
+100 100 setpos 80 -100 rlineto
+
+100 100 setpos 100 -100 rlineto
+
+100 100 setpos 100 -80 rlineto
+100 100 setpos 100 -50 rlineto
+100 100 setpos 100 -30 rlineto
diff --git a/files/main_04.gs b/files/main_04.gs
new file mode 100644
index 0000000..68629ff
--- /dev/null
+++ b/files/main_04.gs
@@ -0,0 +1,27 @@
+/cfont getconsolegstate getfont def
+/foo "foo.fnt" readfile newfont def
+/bar "bar.fnt" readfile newfont def
+
+/text "ABC 12345 xyz # * % & § öäüß €" def
+
+/image newgstate def
+image "katze_800.jpg" readfile unpackimage setcanvas
+
+0 0 setpos image getgstate exch blt
+0x90000000 setcolor
+image dim fillrect
+
+0x00ffffff setcolor
+
+20 20 setpos
+getgstate cfont setfont
+text show
+
+20 60 setpos
+getgstate bar setfont
+text show
+
+20 100 setpos
+getgstate foo setfont
+text show
+
diff --git a/gfxboot b/gfxboot
new file mode 120000
index 0000000..945c9b4
--- /dev/null
+++ b/gfxboot
@@ -0,0 +1 @@
+.
\ No newline at end of file
diff --git a/gfxboot-compile.c b/gfxboot-compile.c
new file mode 100644
index 0000000..f5cb898
--- /dev/null
+++ b/gfxboot-compile.c
@@ -0,0 +1,1919 @@
+#define _GNU_SOURCE
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// initial vocabulary (note: "{" & "}" are special)
+#define WITH_PRIM_NAMES 1
+#define WITH_TYPE_NAMES 1
+#include "vocabulary.h"
+
+#define COMMENT_CHAR '#'
+
+#define MAX_INCLUDE 16
+
+typedef struct {
+ unsigned size;
+ unsigned char *data;
+ unsigned real_size;
+ unsigned char *ptr;
+ char *name;
+ int line;
+} file_data_t;
+
+struct option options[] = {
+ { "create", 1, NULL, 'c' },
+ { "show", 0, NULL, 's' },
+ { "log", 1, NULL, 'l' },
+ { "opt", 0, NULL, 'O' },
+ { "lib", 1, NULL, 'L' },
+ { "help", 0, NULL, 'h' },
+ { }
+};
+
+typedef struct {
+ char *name;
+ type_t type;
+ int line;
+ int del, ref, ref_idx, ref_ind, def, def_idx, def_ind, ref0, ref0_idx;
+ struct {
+ unsigned char *p;
+ unsigned u;
+ unsigned p_len;
+ } value;
+} dict_t;
+
+typedef struct {
+ char *name;
+ type_t type;
+ unsigned ofs;
+ unsigned size;
+ unsigned xref_to;
+ unsigned duplicate:1;
+ int line, incl_level;
+ struct {
+ unsigned char *p;
+ uint64_t u;
+ unsigned p_len;
+ } value;
+ unsigned char *enc;
+} code_t;
+
+void help(void);
+file_data_t read_file(char *name);
+void fix_pal(unsigned char *pal, unsigned shade1, unsigned shade2, unsigned char *rgb);
+int write_data(char *name);
+void encode_number(unsigned char *enc, uint64_t val, unsigned len);
+uint64_t decode_number(unsigned char *data, unsigned len);
+void add_data(file_data_t *d, void *buffer, unsigned size);
+code_t *new_code(void);
+dict_t *new_dict(void);
+int show_info(char *name);
+int get_hex(char *s, unsigned len, unsigned *val);
+char *utf8_encode(unsigned uc);
+int utf8_decode(char **s);
+char *utf8_quote(unsigned uc);
+char *next_word(char **ptr, int *len);
+void parse_comment(char *comment, file_data_t *incl);
+int find_in_dict(char *name);
+int translate(int pass);
+int parse_config(char *name, char *log_file);
+void optimize_dict(FILE *lf);
+unsigned skip_code(unsigned pos);
+unsigned next_code(unsigned pos);
+int optimize_code(FILE *lf);
+int optimize_code1(FILE *lf);
+int optimize_code2(FILE *lf);
+int optimize_code3(FILE *lf);
+int optimize_code4(FILE *lf);
+int optimize_code5(FILE *lf);
+int optimize_code6(FILE *lf);
+void log_code(FILE *lf, int style);
+int decompile(unsigned char *data, unsigned size);
+
+int config_ok = 0;
+
+file_data_t pscode = {};
+file_data_t dict_file = {};
+
+dict_t *dict = NULL;
+unsigned dict_size = 0;
+unsigned dict_max_size = 0;
+
+unsigned prim_words = sizeof prim_names / sizeof *prim_names;
+
+code_t *code = NULL;
+unsigned code_size = 0;
+unsigned code_max_size = 0;
+
+// current config line
+int line = 1;
+
+struct {
+ unsigned verbose;
+ unsigned optimize;
+ unsigned show:1;
+ char *file;
+ char *log_file;
+ char *lib_path[2];
+} opt = { lib_path: { NULL, "/usr/share/gfxboot" } };
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ opterr = 0;
+
+ while((i = getopt_long(argc, argv, "c:sfhL:l:O:v", options, NULL)) != -1) {
+ switch(i) {
+ case 'c':
+ opt.file = optarg;
+ break;
+
+ case 's':
+ opt.show = 1;
+ break;
+
+ case 'l':
+ opt.log_file = optarg;
+ break;
+
+ case 'L':
+ opt.lib_path[0] = optarg;
+ break;
+
+ case 'O':
+ opt.optimize = strtoul(optarg, NULL, 0);
+ break;
+
+ case 'v':
+ opt.verbose++;
+ break;
+
+ default:
+ help();
+ return 0;
+ }
+ }
+
+ argc -= optind; argv += optind;
+
+ if(opt.file && argc <= 1) {
+ if(parse_config(argc ? *argv : "-", opt.log_file)) return 1;
+ return write_data(opt.file);
+ }
+
+ if(opt.show && argc <= 1) {
+ return show_info(argc ? *argv : "-");
+ }
+
+ help();
+
+ return 1;
+}
+
+
+void help()
+{
+ fprintf(stderr, "%s",
+ "Usage: gfxboot-compile [OPTIONS] SOURCE\n"
+ "Compile/decompile gfxboot2 script to byte code.\n"
+ "Options:\n"
+ " -c, --create FILE Compile SOURCE to FILE.\n"
+ " -l, --log LOGFILE Write compile log to LOGFILE.\n"
+ " -s, --show Decompile SOURCE.\n"
+ " -L, --lib PATH Set include file search path to PATH.\n"
+ " -O, --opt LEVEL Optimization level (0 - 3).\n"
+ " -v, --verbose Create more verbose log.\n"
+ " -h, --help Show this help text.\n"
+ );
+}
+
+
+/*
+ * The returned buffer has an extra 0 appended to it for easier parsing...
+ */
+file_data_t read_file(char *name)
+{
+ file_data_t fd = { };
+ FILE *f;
+ unsigned u;
+ char *s;
+
+ if(!name) return fd;
+
+ if(strcmp(name, "-")) {
+ f = fopen(name, "r");
+ }
+ else {
+ f = stdin;
+ }
+
+ if(!f) {
+ for(u = 0; u < sizeof opt.lib_path / sizeof *opt.lib_path; u++) {
+ if(opt.lib_path[u]) {
+ asprintf(&s, "%s/%s", opt.lib_path[u], name);
+ f = fopen(s, "r");
+ if(f) {
+ fd.name = s;
+ break;
+ }
+ else {
+ free(s);
+ }
+ }
+ }
+ }
+ else {
+ fd.name = strdup(name);
+ }
+
+ if(!f) { perror(name); return fd; }
+
+ if(fseek(f, 0, SEEK_END)) {
+ perror(name);
+ exit(30);
+ }
+
+ fd.size = fd.real_size = (unsigned) ftell(f);
+
+ if(fseek(f, 0, SEEK_SET)) {
+ perror(name);
+ exit(30);
+ }
+
+ fd.ptr = fd.data = calloc(1, fd.size + 1);
+ if(!fd.data) {
+ fprintf(stderr, "malloc failed\n");
+ exit(30);
+ }
+
+ if(fread(fd.data, 1, fd.size, f) != fd.size) {
+ perror(name);
+ exit(30);
+ }
+
+ fclose(f);
+
+ return fd;
+}
+
+
+int write_data(char *name)
+{
+ FILE *f;
+ file_data_t fd = {};
+
+ f = strcmp(name, "-") ? fopen(name, "w") : stdout;
+
+ if(!f) {
+ perror(name);
+ return 1;
+ }
+
+ add_data(&fd, pscode.data, pscode.size);
+
+ if(fwrite(fd.data, fd.size, 1, f) != 1) {
+ perror(name);
+ return 1;
+ }
+
+ fclose(f);
+
+ return 0;
+}
+
+
+void encode_number(unsigned char *enc, uint64_t val, unsigned len)
+{
+ while(len--) {
+ *enc++ = val;
+ val >>= 8;
+ }
+}
+
+
+uint64_t decode_number(unsigned char *data, unsigned len)
+{
+ uint64_t val = 0;
+
+ data += len;
+
+ while(len--) {
+ val <<= 8;
+ val += *--data;
+ }
+
+ return val;
+}
+
+
+void add_data(file_data_t *d, void *buffer, unsigned size)
+{
+ ssize_t ofs = 0;
+
+ if(!size || !d || !buffer) return;
+
+ if(d->ptr && d->data) ofs = d->ptr - d->data;
+
+ if(d->size + size > d->real_size) {
+ d->real_size = d->size + size + 0x1000;
+ d->data = realloc(d->data, d->real_size);
+ if(!d->data) d->real_size = 0;
+ }
+
+ if(d->size + size <= d->real_size) {
+ memcpy(d->data + d->size, buffer, size);
+ d->size += size;
+ }
+ else {
+ fprintf(stderr, "Oops, out of memory? Aborted.\n");
+ exit(10);
+ }
+
+ if(d->ptr && d->data) d->ptr = d->data + ofs;
+}
+
+
+code_t *new_code()
+{
+ if(code_size >= code_max_size) {
+ code_max_size += 10;
+ code = realloc(code, code_max_size * sizeof * code);
+ memset(code + code_size, 0, (code_max_size - code_size) * sizeof * code);
+ }
+
+ return code + code_size++;
+}
+
+
+
+dict_t *new_dict()
+{
+ if(dict_size >= dict_max_size) {
+ dict_max_size += 10;
+ dict = realloc(dict, dict_max_size * sizeof *dict);
+ memset(dict + dict_size, 0, (dict_max_size - dict_size) * sizeof *dict);
+ }
+
+ return dict + dict_size++;
+}
+
+
+uint32_t read_uint32_le(file_data_t *fd, unsigned ofs)
+{
+ uint32_t word = 0;
+ unsigned u;
+ for (u = 0; u < 4; u++) {
+ word += (unsigned) fd->data[ofs + u] << (u * 8);
+ }
+ return word;
+}
+
+
+int show_info(char *name)
+{
+ file_data_t fd;
+ int err = 1;
+
+ fd = read_file(name);
+
+ err = decompile(fd.data, fd.size);
+ if(!err) {
+ log_code(stdout, 0);
+ }
+
+ return err;
+}
+
+
+/*
+ * Convert hex number of excatly len bytes.
+ */
+int get_hex(char *s, unsigned len, unsigned *val)
+{
+ unsigned u;
+ char s2[len + 1];
+
+ if(!s || !len) return 0;
+ strncpy(s2, s, len);
+ s2[len] = 0;
+
+ u = strtoul(s2, &s, 16);
+ if(!*s) {
+ if(val) *val = u;
+ return 1;
+ }
+
+ return 0;
+}
+
+
+char *utf8_encode(unsigned uc)
+{
+ static char buf[7];
+ char *s = buf;
+
+ uc &= 0x7fffffff;
+
+ if(uc < 0x80) { // 7 bits
+ *s++ = uc;
+ }
+ else {
+ if(uc < (1 << 11)) { // 11 (5 + 6) bits
+ *s++ = 0xc0 + (uc >> 6);
+ goto utf8_encode_2;
+ }
+ else if(uc < (1 << 16)) { // 16 (4 + 6 + 6) bits
+ *s++ = 0xe0 + (uc >> 12);
+ goto utf8_encode_3;
+ }
+ else if(uc < (1 << 21)) { // 21 (3 + 6 + 6 + 6) bits
+ *s++ = 0xf0 + (uc >> 18);
+ goto utf8_encode_4;
+ }
+ else if(uc < (1 << 26)) { // 26 (2 + 6 + 6 + 6 + 6) bits
+ *s++ = 0xf8 + (uc >> 24);
+ goto utf8_encode_5;
+ }
+ else { // 31 (1 + 6 + 6 + 6 + 6 + 6) bits
+ *s++ = 0xfc + (uc >> 30);
+ }
+
+ *s++ = 0x80 + ((uc >> 24) & ((1 << 6) - 1));
+
+ utf8_encode_5:
+ *s++ = 0x80 + ((uc >> 18) & ((1 << 6) - 1));
+
+ utf8_encode_4:
+ *s++ = 0x80 + ((uc >> 12) & ((1 << 6) - 1));
+
+ utf8_encode_3:
+ *s++ = 0x80 + ((uc >> 6) & ((1 << 6) - 1));
+
+ utf8_encode_2:
+ *s++ = 0x80 + (uc & ((1 << 6) - 1));
+ }
+
+ *s = 0;
+
+ return buf;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Decode utf8 sequence.
+//
+// a) if s points to a valid utf8 sequence:
+// - returns unicode char (a non-negative number)
+// - s is updated to point past utf8 char
+//
+// b) if s does not point to a valid utf8 sequence
+// - returns negated first byte
+// - s is incremented by 1
+//
+int utf8_decode(char **s)
+{
+ unsigned char *p;
+ int c;
+ unsigned u, l;
+
+ if(!s || !*s) return 0;
+
+ p = (uint8_t *) *s;
+
+ u = *p++;
+
+ if(u >= 0x80) {
+ if(u < 0xc0 || u >= 0xfe) {
+ *s = (char *) p;
+ return -(int) u;
+ }
+ l = 1;
+ if(u < 0xe0) {
+ c = u & 0x1f;
+ }
+ else if(u < 0xf0) {
+ c = u & 0x0f;
+ l = 2;
+ }
+ else if(u < 0xf8) {
+ c = u & 0x07;
+ l = 3;
+ }
+ else if(u < 0xfc) {
+ c = u & 0x03;
+ l = 4;
+ }
+ else if(u < 0xfe) {
+ c = u & 0x01;
+ l = 5;
+ }
+ while(l--) {
+ u = *p++;
+ if(u < 0x80 || u >= 0xc0) {
+ u = (uint8_t) **s;
+ (*s)++;
+ return -(int) u;
+ }
+ c = (c << 6) + (int) (u & 0x3f);
+ }
+ }
+ else {
+ c = (int) u;
+ }
+
+ *s = (char *) p;
+
+ return c;
+}
+
+
+char *utf8_quote(unsigned uc)
+{
+ static char buf[16];
+
+ if(uc == '\t') {
+ strcpy(buf, "\\t");
+ }
+ else if(uc == '\n') {
+ strcpy(buf, "\\n");
+ }
+ else if(uc == '\\') {
+ strcpy(buf, "\\\\");
+ }
+ else if(uc == '\'') {
+ strcpy(buf, "\\'");
+ }
+ else if(uc < ' ' || uc == 0x7f) {
+ sprintf(buf, "\\x%02x", uc);
+ }
+ else if(uc >= ' ' && uc < 0x7f) {
+ buf[0] = uc;
+ buf[1] = 0;
+ }
+ else if(uc < 0xffff) {
+ sprintf(buf, "\\u%04x", uc);
+ }
+ else {
+ sprintf(buf, "\\U%08x", uc);
+ }
+
+ return buf;
+}
+
+
+char *next_word(char **ptr, int *len)
+{
+ char *s, *start, *utf8;
+ int is_str, is_comment;
+ static char word[0x1000];
+ unsigned u, n = 0;
+ char qc = 0;
+
+ s = *ptr;
+
+ *word = 0;
+
+ if(len) *len = (int) n;
+
+ while(*s && isspace(*s)) if(*s++ == '\n') line++;
+
+ if(!*s) {
+ *ptr = s;
+ return word;
+ }
+
+ start = s;
+
+ qc = *start;
+ is_str = qc == '"' || qc == '\'' ? 1 : 0;
+ is_comment = qc == COMMENT_CHAR ? 1 : 0;
+
+ if(is_comment) {
+ while(*s && *s != '\n') s++;
+ }
+ else if(is_str) {
+ *word = *s++;
+ for(n = 1; n < sizeof word - 1; n++) {
+ if(!*s) break;
+ if(*s == qc) { s++; break; }
+ if(*s == '\\') {
+ s++;
+ switch(*s) {
+ case 0:
+ word[n++] = '\\';
+ break;
+
+ case 'n':
+ word[n] = '\n';
+ break;
+
+ case 't':
+ word[n] = '\t';
+ break;
+
+ case '0':
+ if(
+ s[0] >= '0' && s[0] <= '7' &&
+ s[1] >= '0' && s[1] <= '7' &&
+ s[2] >= '0' && s[2] <= '7'
+ ) {
+ word[n] = ((s[0] - '0') << 6) + ((s[1] - '0') << 3) + (s[2] - '0');
+ s += 2;
+ }
+ else {
+ word[n] = *s;
+ }
+ break;
+
+ case 'x':
+ if(get_hex(s + 1, 2, &u)) {
+ s += 2;
+ word[n] = u;
+ }
+ else {
+ word[n++] = '\\';
+ word[n] = *s;
+ }
+ break;
+
+ case 'u':
+ if(get_hex(s + 1, 4, &u)) {
+ s += 4;
+ utf8 = utf8_encode(u);
+ while(*utf8) word[n++] = *utf8++;
+ n--;
+ }
+ else {
+ word[n++] = '\\';
+ word[n] = *s;
+ }
+ break;
+
+ case 'U':
+ if(get_hex(s + 1, 8, &u)) {
+ s += 8;
+ utf8 = utf8_encode(u);
+ while(*utf8) word[n++] = *utf8++;
+ n--;
+ }
+ else {
+ word[n++] = '\\';
+ word[n] = *s;
+ }
+ break;
+
+ default:
+ word[n] = *s;
+ }
+ s++;
+ }
+ else {
+ word[n] = *s++;
+ }
+ }
+ word[n] = 0;
+ }
+ else {
+ while(*s && !isspace(*s)) s++;
+ }
+
+ if(!is_str) {
+ n = (unsigned) (s - start);
+ if(n >= sizeof word) n = sizeof word - 1;
+ strncpy(word, start, n);
+ word[n] = 0;
+ }
+
+ *ptr = s;
+
+ if(len) *len = (int) n;
+
+ return word;
+}
+
+
+void parse_comment(char *comment, file_data_t *incl)
+{
+ char t[5][100];
+ int n;
+
+ n = sscanf(comment, " %99s %99s %99s %99s %99s", t[0], t[1], t[2], t[3], t[4]);
+
+ if(!n) return;
+
+ if(n == 2 && !strcmp(t[0], "include")) {
+ *incl = read_file(t[1]);
+ if(!incl->data) exit(18);
+ add_data(incl, "", 1);
+ if(opt.verbose) fprintf(stderr, "including \"%s\"\n", incl->name);
+ return;
+ }
+}
+
+
+int find_in_dict(char *name)
+{
+ unsigned u;
+
+ for(u = 0; u < dict_size; u++) {
+ if(dict[u].name && !strcmp(name, dict[u].name)) return (int) u;
+ }
+
+ return -1;
+}
+
+
+unsigned usize(uint64_t val)
+{
+ unsigned len = 0;
+
+ while(val) {
+ val >>= 8;
+ len++;
+ }
+
+ return len;
+}
+
+
+unsigned isize(int64_t val)
+{
+ unsigned len = 1;
+
+ if(val == 0) return 0;
+
+ while((val >> 7) && (val >> 7) != -1L) {
+ val >>= 8;
+ len++;
+ };
+
+ return len;
+}
+
+
+int translate(int pass)
+{
+ int is_signed;
+ code_t *c;
+ unsigned u, ofs = 0, len, lenx;
+ int changed = 0;
+
+ if(pass == 0) {
+ changed = 1;
+ for(u = 0; u < code_size; u++) {
+ c = code + u;
+
+ c->ofs = ofs;
+
+ is_signed = 0;
+ switch(c->type) {
+ case t_skip:
+ c->size = 0;
+ break;
+
+ case t_int:
+ is_signed = 1;
+
+ case t_nil:
+ case t_bool:
+ case t_prim:
+ case t_comment:
+ case t_string:
+ case t_word:
+ case t_ref:
+ case t_get:
+ case t_set:
+ if(c->value.p) {
+ if(!TYPE_EXPECTS_DATA(c->type)) {
+ fprintf(stderr, "Internal oops %d: type %d needs memory range\n", __LINE__, c->type);
+ exit(9);
+ }
+ lenx = c->value.p_len;
+ if(lenx < 12) {
+ c->size = lenx + 1;
+ c->enc = malloc(c->size);
+ memcpy(c->enc + 1, c->value.p, lenx);
+ c->enc[0] = c->type + (lenx << 4);
+ }
+ else {
+ // len is at least 1 since lenx is != 0
+ len = usize(lenx);
+ if(len > 4) {
+ fprintf(stderr, "Internal oops %d: type %d memory range is too large\n", __LINE__, c->type);
+ exit(11);
+ }
+ c->size = lenx + len + 1;
+ c->enc = malloc(c->size);
+ c->enc[0] = c->type + ((len + 11) << 4);
+ encode_number(c->enc + 1, lenx, len);
+ memcpy(c->enc + 1 + len, c->value.p, lenx);
+ }
+ }
+ else {
+ if(TYPE_EXPECTS_DATA(c->type)) {
+ fprintf(stderr, "Internal oops %d: type %d misses memory range\n", __LINE__, c->type);
+ exit(10);
+ }
+ len = is_signed ? isize((int64_t) c->value.u) : usize(c->value.u);
+ if(c->value.u < 8) {
+ c->size = 1;
+ c->enc = malloc(c->size);
+ c->enc[0] = c->type + (c->value.u << 4);
+ }
+ else {
+ // len is at least 1 since c->value.u is != 0
+ c->size = len + 1;
+ c->enc = malloc(c->size);
+ c->enc[0] = c->type + ((len - 1 + 8) << 4);
+ encode_number(c->enc + 1, c->value.u, len);
+ }
+ }
+ break;
+
+ case t_code:
+ c->size = 2;
+ // dummy value
+ // really encoded in else branch below during later passes (pass != 0)
+ break;
+
+ default:
+ fprintf(stderr, "Internal oops %d: type %d not allowed\n", __LINE__, c->type);
+ exit(8);
+ }
+
+ ofs += c->size;
+ }
+ }
+ else {
+ for(u = 0; u < code_size; u++) {
+ c = code + u;
+
+ if(c->ofs != ofs) changed = 1;
+ c->ofs = ofs;
+
+ if(c->xref_to) {
+ unsigned dist = c->ofs - code[c->xref_to].ofs;
+ unsigned xlen = usize(dist);
+ if(dist < 8) {
+ if(1 < c->size || c->type == t_xref) {
+ c->size = 1;
+ c->enc = malloc(c->size);
+ c->enc[0] = t_xref + (dist << 4);
+ c->type = t_xref;
+ }
+ }
+ else {
+ if(xlen + 1 < c->size || c->type == t_xref) {
+ c->enc[0] = t_xref + ((xlen - 1 + 8) << 4);
+ encode_number(c->enc + 1, dist, xlen);
+ c->size = xlen + 1;
+ c->type = t_xref;
+ }
+ }
+ }
+
+ if(c->type == t_code) {
+ lenx = c->value.u;
+ // we want to encode the length of the code blob, starting *after*
+ // the current instruction
+ if(lenx >= code_size || u >= code_size - 1 || code[lenx].ofs < c[1].ofs) {
+ fprintf(stderr, "Internal error %d\n", __LINE__);
+ exit(11);
+ }
+ lenx = code[lenx].ofs - c[1].ofs;
+ if(lenx < 12) {
+ c->size = 1;
+ c->enc = malloc(c->size);
+ c->enc[0] = c->type + (lenx << 4);
+ }
+ else {
+ len = usize(lenx);
+ if(c->size != len + 1) changed = 1;
+ c->size = len + 1;
+ if(c->enc) free(c->enc);
+ c->enc = malloc(c->size);
+ c->enc[0] = c->type + ((len + 11) << 4);
+ encode_number(c->enc + 1, lenx, len);
+ }
+ }
+
+ ofs += c->size;
+ }
+ }
+
+ return changed;
+}
+
+
+int parse_config(char *name, char *log_file)
+{
+ char *word;
+ file_data_t cfg[MAX_INCLUDE];
+ file_data_t incl;
+ int i, j;
+ unsigned u, word_len;
+ dict_t *d;
+ code_t *c, *c1;
+ char *s;
+ FILE *lf = NULL;
+ int incl_level = 0;
+
+ cfg[incl_level] = read_file(name);
+ add_data(&cfg[incl_level], "", 1);
+
+ if(!cfg[incl_level].ptr) {
+ fprintf(stderr, "error: no source file\n");
+
+ return 1;
+ }
+
+ if(log_file && *log_file) {
+ if(!strcmp(log_file, "-")) {
+ lf = fdopen(dup(fileno(stdout)), "a");
+ }
+ else {
+ lf = fopen(log_file, "w");
+ }
+ }
+
+ // setup initial vocabulary
+ for(u = 0; u < prim_words; u++) {
+ d = new_dict();
+ d->type = t_prim;
+ d->value.u = u;
+ d->name = (char *) prim_names[u];
+ }
+
+ c = new_code();
+ c->type = t_comment;
+ c->value.p_len = 7;
+ c->value.p = calloc(1, 7);
+ encode_number(c->value.p, GFXBOOT_MAGIC, 7);
+ c->name = strdup("# gfxboot magic");
+
+ while(*cfg[incl_level].ptr || incl_level) {
+ if(!*cfg[incl_level].ptr) {
+ incl_level--;
+ line = cfg[incl_level].line;
+ }
+ word = next_word((char **) &cfg[incl_level].ptr, &word_len); // unsigned char **
+ if(!word || !word_len) continue;
+
+ if(word[0] == COMMENT_CHAR) {
+ if(word[1] == COMMENT_CHAR) {
+ incl.ptr = NULL;
+ parse_comment(word + 2, &incl);
+ if(incl.ptr) {
+ if(incl_level == MAX_INCLUDE - 1) {
+ fprintf(stderr, "error: include level exceeded\n");
+ return 1;
+ }
+ else {
+ cfg[incl_level].line = line;
+ cfg[++incl_level] = incl;
+ line = 1;
+ }
+ }
+ }
+ continue;
+ }
+
+ if(opt.verbose >= 2) printf(">%s< [%d] (line %d)\n", word, word_len, line);
+
+ c = new_code();
+ c->line = line;
+ c->incl_level = incl_level;
+
+ if(*word == '"') {
+ c->type = t_string;
+ c->value.p = calloc(1, word_len);
+ c->value.p = memcpy(c->value.p, word + 1, word_len - 1);
+ c->value.p_len = word_len - 1;
+ }
+ else if(*word == '\'') {
+ char *s = word + 1;
+ int uc = utf8_decode(&s);
+ if(uc >= 0 && !*s) {
+ c->type = t_int;
+ c->value.u = (unsigned) uc;
+ asprintf(&c->name, "'%s'", utf8_quote((unsigned) uc));
+ }
+ else {
+ fprintf(stderr, "syntax error: invalid char constant in line %d\n", line);
+ return 1;
+ }
+ }
+ else if(*word == '/') {
+ c->name = strdup(word + 1);
+
+ c->type = t_ref;
+
+ if((i = find_in_dict(word + 1)) == -1) {
+ d = new_dict();
+ d->type = t_nil;
+ d->value.u = 1; // mark as defined
+ d->value.p = strdup(word + 1);
+ d->value.p_len = strlen(word + 1);
+ d->name = strdup(word + 1);
+ c->value.u = dict_size - 1;
+ }
+ else {
+ if(dict[i].type == t_nil && !dict[i].value.u) {
+ dict[i].value.u = 1; // mark as defined
+ }
+ c->value.u = (unsigned) i;
+ }
+ c->value.p = strdup(word + 1);
+ c->value.p_len = strlen(word + 1);
+ }
+ else if(*word == '.') {
+ c->name = strdup(word + 1);
+ c->type = t_get;
+ c->value.p = strdup(word + 1);
+ c->value.p_len = strlen(word + 1);
+ }
+ else if(*word == '=') {
+ c->name = strdup(word + 1);
+ c->type = t_set;
+ c->value.p = strdup(word + 1);
+ c->value.p_len = strlen(word + 1);
+ }
+ else if(!strcmp(word, prim_names[prim_idx_code_start])) {
+ c->type = t_code;
+ c->name = strdup(word);
+ }
+ else if(!strcmp(word, prim_names[prim_idx_code_end])) {
+ c->type = t_prim;
+ c->value.u = prim_idx_code_end;
+ c->name = strdup(word);
+ for(c1 = c; c1 >= code; c1--) {
+ if(c1->type == t_code && !c1->value.u) {
+ // point _after_ "}"
+ c1->value.u = (unsigned) (c - code) + 1;
+ break;
+ }
+ }
+ if(c1 < code) {
+ fprintf(stderr, "syntax error: no matching \"{\" for \"}\" in line %d\n", line);
+ return 1;
+ }
+ }
+ else {
+ c->name = strdup(word);
+
+ i = find_in_dict(word);
+
+ if(i == -1) {
+ uint64_t val = strtoull(word, &s, 0);
+ if(*s) {
+ if(!strcmp(s, "nil")) {
+ c->type = t_nil;
+ c->value.u = 0;
+ }
+ else if(!strcmp(s, "true")) {
+ c->type = t_bool;
+ c->value.u = 1;
+ }
+ else if(!strcmp(s, "false")) {
+ c->type = t_bool;
+ c->value.u = 0;
+ }
+ else {
+ d = new_dict();
+ d->type = t_nil;
+ d->name = strdup(word);
+ c->type = t_word;
+ c->value.u = dict_size - 1;
+ c->value.p = strdup(word);
+ c->value.p_len = strlen(word);
+ }
+ }
+ else {
+ c->type = t_int;
+ c->value.u = val;
+ }
+ }
+ else {
+ c->type = t_word;
+ c->value.u = (unsigned) i;
+ c->value.p = strdup(word);
+ c->value.p_len = strlen(word);
+ }
+ }
+ }
+
+ // check vocabulary
+ if(opt.verbose >= 2) {
+ for(i = j = 0; i < (int) dict_size; i++) {
+ if(
+ dict[i].type == t_nil && !dict[i].value.u
+ ) {
+ if(!j) fprintf(stderr, "Undefined words:");
+ else fprintf(stderr, ",");
+ fprintf(stderr, " %s", dict[i].name);
+ j = 1;
+ }
+ }
+ if(j) {
+ fprintf(stderr, "\n");
+ }
+ }
+
+ if(opt.optimize) {
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# searching for unused code:\n");
+ for(i = 0; i < 64; i++) {
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# pass %d\n", i + 1);
+ if(!optimize_code(lf)) break;
+ }
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# %d optimization passes\n", i + 1);
+ if(i) {
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# searching for unused dictionary entries:\n");
+ optimize_dict(lf);
+ }
+ }
+
+ // translate to byte code
+ for(i = 0; i < 100; i++) {
+ if(!translate(i)) break;
+ }
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# %d encoding passes\n", i + 1);
+ if(i == 100) {
+ fprintf(stderr, "error: code translation does not converge\n");
+ return 1;
+ }
+
+ // store it
+ for(i = 0; i < (int) code_size; i++) {
+ if((!code[i].enc || !code[i].size) && code[i].type != t_skip) {
+ fprintf(stderr, "error: internal oops %d\n", __LINE__);
+ return 1;
+ }
+ add_data(&pscode, code[i].enc, code[i].size);
+ }
+
+ if(lf) fputc('\n', lf);
+ log_code(lf, 1);
+
+ if(lf) fclose(lf);
+
+ return 0;
+}
+
+
+/*
+ * Remove deleted dictionary entries.
+ */
+void optimize_dict(FILE *lf)
+{
+ unsigned u, old_ofs, new_ofs;
+
+ for(old_ofs = new_ofs = 0; old_ofs < dict_size; old_ofs++) {
+ if(dict[old_ofs].del) continue;
+ if(old_ofs != new_ofs) {
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# rename %d -> %d\n", old_ofs, new_ofs);
+ dict[new_ofs] = dict[old_ofs];
+ for(u = 0; u < code_size; u++) {
+ if(
+ (
+ code[u].type == t_word ||
+ code[u].type == t_ref
+ ) &&
+ code[u].value.u == old_ofs
+ ) {
+ code[u].value.u = new_ofs;
+ }
+ }
+ }
+ new_ofs++;
+ }
+ if(opt.verbose >= 2 && lf && new_ofs != old_ofs) {
+ fprintf(lf, "# new dictionary size %d (%d - %d)\n", new_ofs, old_ofs, old_ofs - new_ofs);
+ }
+
+ dict_size = new_ofs;
+}
+
+
+/*
+ * Skip deleted code.
+ */
+unsigned skip_code(unsigned pos)
+{
+ while(pos < code_size && code[pos].type == t_skip) pos++;
+
+ return pos;
+}
+
+
+/*
+ * Return next instruction.
+ */
+unsigned next_code(unsigned pos)
+{
+ if((pos + 1) >= code_size) return pos;
+
+ return skip_code(++pos);
+}
+
+
+int optimize_code(FILE *lf)
+{
+ unsigned u;
+ int changed = 0, ind = 0;
+ code_t *c;
+
+ for(u = 0; u < dict_size; u++) {
+ dict[u].def = dict[u].def_idx =
+ dict[u].ref = dict[u].ref_idx =
+ dict[u].ref0 = dict[u].ref0_idx = 0;
+ }
+
+ for(u = 0; u < code_size; u++) {
+ c = code + u;
+
+ switch(c->type) {
+ case t_code:
+ ind++;
+ break;
+
+ case t_prim:
+ if(c->value.u == prim_idx_code_end) {
+ if(!ind) {
+ fprintf(stderr, "Warning: nesting error at line %d\n", c->line);
+ }
+ ind--;
+ }
+ break;
+
+ case t_word:
+ if(c->value.u < dict_size) {
+ dict[c->value.u].ref++;
+ dict[c->value.u].ref_idx = (int) u;
+ dict[c->value.u].ref_ind = ind;
+ if(ind == 0 && !dict[c->value.u].ref0) {
+ dict[c->value.u].ref0 = 1;
+ dict[c->value.u].ref0_idx = (int) u;
+ }
+ }
+ break;
+
+ case t_ref:
+ if(c->value.u < dict_size) {
+ dict[c->value.u].def++;
+ dict[c->value.u].def_idx = (int) u;
+ dict[c->value.u].def_ind = ind;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if(opt.optimize >= 2) changed |= optimize_code1(lf);
+ if(opt.optimize >= 3) changed |= optimize_code2(lf);
+ if(opt.optimize >= 3) changed |= optimize_code3(lf);
+ if(opt.optimize >= 3) changed |= optimize_code5(lf);
+ if(opt.optimize >= 2) changed |= optimize_code4(lf);
+ // must always be the last step
+ if(opt.optimize >= 1) changed |= optimize_code6(lf);
+
+ return changed;
+}
+
+
+/*
+ * Find references to primitive words.
+ */
+int optimize_code1(FILE *lf)
+{
+ unsigned i, j;
+ int changed = 0;
+ code_t *c;
+
+ for(i = 0; i < dict_size; i++) {
+ if(
+ i < prim_words &&
+ !dict[i].del &&
+ dict[i].def == 0 &&
+ dict[i].ref &&
+ dict[i].type == t_prim
+ ) {
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# replacing %s\n", dict[i].name);
+ for(j = 0; j < code_size; j++) {
+ c = code + j;
+ if(c->type == t_word && c->value.u == i) {
+ c->type = dict[i].type;
+ c->value.u = dict[i].value.u;
+ c->value.p = NULL;
+ c->value.p_len = 0;
+ }
+ }
+
+ changed = 1;
+ }
+ }
+
+ return changed;
+}
+
+
+/*
+ * Remove things like
+ *
+ * /foo 123 def
+ * /foo "abc" def
+ * /foo /bar def
+ *
+ * if foo is unused.
+ */
+int optimize_code2(FILE *lf)
+{
+ unsigned i, j;
+ int changed = 0;
+ code_t *c0, *c1, *c2;
+
+ for(i = 0; i < dict_size; i++) {
+ if(
+ i >= prim_words &&
+ !dict[i].del &&
+ !dict[i].ref &&
+ dict[i].def == 1 &&
+ dict[i].type == t_nil
+ ) {
+ c0 = code + (j = (unsigned) dict[i].def_idx);
+ c1 = code + (j = next_code(j));
+ c2 = code + (j = next_code(j));
+
+ if(
+ c0->type == t_ref &&
+ c0->value.u == i &&
+ (
+ c1->type == t_nil ||
+ c1->type == t_int ||
+ c1->type == t_bool ||
+ c1->type == t_string ||
+ c1->type == t_ref
+ ) &&
+ c2->type == t_prim &&
+ !strcmp(dict[c2->value.u].name, "def")
+ ) {
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# defined but unused: %s (index %d)\n", dict[i].name, i);
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# deleting code: %d - %d\n", dict[i].def_idx, j);
+ c0->type = c1->type = c2->type = t_skip;
+ dict[i].del = 1;
+
+ changed = 1;
+ }
+ }
+ }
+
+ return changed;
+}
+
+
+/*
+ * Remove things like
+ *
+ * /foo { ... } def
+ *
+ * if foo is unused.
+ */
+int optimize_code3(FILE *lf)
+{
+ unsigned i, j, k;
+ int changed = 0;
+ code_t *c0, *c1;
+
+ for(i = 0; i < dict_size; i++) {
+ if(
+ i >= prim_words &&
+ !dict[i].del &&
+ !dict[i].ref &&
+ dict[i].def == 1 &&
+ dict[i].type == t_nil
+ ) {
+ c0 = code + (j = (unsigned) dict[i].def_idx);
+ c1 = code + next_code(j);
+
+ if(c1 == c0) continue;
+
+ if(
+ c0->type == t_ref &&
+ c0->value.u == i &&
+ c1->type == t_code &&
+ code[j = skip_code(c1->value.u)].type == t_prim &&
+ !strcmp(dict[code[j].value.u].name, "def") &&
+ j > (unsigned) dict[i].def_idx
+ ) {
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# defined but unused: %s (index %d)\n", dict[i].name, i);
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# deleting code: %d - %d\n", dict[i].def_idx, j);
+ for(k = (unsigned) dict[i].def_idx; k <= j; k++) code[k].type = t_skip;
+ dict[i].del = 1;
+
+ changed = 1;
+ }
+ }
+ }
+
+ return changed;
+}
+
+
+
+/*
+ * Find unused dictionary entries.
+ */
+int optimize_code4(FILE *lf)
+{
+ unsigned i;
+ int changed = 0;
+
+ for(i = 0; i < dict_size; i++) {
+ if(
+ i >= prim_words &&
+ !dict[i].del &&
+ !dict[i].ref &&
+ !dict[i].def
+ ) {
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# unused: %s (index %d)\n", dict[i].name, i);
+
+ dict[i].del = 1;
+
+ changed = 1;
+ }
+ }
+
+ return changed;
+}
+
+
+/*
+ * Replace references to constant global vars.
+ */
+int optimize_code5(FILE *lf)
+{
+ unsigned i, j, k;
+ int changed = 0;
+ code_t *c, *c0, *c1, *c2;
+ char *s;
+
+ for(i = 0; i < dict_size; i++) {
+ if(
+ i >= prim_words &&
+ !dict[i].del &&
+ dict[i].def == 1 &&
+ dict[i].def_ind == 0 &&
+ (
+ !dict[i].ref0 ||
+ dict[i].ref0_idx > dict[i].def_idx
+ ) &&
+ dict[i].type == t_nil
+ ) {
+ c0 = code + (j = (unsigned) dict[i].def_idx);
+ c1 = code + (j = next_code(j));
+ c2 = code + (j = next_code(j));
+
+ if(
+ c0->type == t_ref &&
+ c0->value.u == i &&
+ (
+ c1->type == t_nil ||
+ c1->type == t_int ||
+ c1->type == t_bool
+ ) &&
+ c2->type == t_prim &&
+ !strcmp(dict[c2->value.u].name, "def")
+ ) {
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# global constant: %s (index %d)\n", dict[i].name, i);
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# replacing %s with %s\n", dict[i].name, c1->name);
+ for(k = 0; k < code_size; k++) {
+ c = code + k;
+ if(c->type == t_word && c->value.u == i) {
+ c->type = c1->type;
+ c->value = c1->value;
+ if(c->type == t_int) {
+ asprintf(&s, "%s # %s", c1->name, c->name);
+ free(c->name);
+ c->name = s;
+ }
+ else if(c->type == t_bool) {
+ asprintf(&s, "%s # %s", c->value.u ? "true" : "false", c->name);
+ free(c->name);
+ c->name = s;
+ }
+ else if(c->type == t_nil) {
+ asprintf(&s, "nil # %s", c->name);
+ free(c->name);
+ c->name = s;
+ }
+ }
+ }
+
+ dict[i].del = 1;
+
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# deleting code: %d - %d\n", dict[i].def_idx, j);
+ c0->type = c1->type = c2->type = t_skip;
+
+ changed = 1;
+ }
+ }
+ }
+
+ return changed;
+}
+
+
+/*
+ */
+int optimize_code6(FILE *lf)
+{
+ unsigned i, j;
+ int changed = 0;
+ code_t *c, *c_ref;
+
+ if(opt.verbose >= 2 && lf) fprintf(lf, "# looking for cross references\n");
+
+ for(i = 0; i < code_size; i++) {
+ c_ref = code + i;
+ if(c_ref->xref_to) continue;
+ if(
+ c_ref->type == t_string ||
+ c_ref->type == t_word ||
+ c_ref->type == t_ref ||
+ c_ref->type == t_get ||
+ c_ref->type == t_set
+ ) {
+ for(j = i + 1; j < code_size; j++) {
+ c = code + j;
+ if(
+ c->type == c_ref->type &&
+ c_ref->value.p && c->value.p &&
+ c_ref->value.p_len == c->value.p_len &&
+ !memcmp(c->value.p, c_ref->value.p, c->value.p_len)
+ ) {
+ c->xref_to = i;
+ if(opt.verbose >= 2 && lf) fprintf(lf, "xref: %d = %d name = >%s<\n", j, i, c->name);
+ changed = 1;
+ }
+ }
+ }
+ }
+
+ return changed;
+}
+
+
+void log_code(FILE *lf, int style)
+{
+ int i, j, l, line = 0, incl_level = 0;
+ int ind = 0, diff = 0;
+ char *s;
+
+ if(!lf) return;
+
+ for(i = j = 0; i < (int) code_size; i++) {
+ if(code[i].type == t_skip) j++;
+ }
+
+ fprintf(lf, "# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
+ fprintf(lf, "# code: %d entries (%d - %d)\n", (int) code_size - j, code_size, j);
+ if(style || opt.verbose) {
+ fprintf(lf, "# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
+ fprintf(lf, "# line i %soffset type hex word\n", opt.verbose ? "index " : "");
+ }
+ fprintf(lf, "# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
+ for(i = 0; i < (int) code_size; i++) {
+ if(code[i].duplicate) {
+ diff++;
+ }
+ if(code[i].type == t_skip && !opt.verbose) continue;
+ if(style || opt.verbose) {
+ if((line != code[i].line || incl_level != code[i].incl_level) && code[i].line) {
+ line = code[i].line;
+ incl_level = code[i].incl_level;
+ fprintf(lf, "%6d", line);
+ if(incl_level) {
+ fprintf(lf, " %d ", incl_level);
+ }
+ else {
+ fprintf(lf, " ");
+ }
+ }
+ else {
+ fprintf(lf, "%9s", "");
+ }
+ if(opt.verbose) {
+ if(code[i].duplicate) {
+ fprintf(lf, "%5s ", "");
+ }
+ else {
+ fprintf(lf, "%5d ", i - diff);
+ }
+ }
+ if(code[i].size && !code[i].duplicate) {
+ fprintf(lf, "0x%05x ", code[i].ofs);
+ }
+ else {
+ fprintf(lf, "%*s", 9, "");
+ }
+ if(code[i].type < sizeof type_name / sizeof *type_name) {
+ fprintf(lf, "%-6s", type_name[code[i].type]);
+ }
+ else {
+ fprintf(lf, "<%4u>", code[i].type);
+ }
+ l = code[i].enc ? (int) code[i].size : 0;
+ if(l > 8) l = 8;
+ for(j = 0; j < l; j++) {
+ fprintf(lf, " %02x", code[i].enc[j]);
+ }
+ }
+ else {
+ l = 8;
+ }
+
+ type_t type = code[i].type;
+ if(code[i].xref_to) type = code[code[i].xref_to].type;
+
+ if(
+ (
+ (type == t_word || type == t_prim) &&
+ (
+ !strcmp(code[i].name, prim_names[prim_idx_array_end]) ||
+ !strcmp(code[i].name, prim_names[prim_idx_hash_end]) ||
+ !strcmp(code[i].name, prim_names[prim_idx_code_end])
+ )
+ ) &&
+ ind > 0
+ ) ind -= 2;
+ fprintf(lf, "%*s", 3 * (8 - l) + ind + (style || opt.verbose ? 2 : 0), "");
+
+ if(type == t_skip) fprintf(lf, "# ");
+
+ if(
+ type == t_code ||
+ (
+ (type == t_word || type == t_prim) &&
+ (
+ !strcmp(code[i].name, prim_names[prim_idx_array_start]) ||
+ !strcmp(code[i].name, prim_names[prim_idx_hash_start]) ||
+ !strcmp(code[i].name, prim_names[prim_idx_code_start])
+ )
+ )
+ ) ind += 2;
+
+ if(
+ type == t_string ||
+ type == t_word ||
+ type == t_ref ||
+ type == t_get ||
+ type == t_set
+ ) {
+ if(type == t_string) fprintf(lf, "\"");
+ if(type == t_ref) fprintf(lf, "/");
+ if(type == t_get) fprintf(lf, ".");
+ if(type == t_set) fprintf(lf, "=");
+ s = code[i].value.p;
+ unsigned p_len = code[i].value.p_len;
+ while(p_len--) {
+ if(*s >= 0 && *s < 0x20) {
+ if(*s == '\n') {
+ fprintf(lf, "\\n");
+ }
+ else if(*s == '\t') {
+ fprintf(lf, "\\t");
+ }
+ else {
+ fprintf(lf, "\\x%02x", (unsigned char) *s);
+ }
+ }
+ else {
+ fprintf(lf, "%c", *s);
+ }
+ s++;
+ }
+ if(type == t_string) fprintf(lf, "\"");
+ }
+ else {
+ fprintf(lf, "%s", code[i].name ?: "");
+ }
+
+ if((style || opt.verbose) && code[i].enc && code[i].size > 8) {
+ for(j = 8; j < (int) code[i].size; j++) {
+ if(j & 7) {
+ fprintf(lf, " ");
+ }
+ else {
+ fprintf(lf, "\n%*s", 25 + (opt.verbose ? 7 : 0), "");
+ }
+ fprintf(lf, "%02x", code[i].enc[j]);
+ }
+ }
+ fprintf(lf, "\n");
+ }
+}
+
+
+unsigned decode_instr(unsigned char *data, type_t *type, int64_t *arg1, unsigned char **arg2)
+{
+ unsigned u, len;
+ type_t t;
+ unsigned inst_size = 1;
+ int64_t val;
+
+ u = data[0] >> 4;
+ *type = t = data[0] & 0xf;
+ *arg2 = 0;
+
+ if(TYPE_EXPECTS_DATA(t)) {
+ if(u >= 12) {
+ u -= 11;
+ len = decode_number(data + 1, u);
+ inst_size += u;
+ }
+ else {
+ len = u;
+ }
+ *arg1 = len;
+ // we want to decode the code blobs
+ if(t != t_code) {
+ *arg2 = data + inst_size;
+ inst_size += len;
+ }
+ }
+ else {
+ if(u >= 8) {
+ u -= 7;
+ val = (int64_t) decode_number(data + 1, u);
+ inst_size += u;
+ if(t == t_int) {
+ // expand sign bit
+ val <<= 8 * (8 - u);
+ val >>= 8 * (8 - u);
+ }
+ }
+ else {
+ val = u;
+ }
+ *arg1 = val;
+ }
+
+ return inst_size;
+}
+
+
+int decompile(unsigned char *data, unsigned size)
+{
+ unsigned i, j, inst_size;
+ dict_t *d;
+ code_t *c;
+ type_t type;
+ int64_t arg1;
+ unsigned char *arg2;
+
+ // setup initial vocabulary
+ for(i = 0; i < prim_words; i++) {
+ d = new_dict();
+ d->type = t_prim;
+ d->value.u = i;
+ d->name = (char *) prim_names[i];
+ }
+
+ for(i = 0; i < size; i += inst_size) {
+ inst_size = decode_instr(data + i, &type, &arg1, &arg2);
+
+ if(i + inst_size > size) {
+ if(i) {
+ fprintf(stderr, "error: instruction size bounds exceeded: %u > %u\n", i + inst_size, size);
+ }
+ else {
+ fprintf(stderr, "error: invalid file format\n");
+ }
+
+ return 1;
+ }
+
+ c = new_code();
+ c->type = type;
+
+ if(
+ i == 0 &&
+ !(
+ c->type == t_comment &&
+ inst_size == 8
+ )
+ ) {
+ fprintf(stderr, "error: invalid file format\n");
+
+ return 1;
+ }
+
+ c->ofs = i;
+ c->size = inst_size;
+ c->enc = malloc(inst_size);
+ memcpy(c->enc, data + i, inst_size);
+
+ c->value.u = (uint64_t) arg1;
+
+ if(arg2) {
+ c->value.p = arg2;
+ c->value.p_len = arg1;
+ }
+
+ if(i == 0 && decode_number(c->value.p, c->value.p_len) != GFXBOOT_MAGIC) {
+ fprintf(stderr, "error: gfxboot magic not matching\n");
+
+ return 1;
+ }
+
+ if(c->type == t_xref) {
+ c->value.u = (uint64_t) arg1;
+ for(j = 0; j < code_size - 1; j++) {
+ if(code[j].ofs == c->ofs - arg1) {
+ c->xref_to = j;
+ }
+ }
+ if(!c->xref_to) {
+ fprintf(stderr, "error: invalid cross reference: ofs 0x%x, %d\n", c->ofs, (unsigned) arg1);
+ return 2;
+ }
+ code_t *c_ref = code + c->xref_to;
+ unsigned old_ofs = c->ofs;
+ c->xref_to = 0;
+ asprintf(&c->name, "# -> offset 0x%05x", c_ref->ofs);
+ if(opt.verbose >= 2) {
+ c = new_code();
+ *c = *c_ref;
+ c->duplicate = 1;
+ }
+ else {
+ *c = *c_ref;
+ }
+ c->ofs = old_ofs;
+ }
+
+ switch(c->type) {
+ case t_code:
+ c->name = (char *) prim_names[prim_idx_code_start];
+ break;
+
+ case t_int:
+ asprintf(&c->name, "%lld", (long long) arg1);
+ break;
+
+ case t_string:
+ case t_word:
+ case t_ref:
+ case t_get:
+ case t_set:
+ c->name = calloc(1, c->value.p_len + 1);
+ memcpy(c->name, c->value.p, c->value.p_len);
+ break;
+
+ case t_prim:
+ if(arg1 < dict_size) {
+ c->name = dict[arg1].name;
+ }
+ else {
+ fprintf(stderr, "error: word %u not in dictionary\n", (unsigned) arg1);
+ return 1;
+ }
+ break;
+
+ case t_bool:
+ asprintf(&c->name, "%s", arg1 ? "true" : "false");
+ break;
+
+ case t_nil:
+ c->name = strdup("nil");
+ break;
+
+ case t_comment:
+ if(
+ c->value.p &&
+ c->value.p_len == 7 &&
+ decode_number(c->value.p, 7) == GFXBOOT_MAGIC
+ ) {
+ if(!c->name) c->name = "# gfxboot magic";
+ }
+ break;
+
+ case t_xref:
+ break;
+
+ default:
+ fprintf(stderr, "error: type %d not recognized\n", c->type);
+ return 2;
+ }
+ }
+
+ return 0;
+}
diff --git a/gfxboot-font.c b/gfxboot-font.c
new file mode 100644
index 0000000..159f062
--- /dev/null
+++ b/gfxboot-font.c
@@ -0,0 +1,1544 @@
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#pragma GCC diagnostic ignored "-Wsign-compare"
+
+#define _GNU_SOURCE
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include FT_FREETYPE_H
+#include FT_SYNTHESIS_H
+
+
+#define MAGIC 0xa42a9123
+
+// 4 bits seem to be enough
+#define GRAY_BITS 4
+// 3 gave smallest file for about 16x16 pixel fonts, but it doesn't really matter much
+#define GRAY_BIT_COUNT 3
+// maximal gray value (min is 0)
+#define MAX_GRAY ((1 << GRAY_BITS) - 3)
+// repeat background color (font pixel unset)
+#define REP_BG (MAX_GRAY + 1)
+// repeat foreground color (font pixel set)
+#define REP_FG (MAX_GRAY + 2)
+// color quantization method: choose 1 or 2
+#define COLOR_QUANT 1
+
+struct option options[] = {
+ { "add", 1, NULL, 'a' },
+ { "add-charset", 1, NULL, 'c' },
+ { "font", 1, NULL, 'f' },
+ { "line-height", 1, NULL, 'l' },
+ { "font-height", 1, NULL, 'H' },
+ { "font-path", 1, NULL, 'p' },
+ { "show", 0, NULL, 's' },
+ { "add-text", 1, NULL, 't' },
+ { "verbose", 0, NULL, 'v' },
+ { "test", 0, NULL, 999 },
+ { }
+};
+
+
+typedef struct list_any_s {
+ struct list_any_s *next;
+} list_any_t;
+
+typedef struct {
+ void *start;
+ void *end;
+} list_t;
+
+typedef struct n_set_s {
+ struct n_set_s *next;
+ int first, last;
+} n_set_t;
+
+typedef struct {
+ unsigned size;
+ unsigned char *data;
+ unsigned real_size;
+} file_data_t;
+
+typedef struct __attribute ((packed)) {
+ uint32_t magic;
+ uint8_t res1, res2, res3;
+ uint8_t max_bitmap_width;
+ uint8_t max_bitmap_height;
+ uint8_t height;
+ uint8_t line_height;
+ int8_t baseline;
+ uint32_t entries;
+} font_header_t;
+
+typedef struct font_s {
+ struct font_s *next;
+ char *name;
+ char *file_name;
+ FT_Face face;
+ int size;
+ int prop;
+ int space_width;
+ int dy;
+ unsigned index;
+ int height;
+ int baseline;
+ list_t chars; /* n_set_t */
+ unsigned used:1; /* font is actually used */
+ unsigned ok:1;
+ unsigned bold:1;
+ unsigned nobitmap:1;
+ unsigned autohint:2; /* 0: auto, 1: off, 2: on */
+ unsigned autosize:1;
+ unsigned autoshift:1;
+} font_t;
+
+typedef struct char_data_s {
+ struct char_data_s* next;
+ unsigned ok:1; /* char exists */
+ unsigned top:1;
+ unsigned bottom:1;
+ int c; /* char (utf32) */
+ font_t *font; /* pointer to font */
+ int x_advance;
+ int x_ofs; /* where to draw lower left bitmap corner */
+ int y_ofs;
+ unsigned char *bitmap; /* char bitmap, width x height */
+ int bitmap_width;
+ int bitmap_height;
+ unsigned char *data;
+ int data_len;
+} char_data_t;
+
+list_t font_list; /* font_t */
+list_t char_list; /* char_data_t */
+list_t chars_missing; /* n_set_t */
+list_t chars_top; /* n_set_t */
+list_t chars_bottom; /* n_set_t */
+
+int max_bitmap_width;
+int max_bitmap_height;
+int font_height;
+int font_y_ofs;
+
+struct {
+ int verbose;
+ int test;
+ int line_height;
+ int max_font_height;
+ char *font_path;
+ list_t chars; /* n_set_t */
+ char *file;
+ unsigned show:1;
+} opt;
+
+
+file_data_t *read_file(char *name);
+void dump_char(char_data_t *cd);
+void add_data(file_data_t *d, void *buffer, unsigned size);
+void write_data(file_data_t *d, char *name);
+int intersect(int first0, int last0, int first1, int last1);
+void insert_int_list(list_t *list, int first, int last);
+void *add_list(list_t *list, void *entry);
+void *new_mem(size_t size);
+char *new_str(char *str);
+int parse_int_list(list_t *list, char *str);
+char *search_font(char *font_path, char *name);
+void render_char(char_data_t *cd);
+int empty_row(char_data_t *cd, int row);
+int empty_column(char_data_t *cd, int column);
+void add_bbox(char_data_t *cd);
+void make_prop(char_data_t *cd);
+char *utf32_to_utf8(int u8);
+void add_bits(unsigned char *buf, int *buf_ptr, int bits, unsigned data);
+unsigned read_unsigned_bits(unsigned char *buf, int *buf_ptr, int bits);
+int read_signed_bits(unsigned char *buf, int *buf_ptr, int bits);
+int signed_bits(int num);
+int unsigned_bits(unsigned num);
+void encode_char(char_data_t *cd);
+int show_font(char *name);
+void get_font_height(font_t *font, int *height, int *y_ofs);
+
+
+int main(int argc, char **argv)
+{
+ int i, j, k, err, ofs;
+ char *str, *str1, *t, *s, *s1, *font_spec;
+ iconv_t ic = (iconv_t) -1, ic2;
+ unsigned char obuf[4];
+ char ibuf[6];
+ char obuf2[4*0x100], ibuf2[0x100];
+ char *obuf_ptr, *ibuf_ptr;
+ size_t obuf_left, ibuf_left;
+ FILE *f;
+ font_t *font;
+ n_set_t *n;
+ char_data_t *cd;
+ FT_Library ft_lib;
+ font_header_t fh;
+ file_data_t font_file = {};
+ unsigned char char_ofs[5];
+
+ opt.font_path = "\
+/usr/share/fonts/truetype:\
+/usr/share/fonts/Type1:\
+/usr/share/fonts/misc:\
+/usr/X11R6/lib/X11/fonts/truetype:\
+/usr/X11R6/lib/X11/fonts/Type1:\
+/usr/X11R6/lib/X11/fonts/misc\
+";
+
+ opterr = 0;
+
+ while((i = getopt_long(argc, argv, "Aa:c:f:H:l:p:st:v", options, NULL)) != -1) {
+ switch(i) {
+ case 'a':
+ err = parse_int_list(&opt.chars, optarg);
+ if(err) {
+ fprintf(stderr, "%s: invalid char range spec\n", optarg);
+ return 1;
+ }
+ break;
+
+ case 'c':
+ ic2 = iconv_open("utf32le", optarg);
+ if(ic2 == (iconv_t) -1) {
+ fprintf(stderr, "don't know char set %s\ntry 'iconv --list'\n", optarg);
+ return 1;
+ }
+ ibuf_ptr = ibuf2;
+ ibuf_left = sizeof ibuf2;
+ obuf_ptr = obuf2;
+ obuf_left = sizeof obuf2;
+ for(j = 0; j < sizeof ibuf2; j++) ibuf2[j] = j;
+ iconv(ic2, &ibuf_ptr, &ibuf_left, &obuf_ptr, &obuf_left);
+ for(str = obuf2; str < obuf_ptr; str += 4) {
+ i = *(int *) str;
+ if(i >= 0x20 && (i < 0x7f || i > 0x9f)) insert_int_list(&opt.chars, i, i);
+ }
+ iconv_close(ic2);
+ break;
+
+ case 'f':
+ font = add_list(&font_list, new_mem(sizeof *font));
+ font_spec = new_str(optarg);
+
+ if((s = strchr(font_spec, ':'))) {
+ font->name = new_mem(s - font_spec + 1);
+ memcpy(font->name, font_spec, s - font_spec);
+ t = s + 1;
+ err = 0;
+ while(!err && (str = strsep(&t, ":"))) {
+ if((s = strchr(str, '='))) {
+ *s++ = 0;
+ if(!strcmp(str, "size")) {
+ font->size = strtol(s, &s1, 0);
+ if(*s1) err = 1;
+ }
+ else if(!strcmp(str, "prop")) {
+ font->prop = strtol(s, &s1, 0);
+ if(*s1) err = 1;
+ }
+ else if(!strcmp(str, "space_width")) {
+ font->space_width = strtol(s, &s1, 0);
+ if(*s1) err = 1;
+ }
+ else if(!strcmp(str, "dy")) {
+ font->dy = strtol(s, &s1, 0);
+ if(*s1) err = 1;
+ }
+ else if(!strcmp(str, "bold")) {
+ font->bold = strtol(s, &s1, 0);
+ if(*s1) err = 1;
+ }
+ else if(!strcmp(str, "nobitmap")) {
+ font->nobitmap = strtol(s, &s1, 0);
+ if(*s1) err = 1;
+ }
+ else if(!strcmp(str, "autohint")) {
+ font->autohint = strtol(s, &s1, 0) + 1;
+ if(*s1) err = 1;
+ }
+ else if(!strcmp(str, "autosize")) {
+ font->autosize = strtol(s, &s1, 0);
+ if(*s1) err = 1;
+ }
+ else if(!strcmp(str, "autoshift")) {
+ font->autoshift = strtol(s, &s1, 0);
+ if(*s1) err = 1;
+ }
+ else if(!strcmp(str, "c")) {
+ err = parse_int_list(&font->chars, s);
+ }
+ else {
+ err = 1;
+ }
+ }
+ else {
+ if(*str) err = 1;
+ }
+ }
+ free(font_spec);
+ if(err) {
+ fprintf(stderr, "%s: invalid font spec\n", optarg);
+ return 1;
+ }
+ }
+ else {
+ font->name = font_spec;
+ }
+ break;
+
+ case 'H':
+ str = optarg;
+ i = strtol(str, &str1, 0);
+ if(*str1 || i < 0) {
+ fprintf(stderr, "%s: invalid font height\n", str);
+ return 1;
+ }
+ opt.max_font_height = i;
+ break;
+
+ case 'l':
+ str = optarg;
+ i = strtol(str, &str1, 0);
+ if(*str1 || i < 0) {
+ fprintf(stderr, "%s: invalid line height\n", str);
+ return 1;
+ }
+ opt.line_height = i;
+ break;
+
+ case 'p':
+ opt.font_path = optarg;
+ break;
+
+ case 's':
+ opt.show = 1;
+ break;
+
+ case 't':
+ if(ic == (iconv_t) -1) {
+ ic = iconv_open("utf32le", "utf8");
+ if(ic == (iconv_t) -1) {
+ fprintf(stderr, "can't convert utf8 data\n");
+ return 1;
+ }
+ }
+ if((f = fopen(optarg, "r"))) {
+ int ok;
+
+ ibuf_left = 0;
+ while((i = fread(ibuf + ibuf_left, 1, sizeof ibuf - ibuf_left, f)) > 0) {
+ // fprintf(stderr, "ibuf_left = %d, fread = %d\n", ibuf_left, i);
+ ibuf_ptr = ibuf;
+ ibuf_left += i;
+ do {
+ obuf_ptr = obuf;
+ obuf_left = sizeof obuf;
+ k = iconv(ic, &ibuf_ptr, &ibuf_left, &obuf_ptr, &obuf_left);
+ // fprintf(stderr, "k = %d, errno = %d, ibuf_left = %d, obuf_left = %d\n", k, k ? errno : 0, ibuf_left, obuf_left);
+ if(k >= 0 || (k == -1 && !obuf_left)) {
+ ok = 1;
+ if(!obuf_left) {
+ i = obuf[0] + (obuf[1] << 8) + (obuf[2] << 16) + (obuf[3] << 24);
+ if(i >= 0x20) {
+ insert_int_list(&opt.chars, i, i);
+ }
+ }
+ }
+ else {
+ ok = 0;
+ }
+ }
+ while(ok && ibuf_left);
+ if(k == -1 && errno == EILSEQ) {
+ perror("iconv");
+ return 1;
+ }
+ if(ibuf_left) {
+ memcpy(ibuf, ibuf + sizeof ibuf - ibuf_left, ibuf_left);
+ }
+ }
+ fclose(f);
+ }
+ else {
+ perror(optarg);
+ return 1;
+ }
+ break;
+
+ case 'v':
+ opt.verbose++;
+ break;
+
+ case 999:
+ opt.test++;
+ break;
+ }
+ }
+
+ if(ic != (iconv_t) -1) iconv_close(ic);
+
+ // use default char list
+ if(!opt.chars.start) insert_int_list(&opt.chars, 0x20, 0x7f);
+
+ argc -= optind; argv += optind;
+
+ // FreeSans[size=16 prop=2 space_width=4 dy=16 c=0x1200,0x1000-0x2000]
+ if(argc != 1) {
+ fprintf(stderr,
+ "Usage: gfxboot-font [options] fontfile\n"
+ "Build font for boot loader.\n"
+ " -a, --add=first[-last]\n\tAdd chars from this range.\n"
+ " -c, --add-charset=charset\n\tAdd all chars from this charset.\n"
+ " -f, --font=font_spec\n\tUse this font. Spec format is fontname[option1 option2 ...]\n"
+ " -h, --help\n\tShow this help text.\n"
+ " -l, --line-height=n\n\tSet line height (default: font height).\n"
+ " -p, --font-path=font path\n\tFont path, elements separated by ':'.\n"
+ " -s, --show\n\tShow font info.\n"
+ " -t, --add-text=samplefile\n\tAdd all chars used in this file. File must be UTF-8 encoded.\n"
+ " -v, --verbose\n\tDump font info.\n"
+ );
+ return 1;
+ }
+
+ opt.file = argv[0];
+
+ if(opt.show) return show_font(opt.file);
+
+ if((err = FT_Init_FreeType(&ft_lib))) {
+ fprintf(stderr, "FreeType init failed (err = %d)\n", err);
+ return 3;
+ }
+
+ // open all fonts
+ for(i = 0, font = font_list.start; font; font = font->next) {
+ font->index = i++;
+ font->file_name = search_font(opt.font_path, font->name);
+ if(font->file_name) {
+ err = FT_New_Face(ft_lib, font->file_name, 0, &font->face);
+ if(!err) {
+ if(!font->size) {
+ if(font->face->num_fixed_sizes > 0) {
+ font->size = font->face->available_sizes[0].height;
+ }
+ }
+ if(
+ font->size &&
+ !FT_Set_Pixel_Sizes(font->face, font->size, 0)
+ ) {
+ font->ok = 1;
+ }
+ }
+ }
+ }
+
+ // build char list
+ for(n = opt.chars.start; n; n = n->next) {
+ for(i = n->first; i <= n->last; i++) {
+ cd = add_list(&char_list, new_mem(sizeof *cd));
+ cd->c = i;
+ }
+ }
+
+ // just check the list is really sorted
+ for(i = -1, cd = char_list.start; cd; cd = cd->next) {
+ if(cd->c <= i) {
+ fprintf(stderr, "internal error: char list not sorted\n");
+ return 4;
+ }
+ i = cd->c;
+ }
+
+ // render all chars
+ for(cd = char_list.start; cd; cd = cd->next) {
+ render_char(cd);
+ }
+
+ // fix vertical glyph positions
+ for(cd = char_list.start; cd; cd = cd->next) {
+ if(cd->ok) cd->y_ofs += cd->font->dy;
+ }
+
+ if(!opt.test) for(cd = char_list.start; cd; cd = cd->next) add_bbox(cd);
+
+// ##############
+
+ // get font dimensions
+ get_font_height(NULL, &font_height, &font_y_ofs);
+
+ for(font = font_list.start; font; font = font->next) {
+ if(!font->ok) continue;
+ get_font_height(font, &i, &j);
+ font->height = i;
+ font->baseline = -j;
+ }
+
+// ##############
+
+ FT_Done_FreeType(ft_lib);
+
+ // label largest chars
+ for(cd = char_list.start; cd; cd = cd->next) {
+ if(!cd->ok) continue;
+ if(cd->y_ofs - font_y_ofs + cd->bitmap_height >= font_height) cd->top = 1;
+ if(cd->y_ofs - font_y_ofs <= 0) cd->bottom = 1;
+ }
+
+ for(cd = char_list.start; cd; cd = cd->next) make_prop(cd);
+
+ for(cd = char_list.start; cd; cd = cd->next) {
+ if(!cd->ok) insert_int_list(&chars_missing, cd->c, cd->c);
+ }
+
+ for(cd = char_list.start; cd; cd = cd->next) {
+ if(cd->ok && cd->top) insert_int_list(&chars_top, cd->c, cd->c);
+ }
+
+ for(cd = char_list.start; cd; cd = cd->next) {
+ if(cd->ok && cd->bottom) insert_int_list(&chars_bottom, cd->c, cd->c);
+ }
+
+ for(cd = char_list.start; cd; cd = cd->next) {
+ if(!cd->ok) continue;
+ if(cd->bitmap_width > max_bitmap_width) max_bitmap_width = cd->bitmap_width;
+ if(cd->bitmap_height > max_bitmap_height) max_bitmap_height = cd->bitmap_height;
+ }
+
+ for(cd = char_list.start; cd; cd = cd->next) encode_char(cd);
+
+ memset(&fh, 0, sizeof fh);
+
+ fh.magic = MAGIC;
+ fh.max_bitmap_width = max_bitmap_width;
+ fh.max_bitmap_height = max_bitmap_height;
+ fh.height = font_height;
+ fh.line_height = opt.line_height ?: fh.height + 2;
+ fh.baseline = -font_y_ofs;
+
+ for(cd = char_list.start; cd; cd = cd->next) if(cd->ok) fh.entries++;
+
+ // print font info
+ if(opt.verbose) {
+ printf("Font List\n");
+ for(font = font_list.start; font; font = font->next) {
+ printf(" #%d %s (%s)\n", font->index, font->name, font->ok ? "ok" : "not used");
+ printf(" File %s\n", font->file_name);
+ printf(" Size %d", font->size);
+ if(font->dy) printf(", dY %d", font->dy);
+ if(font->prop) printf(", Prop %d", font->prop);
+ if(font->space_width) printf(", SpaceWidth %d", font->space_width);
+ printf("\n");
+ printf(" Height %d, Baseline %d\n", font->height, font->baseline);
+ if(font->chars.start) {
+ for(n = font->chars.start; n; n = n->next) {
+ printf(" c 0x%04x", n->first);
+ if(n->last != n->first) printf("-0x%04x", n->last);
+ printf("\n");
+ }
+ }
+ }
+ printf("\n");
+ }
+
+ if(opt.verbose >= 2) {
+ printf("Requested Char List\n");
+ for(n = opt.chars.start; n; n = n->next) {
+ printf(" 0x%04x", n->first);
+ if(n->last != n->first) printf("-0x%04x", n->last);
+ printf("\n");
+ }
+ printf("\n");
+ }
+
+ if(opt.verbose) {
+ if(chars_missing.start) {
+ printf("Missing Chars\n");
+ for(n = chars_missing.start; n; n = n->next) {
+ printf(" 0x%04x", n->first);
+ if(n->last != n->first) printf("-0x%04x", n->last);
+ printf("\n");
+ }
+ printf("\n");
+ }
+
+ if(chars_top.start) {
+ printf("Top Chars\n");
+ for(n = chars_top.start; n; n = n->next) {
+ printf(" 0x%04x", n->first);
+ if(n->last != n->first) printf("-0x%04x", n->last);
+ printf("\n");
+ }
+ printf("\n");
+ }
+
+ if(chars_bottom.start) {
+ printf("Bottom Chars\n");
+ for(n = chars_bottom.start; n; n = n->next) {
+ printf(" 0x%04x", n->first);
+ if(n->last != n->first) printf("-0x%04x", n->last);
+ printf("\n");
+ }
+ printf("\n");
+ }
+
+ printf(
+ "Font Size\n Height: %d\n Baseline: %d\n Line Height: %d\n Bitmap Max: %d x %d\n\n",
+ font_height, -font_y_ofs, fh.line_height,
+ fh.max_bitmap_width, fh.max_bitmap_height
+ );
+
+ for(cd = char_list.start; cd; cd = cd->next) dump_char(cd);
+ }
+
+ add_data(&font_file, &fh, sizeof fh);
+
+ ofs = font_file.size + fh.entries * sizeof char_ofs;
+
+ for(cd = char_list.start; cd; cd = cd->next) {
+ if(!cd->ok) continue;
+ i = 0;
+ add_bits(char_ofs, &i, 21, cd->c);
+ add_bits(char_ofs, &i, 19, ofs);
+ add_data(&font_file, char_ofs, sizeof char_ofs);
+ ofs += cd->data_len;
+ }
+
+ for(cd = char_list.start; cd; cd = cd->next) {
+ if(!cd->ok) continue;
+ add_data(&font_file, cd->data, cd->data_len);
+ }
+
+ write_data(&font_file, opt.file);
+
+ return 0;
+}
+
+
+file_data_t *read_file(char *name)
+{
+ file_data_t *fd;
+ FILE *f;
+
+ fd = new_mem(sizeof *fd);
+
+ if(!name) return fd;
+
+ f = fopen(name, "r");
+
+ if(!f) { perror(name); return fd; }
+
+ if(fseek(f, 0, SEEK_END)) {
+ perror(name);
+ exit(30);
+ }
+
+ fd->size = fd->real_size = ftell(f);
+
+ if(fseek(f, 0, SEEK_SET)) {
+ perror(name);
+ exit(30);
+ }
+
+ if(fd->size) {
+ fd->data = new_mem(fd->size);
+ if(!fd->data) {
+ fprintf(stderr, "malloc failed\n");
+ exit(30);
+ }
+ }
+
+ if(fread(fd->data, 1, fd->size, f) != fd->size) {
+ perror(name);
+ exit(30);
+ }
+
+ fclose(f);
+
+ return fd;
+}
+
+
+void dump_char(char_data_t *cd)
+{
+ int i, j, y0, y1, y2, x0, x1, x2, width;
+ unsigned char *p;
+ char c;
+
+ if(!cd || !cd->ok) return;
+
+ printf("Char 0x%04x '%s'", cd->c, utf32_to_utf8(cd->c));
+ if(cd->top) printf(" top");
+ if(cd->bottom) printf(" bottom");
+ printf("\n");
+
+ if(cd->font) printf(" Font: #%d %s (%d)\n", cd->font->index, cd->font->name, cd->font->size);
+
+ printf(
+ " Bitmap: %d x %d\n Advance: %d\n Offset: %d x %d\n",
+ cd->bitmap_width, cd->bitmap_height,
+ cd->x_advance, cd->x_ofs, cd->y_ofs
+ );
+
+ if(opt.verbose >= 2 && cd->data) {
+ printf(" Data[%d]:", cd->data_len);
+ for(i = 0; i < cd->data_len; i++) {
+ if(!(i & 7)) {
+ printf("\n ");
+ }
+ printf(" %02x", cd->data[i]);
+ }
+ printf("\n");
+ }
+
+ if(cd->bitmap) {
+ p = cd->bitmap;
+
+ y0 = font_height + font_y_ofs;
+ y1 = y0 - cd->bitmap_height - cd->y_ofs;
+ y2 = y1 + cd->bitmap_height;
+
+ x1 = cd->bitmap_width + cd->x_ofs;
+ if(cd->x_advance > x1) x1 = cd->x_advance;
+
+ if(cd->x_ofs < 0) {
+ width = x1 - cd->x_ofs;
+ x1 = 0;
+ x0 = -cd->x_ofs;
+ }
+ else {
+ width = x1;
+ x1 = cd->x_ofs;
+ x0 = 0;
+ }
+
+ x2 = x1 + cd->bitmap_width;
+
+ // printf("y0 = %d, y1 = %d, y2 = %d\n", y0, y1, y2);
+ // printf("x0 = %d, x1 = %d, x2 = %d, width = %d\n", x0, x1, x2, width);
+
+ printf(" ");
+ c = ' ';
+ for(i = 0; i < cd->x_advance + x0; i++ ) {
+ if(i == x0) c = '_';
+ printf("%c", c);
+ }
+ printf("\n");
+
+ for(j = 0; j < font_height; j++) {
+ printf(" %s", j == y0 - 1 ? "->|" : " |");
+ if(j < y1 || j >= y2) {
+ for(i = 0; i < width; i++) printf(".");
+ }
+ else {
+ for(i = 0; i < width; i++) {
+ if(i < x1 || i >= x2) {
+ printf(".");
+ }
+ else {
+ c = p[(j - y1) * cd->bitmap_width + i - x1];
+ if(c == 0) {
+ c = ' ';
+ }
+ else if(c >= MAX_GRAY) {
+ c = '#';
+ }
+ else {
+ c += '0';
+ if(c > '9') c += 'a' - '9' - 1;
+ }
+ printf("%c", c);
+ }
+ }
+ }
+ printf("|%s\n", j == y0 - 1 ? "<-" : "");
+ }
+
+ printf(" ");
+ c = ' ';
+ for(i = 0; i < cd->x_advance + x0; i++ ) {
+ if(i == x0) c = '-';
+ printf("%c", c);
+ }
+ printf("\n");
+ }
+
+ printf("\n");
+}
+
+
+void add_data(file_data_t *d, void *buffer, unsigned size)
+{
+ if(!size || !d || !buffer) return;
+
+ if(d->size + size > d->real_size) {
+ d->real_size = d->size + size + 0x1000;
+ d->data = realloc(d->data, d->real_size);
+ if(!d->data) d->real_size = 0;
+ }
+
+ if(d->size + size <= d->real_size) {
+ memcpy(d->data + d->size, buffer, size);
+ d->size += size;
+ }
+ else {
+ fprintf(stderr, "Oops, out of memory? Aborted.\n");
+ exit(10);
+ }
+}
+
+
+void write_data(file_data_t *d, char *name)
+{
+ FILE *f;
+
+ f = strcmp(name, "-") ? fopen(name, "w") : stdout;
+
+ if(!f) {
+ perror(name);
+ return;
+ }
+
+ if(fwrite(d->data, d->size, 1, f) != 1) {
+ perror(name);
+ exit(3);
+ }
+
+ fclose(f);
+}
+
+
+int intersect(int first0, int last0, int first1, int last1)
+{
+ return
+ (first1 >= first0 && first1 <= last0 + 1) ||
+ (last1 >= first0 - 1 && last1 <= last0) ||
+ (first1 < first0 && last1 > last0);
+}
+
+
+void insert_int_list(list_t *list, int first, int last)
+{
+ n_set_t *n, *p, *next;
+
+ for(n = list->start; n; n = n->next) {
+ if(intersect(n->first, n->last, first, last)) {
+ if(first < n->first) n->first = first;
+ if(last > n->last) n->last = last;
+ break;
+ }
+ }
+
+ if(!n) { /* not joined */
+ if(!(n = list->start) || first < n->first) {
+ list->start = p = new_mem(sizeof *p);
+ p->next = n;
+ p->first = first;
+ p->last = last;
+ }
+ else {
+ for(n = list->start; n; n = n->next) {
+ if(!n->next || first < n->next->first) {
+ p = new_mem(sizeof *p);
+ p->next = n->next;
+ p->first = first;
+ p->last = last;
+ n->next = p;
+ if(!p->next) list->end = p;
+ break;
+ }
+ }
+ }
+ }
+
+ for(n = list->start; n; n = next) {
+ if(!(next = n->next)) break;
+
+ if(intersect(n->first, n->last, next->first, next->last)) {
+ if(next->first < n->first) n->first = next->first;
+ if(next->last > n->last) n->last = next->last;
+ n->next = next->next;
+ if(!n->next) list->end = n;
+ free(next);
+ next = n;
+ }
+ }
+}
+
+
+void *add_list(list_t *list, void *entry)
+{
+ if(list->end) {
+ ((list_any_t *) list->end)->next = entry;
+ }
+ list->end = entry;
+
+ if(!list->start) {
+ list->start = entry;
+ }
+
+ return entry;
+}
+
+
+void *new_mem(size_t size)
+{
+ return calloc(size, 1);
+}
+
+
+char *new_str(char *str)
+{
+ return str ? strdup(str) : str;
+}
+
+
+int parse_int_list(list_t *list, char *str)
+{
+ int err = 0, i, j, k;
+ char *s, *s1, *t;
+
+ if(!str) return 0;
+
+ while(isspace(*str)) str++;
+
+ if(!*str) return 0;
+
+ t = str = new_str(str);
+
+ while((s = strsep(&t, ","))) {
+ if(sscanf(s, "%i - %i%n", &i, &j, &k) == 2 && k == strlen(s)) {
+ insert_int_list(list, i, j);
+ }
+ else {
+ i = strtol(s, &s1, 0);
+ if(*s1) {
+ err = 1;
+ break;
+ }
+ insert_int_list(list, i, i);
+ }
+ }
+
+ free(str);
+
+ return err;
+}
+
+
+char *search_font(char *font_path, char *name)
+{
+ int i;
+ char *font_name = NULL;
+ char *cur_path, *sep;
+ struct stat sbuf;
+ static char *suffix[] = { "", ".otf", ".ttf", ".ttc", ".pfa", ".pfb", ".pcf.gz" };
+
+ if(!font_path || !name) return NULL;
+
+ while(*font_path) {
+ cur_path = strdup(font_path);
+
+ if((sep = strchr(cur_path, ':'))) *sep = 0;
+
+ for(i = 0; i < sizeof suffix / sizeof *suffix; i++) {
+ asprintf(&font_name, "%s/%s%s", cur_path, name, suffix[i]);
+ if(!stat(font_name, &sbuf) && S_ISREG(sbuf.st_mode)) break;
+ free(font_name);
+ font_name = NULL;
+ }
+
+ if(i < sizeof suffix / sizeof *suffix) {
+ free(cur_path);
+ break;
+ }
+
+ if(sep) {
+ font_path += sep - cur_path + 1;
+ }
+ else {
+ font_path = "";
+ }
+
+ free(cur_path);
+ }
+
+ return font_name;
+}
+
+
+void render_char(char_data_t *cd)
+{
+ n_set_t *n;
+ int err, glyph_index;
+ FT_GlyphSlot glyph;
+ int i, j;
+ unsigned char uc;
+
+ if(cd->ok) {
+ glyph_index = FT_Get_Char_Index(cd->font->face, cd->c);
+ if(!glyph_index) return;
+
+ err = FT_Load_Char(
+ cd->font->face,
+ cd->c,
+ FT_LOAD_RENDER |
+ (cd->font->nobitmap ? FT_LOAD_NO_BITMAP : 0) |
+ (cd->font->autohint ? cd->font->autohint == 1 ? FT_LOAD_NO_AUTOHINT : FT_LOAD_FORCE_AUTOHINT : 0)
+ );
+ if(err) return;
+ }
+ else {
+ font_t *font;
+
+ for(font = font_list.start; font; font = font->next) {
+ if(!font->ok) continue;
+ if(font->chars.start) {
+ for(n = font->chars.start; n; n = n->next) {
+ if(cd->c >= n->first && cd->c <= n->last) break;
+ }
+ if(!n) continue;
+ }
+
+ glyph_index = FT_Get_Char_Index(font->face, cd->c);
+ if(!glyph_index) continue;
+
+ err = FT_Load_Char(
+ font->face,
+ cd->c,
+ FT_LOAD_RENDER |
+ (font->nobitmap ? FT_LOAD_NO_BITMAP : 0) |
+ (font->autohint ? font->autohint == 1 ? FT_LOAD_NO_AUTOHINT : FT_LOAD_FORCE_AUTOHINT : 0)
+ );
+ if(err) continue;
+
+ cd->ok = 1;
+ cd->font = font;
+
+ break;
+ }
+ }
+
+ if(!cd->ok) return;
+
+ glyph = cd->font->face->glyph;
+ if(cd->font->bold) FT_GlyphSlot_Embolden(glyph);
+
+ cd->bitmap_width = glyph->bitmap.width;
+ cd->bitmap_height = glyph->bitmap.rows;
+ free(cd->bitmap);
+ cd->bitmap = new_mem(cd->bitmap_width * cd->bitmap_height);
+
+ cd->x_advance = glyph->advance.x / 64.;
+ cd->x_ofs = glyph->bitmap_left;
+ cd->y_ofs = glyph->bitmap_top - glyph->bitmap.rows;
+
+ for(j = 0; j < cd->bitmap_height; j++) {
+ for(i = 0; i < cd->bitmap_width; i++) {
+ switch(glyph->bitmap.pixel_mode) {
+ case FT_PIXEL_MODE_MONO:
+ uc = ((glyph->bitmap.buffer[i / 8 + j * glyph->bitmap.pitch] >> (7 - (i & 7))) & 1) * MAX_GRAY;
+ break;
+
+ case FT_PIXEL_MODE_GRAY:
+#if COLOR_QUANT == 1
+ uc = (glyph->bitmap.buffer[i + j * glyph->bitmap.pitch] * (MAX_GRAY + 1)) / (255 + 1);
+#else
+ uc = (glyph->bitmap.buffer[i + j * glyph->bitmap.pitch] * (2 * MAX_GRAY)) / (255 + 1);
+ uc = (uc + 1) / 2;
+#endif
+ break;
+
+ default:
+ uc = 0;
+ }
+ cd->bitmap[i + j * cd->bitmap_width] = uc;
+ }
+ }
+
+#if 0
+ printf(
+ "bitmap: mode %d, %d x %d, + %d x %d, advance %f x %f\n",
+ glyph->bitmap.pixel_mode,
+ glyph->bitmap.width,
+ glyph->bitmap.rows,
+ glyph->bitmap_left,
+ glyph->bitmap_top,
+ glyph->advance.x / 64.,
+ glyph->advance.y / 64.
+ );
+
+ printf(
+ "metrics:\n size %f x %f\n bearing %f x %f, advance %f\n",
+ glyph->metrics.width / 64., glyph->metrics.height / 64.,
+ glyph->metrics.horiBearingX / 64., glyph->metrics.horiBearingY / 64.,
+ glyph->metrics.horiAdvance / 64.
+ );
+#endif
+}
+
+
+int empty_row(char_data_t *cd, int row)
+{
+ unsigned char *p1, *p2;
+
+ p2 = (p1 = cd->bitmap + row * cd->bitmap_width) + cd->bitmap_width;
+ while(p1 < p2) if(*p1++) return 0;
+
+ return 1;
+}
+
+
+int empty_column(char_data_t *cd, int col)
+{
+ int i;
+ unsigned char *p;
+
+ for(p = cd->bitmap + col, i = 0; i < cd->bitmap_height; i++, p += cd->bitmap_width) {
+ if(*p) return 0;
+ }
+
+ return 1;
+}
+
+
+void add_bbox(char_data_t *cd)
+{
+ int i;
+ unsigned char *bitmap;
+ int width, height, dx, dy;
+
+ if(!cd->ok) return;
+
+ width = cd->bitmap_width;
+ height = cd->bitmap_height;
+ dx = dy = 0;
+
+ while(height && empty_row(cd, height - 1)) height--;
+ while(width && empty_column(cd, width - 1)) width--;
+
+ for(dx = 0; dx < width && empty_column(cd, dx); dx++);
+ for(dy = 0; dy < height && empty_row(cd, dy); dy++);
+
+ width -= dx;
+ height -= dy;
+
+ if(width != cd->bitmap_width || height != cd->bitmap_height) {
+ bitmap = new_mem(width * height);
+
+ for(i = 0; i < height; i++) {
+ memcpy(bitmap + i * width, cd->bitmap + dx + (i + dy) * cd->bitmap_width, width);
+ }
+
+ free(cd->bitmap);
+ cd->bitmap = bitmap;
+
+ cd->x_ofs += dx;
+ cd->y_ofs += cd->bitmap_height - height - dy;
+
+ cd->bitmap_width = width;
+ cd->bitmap_height = height;
+ }
+
+ if(!cd->bitmap_width || !cd->bitmap_height) {
+ cd->x_ofs = cd->y_ofs = 0;
+ }
+}
+
+
+/*
+ * Fake proprtionally spaced font from fixed size font.
+ */
+void make_prop(char_data_t *cd)
+{
+ int width;
+
+ if(!cd->ok || !cd->font->prop) return;
+
+ width = cd->bitmap_width ? cd->bitmap_width + cd->font->prop : cd->font->space_width;
+ cd->x_ofs = cd->font->prop;
+ cd->x_advance = width;
+}
+
+
+char *utf32_to_utf8(int u8)
+{
+ static char buf[16];
+ static iconv_t ic = (iconv_t) -1;
+ char *ibuf, *obuf;
+ size_t obuf_left, ibuf_left;
+ int i;
+
+ *buf = 0;
+
+ if(ic == (iconv_t) -1) {
+ ic = iconv_open("utf8", "utf32le");
+ if(ic == (iconv_t) -1) {
+ fprintf(stderr, "Error: can't convert utf8 data.\n");
+ exit(1);
+ }
+ }
+
+ ibuf = (char *) &u8;
+ obuf = buf;
+ ibuf_left = 4;
+ obuf_left = sizeof buf - 1;
+
+ i = iconv(ic, &ibuf, &ibuf_left, &obuf, &obuf_left);
+
+ if(i >= 0) {
+ i = sizeof buf - 1 - obuf_left;
+ buf[i] = 0;
+ }
+ else {
+ fprintf(stderr, "Warning: failed to convert 0x%x to utf8.\n", u8);
+ }
+
+ return buf;
+}
+
+
+void add_bits(unsigned char *buf, int *buf_ptr, int bits, unsigned data)
+{
+ int rem, ptr;
+
+ while(bits > 0) {
+ ptr = *buf_ptr >> 3;
+ rem = 8 - (*buf_ptr & 7);
+ if(rem > bits) rem = bits;
+ buf[ptr] = (buf[ptr] & ((1 << (*buf_ptr & 7)) - 1)) + ((data & ((1 << rem) - 1)) << (*buf_ptr & 7));
+ *buf_ptr += rem;
+ bits -= rem;
+ data >>= rem;
+ }
+}
+
+
+unsigned read_unsigned_bits(unsigned char *buf, int *buf_ptr, int bits)
+{
+ int rem, ptr;
+ unsigned data = 0, dptr = 0;
+
+ while(bits > 0) {
+ ptr = *buf_ptr >> 3;
+ rem = 8 - (*buf_ptr & 7);
+ if(rem > bits) rem = bits;
+ data += ((buf[ptr] >> (*buf_ptr & 7)) & ((1 << rem) - 1)) << dptr;
+ dptr += rem;
+ *buf_ptr += rem;
+ bits -= rem;
+ }
+
+ return data;
+}
+
+
+int read_signed_bits(unsigned char *buf, int *buf_ptr, int bits)
+{
+ int i;
+
+ i = read_unsigned_bits(buf, buf_ptr, bits);
+
+ if(bits == 0) return i;
+
+ if((i & (1 << (bits - 1)))) {
+ i += -1 << bits;
+ }
+
+ return i;
+}
+
+
+int signed_bits(int num)
+{
+ int bits = 32;
+ int val = num & (1 << 31);
+
+ if(num == 0) return 0;
+
+ while((num & (1 << 31)) == val) {
+ bits--;
+ num <<= 1;
+ }
+
+ return bits + 1;
+}
+
+
+int unsigned_bits(unsigned num)
+{
+ int bits = 0;
+
+ if(num == 0) return 0;
+
+ while(num) {
+ num >>= 1;
+ bits++;
+ }
+
+ return bits;
+}
+
+
+void encode_cnt(unsigned char *buf, int *buf_ptr, int lc, int lc_cnt)
+{
+ if((lc_cnt - 2) >= (1 << GRAY_BIT_COUNT)) {
+ fprintf(stderr, "cnt %d too large\n", lc_cnt);
+ exit(1);
+ }
+
+ if(lc_cnt >= 2) {
+ *buf_ptr -= GRAY_BITS;
+ add_bits(buf, buf_ptr, GRAY_BITS, lc == 0 ? REP_BG : REP_FG);
+ // printf("(%d)", lc == 0 ? REP_BG : REP_FG);
+ add_bits(buf, buf_ptr, GRAY_BIT_COUNT, lc_cnt - 2);
+ // printf("(%d)", lc_cnt - 2);
+ }
+ else if(lc_cnt) {
+ add_bits(buf, buf_ptr, GRAY_BITS, lc);
+ // printf("[%d]", lc);
+ }
+}
+
+
+void encode_char(char_data_t *cd)
+{
+ int i, j, bits, lc_cnt;
+ unsigned char *buf;
+ int buf_ptr;
+ unsigned type;
+ unsigned char col[MAX_GRAY + 1];
+ int lc;
+
+ if(!cd->ok) return;
+
+ // just large enough
+ buf = new_mem(cd->bitmap_width * cd->bitmap_height + 5 * 8 + 1);
+ buf_ptr = 0;
+
+ memset(col, 0, sizeof col);
+
+ for(i = 0; i < cd->bitmap_width * cd->bitmap_height; i++) {
+ if(cd->bitmap[i] <= MAX_GRAY) {
+ col[cd->bitmap[i]] = 1;
+ }
+ }
+
+ type = 0;
+ for(i = 1; i < MAX_GRAY; i++) {
+ if(col[i]) {
+ type = 1;
+ break;
+ }
+ }
+
+ // type 0: mono, 1: grays
+
+ // always 1
+ type = 1;
+
+ add_bits(buf, &buf_ptr, 2, type);
+
+ bits = unsigned_bits(cd->bitmap_width);
+ j = unsigned_bits(cd->bitmap_height);
+ if(j > bits) bits = j;
+ j = signed_bits(cd->x_advance);
+ if(j > bits) bits = j;
+ j = signed_bits(cd->x_ofs);
+ if(j > bits) bits = j;
+ j = signed_bits(cd->y_ofs);
+ if(j > bits) bits = j;
+
+ if(!bits) bits = 1;
+
+ if(bits > 8) {
+ free(buf);
+ cd->ok = 0;
+
+ return;
+ }
+
+ add_bits(buf, &buf_ptr, 3, bits - 1);
+ add_bits(buf, &buf_ptr, bits, cd->bitmap_width);
+ add_bits(buf, &buf_ptr, bits, cd->bitmap_height);
+ add_bits(buf, &buf_ptr, bits, cd->x_ofs);
+ add_bits(buf, &buf_ptr, bits, cd->y_ofs);
+ add_bits(buf, &buf_ptr, bits, cd->x_advance);
+
+ switch(type) {
+ case 0:
+ for(i = 0; i < cd->bitmap_width * cd->bitmap_height; i++) {
+ add_bits(buf, &buf_ptr, 1, cd->bitmap[i] ? 1 : 0);
+ }
+ break;
+
+ case 1:
+ lc = -1;
+ for(i = lc_cnt = 0; i < cd->bitmap_width * cd->bitmap_height; i++) {
+ if(cd->bitmap[i] == lc && (lc == 0 || lc == MAX_GRAY) && lc_cnt < ((1 << GRAY_BIT_COUNT) + 1)) {
+ lc_cnt++;
+ }
+ else {
+ if(lc_cnt) {
+ encode_cnt(buf, &buf_ptr, lc, lc_cnt);
+ lc_cnt = 0;
+ lc = -1;
+ }
+ add_bits(buf, &buf_ptr, GRAY_BITS, cd->bitmap[i]);
+ // printf("[%d]", cd->bitmap[i]);
+ }
+ lc = cd->bitmap[i];
+ }
+ if(lc_cnt) {
+ encode_cnt(buf, &buf_ptr, lc, lc_cnt);
+ }
+ break;
+ }
+
+ cd->data = new_mem(cd->data_len = ((buf_ptr + 7) >> 3));
+ memcpy(cd->data, buf, cd->data_len);
+
+ free(buf);
+}
+
+
+int show_font(char *name)
+{
+ int i, j, ofs, ofs2, bits, lc, lc_cnt, bitmap_len;
+ file_data_t *font_file;
+ font_header_t fh;
+ unsigned type;
+ char_data_t *cd;
+
+ opt.verbose++;
+
+ font_file = read_file(name);
+
+ if(font_file->size < sizeof fh) return 0;
+
+ memcpy(&fh, font_file->data, sizeof fh);
+
+ if(fh.magic != MAGIC) {
+ fprintf(stderr, "%s: wrong file format\n", name);
+ return 1;
+ }
+
+ if(font_file->size < sizeof fh + fh.entries * 5) {
+ fprintf(stderr, "%s: file too short\n", name);
+ return 2;
+ }
+
+ font_height = fh.height;
+ font_y_ofs = -fh.baseline;
+
+ for(i = 0; i < fh.entries; i++) {
+ cd = add_list(&char_list, new_mem(sizeof *cd));
+ j = 0;
+ cd->c = read_unsigned_bits(font_file->data + sizeof fh + i * 5, &j, 21);
+ ofs = read_unsigned_bits(font_file->data + sizeof fh + i * 5, &j, 19);
+
+ if(i != fh.entries - 1) {
+ j = 21;
+ ofs2 = read_unsigned_bits(font_file->data + sizeof fh + (i + 1) * 5, &j, 19);
+ }
+ else {
+ ofs2 = font_file->size;
+ }
+
+ if(ofs2 < ofs || ofs2 > font_file->size) {
+ fprintf(stderr, "%s: invalid data for chhar 0x%04x\n", name, cd->c);
+ return 3;
+ }
+
+ cd->data = new_mem(cd->data_len = ofs2 - ofs);
+ memcpy(cd->data, font_file->data + ofs, cd->data_len);
+ }
+
+ for(cd = char_list.start; cd; cd = cd->next) {
+ j = 0;
+ type = read_unsigned_bits(cd->data, &j, 2);
+ bits = read_unsigned_bits(cd->data, &j, 3) + 1;
+
+ if(type > 1) {
+ fprintf(stderr, "%s: unknown type %d for char 0x%04x\n", name, type, cd->c);
+ return 3;
+ }
+
+ cd->bitmap_width = read_unsigned_bits(cd->data, &j, bits);
+ cd->bitmap_height = read_unsigned_bits(cd->data, &j, bits);
+ cd->x_ofs = read_signed_bits(cd->data, &j, bits);
+ cd->y_ofs = read_signed_bits(cd->data, &j, bits);
+ cd->x_advance = read_signed_bits(cd->data, &j, bits);
+
+ cd->bitmap = new_mem(bitmap_len = cd->bitmap_width * cd->bitmap_height);
+
+ switch(type) {
+ case 0:
+ for(i = 0; i < bitmap_len; i++) {
+ cd->bitmap[i] = read_unsigned_bits(cd->data, &j, 1) ? MAX_GRAY : 0;
+ }
+ break;
+
+ case 1:
+ for(i = 0; i < bitmap_len;) {
+ lc = read_unsigned_bits(cd->data, &j, GRAY_BITS);
+ // printf("(%d)", lc);
+ if(lc <= MAX_GRAY) {
+ cd->bitmap[i++] = lc;
+ continue;
+ }
+ lc = lc == REP_BG ? 0 : MAX_GRAY;
+ lc_cnt = read_unsigned_bits(cd->data, &j, GRAY_BIT_COUNT) + 3;
+ // printf("(%d)", lc_cnt);
+ while(i < bitmap_len && lc_cnt--) cd->bitmap[i++] = lc;
+ }
+ break;
+ }
+
+ cd->ok = 1;
+ }
+
+ printf(
+ "Font Size\n Height: %d\n Baseline: %d\n Line Height: %d\n\n",
+ font_height, -font_y_ofs, fh.line_height
+ );
+
+ for(cd = char_list.start; cd; cd = cd->next) dump_char(cd);
+
+ return 0;
+}
+
+
+void get_font_height(font_t *font, int *height, int *y_ofs)
+{
+ int h, dy, i;
+ char_data_t *cd;
+
+ // get font dimensions
+ h = dy = 0;
+ for(cd = char_list.start; cd; cd = cd->next) {
+ if(!cd->ok) continue;
+ if(font && cd->font != font) continue;
+ if(cd->y_ofs < dy) dy = cd->y_ofs;
+ i = cd->bitmap_height + cd->y_ofs;
+ if(i > h) h = i;
+ }
+
+ *height = h - dy;
+ *y_ofs = dy;
+}
+
+
diff --git a/gfxboot-x11.c b/gfxboot-x11.c
new file mode 100644
index 0000000..cc10fac
--- /dev/null
+++ b/gfxboot-x11.c
@@ -0,0 +1,687 @@
+#define _GNU_SOURCE
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#define FAKE_WIDTH 800
+#define FAKE_HEIGHT 600
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+gfxboot_data_t *gfxboot_data;
+
+struct {
+ unsigned width, height;
+ Display *display;
+ Window window;
+ GC gc;
+ XIM xim;
+ XIC xic;
+ XImage *ximage;
+ Visual *visual;
+ int depth;
+ int timeout;
+} config;
+
+XImage fake_ximage = {
+ .width = FAKE_WIDTH, .height = FAKE_HEIGHT,
+ .bitmap_pad = 32, .bytes_per_line = FAKE_WIDTH * 4, .bits_per_pixel = 32,
+ .red_mask = 0xff0000, .green_mask = 0xff00, .blue_mask = 0xff
+};
+
+struct {
+ unsigned verbose:1;
+ unsigned help:1;
+ unsigned x11:1;
+ FILE *debug_file;
+} opt;
+
+struct option options[] = {
+ { "help", 0, NULL, 'h' },
+ { "verbose", 0, NULL, 'v' },
+ { "no-x11", 0, NULL, 'n' },
+ { "file", 1, NULL, 1 },
+ { }
+};
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void help(void);
+void run_debug_commands(FILE *file);
+
+int NewXErrorHandler(Display *display, XErrorEvent *xev);
+int NewXIOErrorHandler(Display *display);
+color_bits_t mask_to_color_bits(unsigned mask);
+
+int x11_create_window(unsigned width, unsigned height);
+int x11_close_window(void);
+int x11_event_loop(void);
+
+int x11_gfxboot_init(void);
+int x11_gfxboot_process_key(int *key);
+void x11_gfxboot_print_timeout(int timeout, void *data);
+void x11_gfxboot_clear_timeout(void *data);
+void x11_gfxboot_data_free(void);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int main(int argc, char **argv)
+{
+ int i;
+
+ opterr = 0;
+
+ opt.x11 = 1;
+
+ while((i = getopt_long(argc, argv, "hv", options, NULL)) != -1) {
+ switch(i) {
+ case 1:
+ if(!strcmp(optarg, "-")) {
+ opt.debug_file = stdin;
+ }
+ else {
+ opt.debug_file = fopen(optarg, "r");
+ }
+ break;
+
+ case 'v':
+ opt.verbose = 1;
+ break;
+
+ case 'n':
+ opt.x11 = 0;
+ break;
+
+ default:
+ help();
+ return i == 'h' ? 0 : 1;
+ }
+ }
+
+ if(argc == optind + 1) {
+ if(chdir(argv[optind])) {
+ perror(argv[optind]);
+ return 1;
+ }
+ }
+ else {
+ fprintf(stderr, "gfxboot-x11: directory missing\n");
+ help();
+ return 1;
+ }
+
+ if(opt.x11) {
+ XSetErrorHandler(NewXErrorHandler);
+ XSetIOErrorHandler(NewXIOErrorHandler);
+
+ if(x11_create_window(FAKE_WIDTH, FAKE_HEIGHT)) return 1;
+ }
+ else {
+ config.ximage = &fake_ximage;
+ config.ximage->data = calloc(1, (size_t) (config.ximage->bytes_per_line * config.ximage->height));
+ config.width = (unsigned) config.ximage->width;
+ config.height = (unsigned) config.ximage->height;
+ config.depth = config.ximage->bits_per_pixel;
+ }
+
+ config.timeout = opt.debug_file ? 0 : 10;
+
+ if(x11_gfxboot_init()) return 2;
+
+ run_debug_commands(opt.debug_file);
+
+ if(opt.x11) {
+ x11_gfxboot_print_timeout(config.timeout, NULL);
+ x11_event_loop();
+ x11_close_window();
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Display short usage message.
+void help()
+{
+ printf(
+ "Usage: gfxboot-x11 [OPTIONS] DIR\n"
+ "\n"
+ "Run gfxboot file in DIR.\n"
+ "\n"
+ "Options:\n"
+ " --file FILE Run debug commands from FILE at startup.\n"
+ " -v, --verbose Show more detailed info.\n"
+ " -h, --help Show this text.\n"
+ "\n"
+ );
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void run_debug_commands(FILE *file)
+{
+ if(!file) return;
+
+ char *buf = NULL;
+ size_t len = 0;
+ ssize_t line_len;
+
+ while((line_len = getline(&buf, &len, file)) > 0) {
+ buf[line_len - 1] = 0; // strip newline
+ gfxboot_debug_command(buf);
+ }
+
+ fclose(file);
+
+ free(buf);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int NewXErrorHandler(Display *display, XErrorEvent *xev)
+{
+ gfxboot_log("X Error\n");
+
+ exit(1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int NewXIOErrorHandler(Display *display)
+{
+ // gfxboot_log("X IO Error\n");
+
+ exit(0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+color_bits_t mask_to_color_bits(unsigned mask)
+{
+ color_bits_t x = { 0, 0 };
+
+ if(mask) for(; !(mask & 1); mask >>= 1, x.pos++);
+ if(mask) for(; mask; mask >>= 1, x.size++);
+
+ return x;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int x11_create_window(unsigned width, unsigned height)
+{
+ char *title = "gfxboot2";
+
+ config.width = width;
+ config.height = height;
+
+ XSizeHints hints;
+
+ hints.width_inc = 1;
+ hints.height_inc = 1;
+
+ hints.min_width = hints.max_width = hints.width = (int) config.width;
+ hints.min_height = hints.max_height = hints.height = (int) config.height;
+
+ hints.flags = PResizeInc | PSize | PMinSize | PMaxSize;
+
+ if((config.display = XOpenDisplay(NULL)) == NULL) {
+ gfxboot_log("Error: failed to open display.\n");
+
+ return 1;
+ }
+
+ config.window = XCreateSimpleWindow(
+ config.display, DefaultRootWindow(config.display),
+ 0, 0,
+ config.width, config.height,
+ 0, 0, 0
+ );
+
+ gfxboot_log("window id: 0x%08x\n", (unsigned) config.window);
+
+ if(!config.window) {
+ gfxboot_log("Error: X server does not co-operate\n");
+ XCloseDisplay(config.display);
+
+ return 2;
+ }
+
+ config.xim = XOpenIM(config.display, NULL, NULL, NULL);
+ if(config.xim) {
+ config.xic = XCreateIC(config.xim,
+ XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
+ XNClientWindow, config.window,
+ XNFocusWindow, config.window,
+ NULL
+ );
+ }
+
+ fprintf(stderr, "xim = %p, xic = %p\n", config.xim, config.xic);
+
+ XSetWindowAttributes xswa;
+
+ xswa.backing_store = Always;
+ xswa.backing_planes = 1;
+ xswa.save_under = True;
+
+ XChangeWindowAttributes(config.display, config.window, CWBackingStore | CWBackingPlanes | CWSaveUnder, &xswa);
+
+ XSelectInput(config.display, config.window, StructureNotifyMask);
+ XSetStandardProperties(config.display, config.window, title, title, None, NULL, 0, &hints);
+
+ XMapWindow(config.display, config.window);
+ XSync(config.display, False);
+
+ XSelectInput(config.display, config.window, NoEventMask);
+
+ config.visual = DefaultVisual(config.display, DefaultScreen(config.display));
+ config.depth = DefaultDepth(config.display, DefaultScreen(config.display));
+
+ if(
+ (
+ config.visual->class != TrueColor &&
+ config.visual->class != DirectColor
+ ) ||
+ config.depth < 15
+ ) {
+ return 3;
+ }
+
+ config.gc = XCreateGC(config.display, config.window, 0, 0);
+
+ config.ximage = XCreateImage(
+ config.display,
+ config.visual,
+ (unsigned) config.depth,
+ ZPixmap,
+ 0,
+ NULL,
+ config.width,
+ config.height,
+ 32,
+ 0
+ );
+
+ if(!config.ximage) {
+ gfxboot_log("Error: failed to create XImage\n");
+ XCloseDisplay(config.display);
+
+ return 4;
+ }
+
+ config.ximage->data = calloc(1, (size_t) (config.ximage->height * config.ximage->bytes_per_line));
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int x11_close_window(void)
+{
+ XFreeGC(config.display, config.gc);
+ XDestroyImage(config.ximage);
+ XCloseDisplay(config.display);
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int x11_event_loop()
+{
+ int exit_event = 0;
+
+ XSelectInput(config.display, config.window, KeyPressMask|ExposureMask|StructureNotifyMask|ButtonPressMask|PointerMotionMask);
+
+ while(!exit_event) {
+ int action __attribute__ ((unused)) = 0;
+ XEvent xev;
+ KeySym ks;
+ // buf just needs to be large enough to hold a key code
+ char buf[16], *buf_ptr;
+ int buf_len, key_code;
+ char *key_name;
+
+ while(XPending(config.display)) {
+ XNextEvent(config.display, &xev);
+ switch(xev.type) {
+
+ case KeyPress:
+ if(config.xic) {
+ buf_len = Xutf8LookupString(config.xic, (XKeyEvent *) &xev, buf, sizeof buf - 1, &ks, NULL);
+ }
+ else {
+ buf_len = XLookupString((XKeyEvent *) &xev, buf, sizeof buf - 1, &ks, NULL);
+ }
+ if(buf_len >= 0) buf[buf_len + 1] = 0;
+ buf_ptr = buf;
+ key_code = gfx_utf8_dec(&buf_ptr, &buf_len);
+ if(key_code < 0) key_code = -key_code;
+ key_name = XKeysymToString(ks);
+ gfxboot_debug(2, 2, "x11_event_loop: key = 0x%02x '%s'\n", key_code, key_name);
+ // '^C'
+ if(key_code == 0x03) exit_event = 1;
+ action = x11_gfxboot_process_key(&key_code);
+ break;
+
+ case Expose:
+ // gfxboot_log("XEvent.type == Expose(%d, %d, %d, %d)\n", xev.xexpose.x, xev.xexpose.y, xev.xexpose.width, xev.xexpose.height);
+ break;
+
+ case ConfigureNotify:
+ // gfxboot_log("XEvent.type == ConfigureNotify\n");
+ if(xev.xconfigure.width != (int) config.width || xev.xconfigure.height != (int) config.height) {
+ gfxboot_log("ConfigureNotify: oops\n");
+ }
+ break;
+
+ case ButtonPress:
+ gfxboot_log("XEvent.type == ButtonPress\n");
+ break;
+
+ default:
+ // gfxboot_log("XEvent.type == 0x%x\n", (int) xev.type);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int x11_gfxboot_init()
+{
+ x11_gfxboot_data_free();
+
+ gfxboot_data = calloc(1, sizeof *gfxboot_data);
+ if(!gfxboot_data) return 1;
+
+ // don't log to console
+ gfxboot_data->vm.debug.log_level_console = -1;
+
+ gfxboot_data->menu.entry = 0;
+ gfxboot_data->menu.nested = 0;
+ gfxboot_data->menu.timeout.max = config.timeout;
+
+ gfxboot_data->screen.real.ptr = config.ximage->data;
+ gfxboot_data->screen.real.width = (int) config.width;
+ gfxboot_data->screen.real.height = (int) config.height;
+
+ gfxboot_data->screen.real.bytes_per_line = config.ximage->bytes_per_line;
+ gfxboot_data->screen.real.bytes_per_pixel = config.ximage->bitmap_pad / 8;
+ gfxboot_data->screen.real.bits_per_pixel = config.ximage->bits_per_pixel;
+
+ gfxboot_data->screen.real.red = mask_to_color_bits(config.ximage->red_mask);
+ gfxboot_data->screen.real.green = mask_to_color_bits(config.ximage->green_mask);
+ gfxboot_data->screen.real.blue = mask_to_color_bits(config.ximage->blue_mask);
+
+ unsigned u = ~(config.ximage->red_mask | config.ximage->green_mask | config.ximage->blue_mask);
+ u &= ((uint64_t) 1 << (gfxboot_data->screen.real.bytes_per_pixel * 8)) - 1;
+ gfxboot_data->screen.real.res = mask_to_color_bits(u);
+
+ // reserve 16 MiB for our VM
+ gfxboot_data->vm.mem.size = 16 * (1 << 20);
+ gfxboot_data->vm.mem.ptr = calloc(1, gfxboot_data->vm.mem.size);
+ if(!gfxboot_data->vm.mem.ptr) return 1;
+
+ if(gfxboot_init()) return 1;
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int x11_gfxboot_process_key(int *key)
+{
+ int action;
+
+ if(!gfxboot_data || !key) return 0;
+
+ gfxboot_debug(2, 2, "x11_gfxboot_process_key: key = 0x%x '%s'\n", *key, *key >= ' ' ? gfx_utf8_enc((unsigned) *key) : "");
+
+ if(gfxboot_data->menu.timeout.current > 0) {
+ // grub_env_unset("timeout");
+ // grub_env_unset("fallback");
+ x11_gfxboot_clear_timeout(NULL);
+ }
+
+ action = gfxboot_process_key((unsigned) *key);
+
+ gfxboot_debug(1, 2, "x11_gfxboot_process_key: action = %d.%02x\n", action >> 8, action & 0xff);
+
+ *key = 0;
+
+ return action;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void x11_gfxboot_print_timeout(int timeout, void *data __attribute__ ((unused)))
+{
+ gfxboot_data->menu.timeout.current = timeout;
+
+ gfxboot_timeout();
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void x11_gfxboot_clear_timeout(void *data __attribute__ ((unused)))
+{
+ gfxboot_data->menu.timeout.current = 0;
+
+ gfxboot_timeout();
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Print to serial line and/or console.
+//
+// dst - bitmask; bit 0: serial, bit 1: console
+//
+int gfxboot_printf(int dst, const char *format, ...)
+{
+ int len;
+ char *s;
+ int log_level_serial = (signed char) ((dst >> 8) & 0xff);
+ int log_level_console = (signed char) ((dst >> 16) & 0xff);
+
+ if(!format) return 0;
+
+ va_list args;
+ va_start(args, format);
+ len = vasprintf(&s, format, args);
+ va_end(args);
+
+ if(
+ (dst & 1) &&
+ (!gfxboot_data || log_level_serial <= gfxboot_data->vm.debug.log_level_serial)
+ ) {
+ fwrite(s, (size_t) len, 1, stdout);
+ fflush(stdout);
+ }
+
+ if(
+ (dst & 2) &&
+ gfxboot_data &&
+ log_level_console <= gfxboot_data->vm.debug.log_level_console
+ ) {
+ char *t = s;
+ while(*t) {
+ if(*t == '\n') gfx_console_putc('\r', 1);
+ gfx_console_putc((unsigned char) *t++, 1);
+ }
+ }
+
+ free(s);
+
+ return len;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_asprintf(char **str, const char *format, ...)
+{
+ int len;
+
+ va_list args;
+ va_start(args, format);
+ len = vasprintf(str, format, args);
+ va_end(args);
+
+ return len;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_snprintf(char *str, unsigned size, const char *format, ...)
+{
+ int len;
+
+ va_list args;
+ va_start(args, format);
+ len = vsnprintf(str, size, format, args);
+ va_end(args);
+
+ return len;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfxboot_sys_free(void *ptr)
+{
+ if(ptr) free(ptr);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned long gfxboot_sys_strlen(const char *s)
+{
+ return strlen(s);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_sys_strcmp(const char *s1, const char *s2)
+{
+ return strcmp(s1, s2);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+long int gfxboot_sys_strtol(const char *nptr, char **endptr, int base)
+{
+ return strtol(nptr, endptr, base);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_sys_read_file(char *name, void **buf)
+{
+ int size = -1;
+ char *tmp = NULL;
+ struct stat sbuf;
+ int fd, len;
+
+ *buf = NULL;
+
+ if(!name) return size;
+
+ fd = open(name, O_RDONLY);
+
+ gfxboot_log("open(%s) = %d\n", name, fd);
+
+ if(fd >= 0) {
+ if(!fstat(fd, &sbuf)) {
+ size = sbuf.st_size;
+ }
+
+ if(size > 0) {
+ *buf = tmp = malloc((size_t) size);
+ while(size > 0) {
+ len = read(fd, tmp, (size_t) size);
+ if(len <= 0) break;
+ size -= len;
+ tmp += len;
+ }
+ }
+
+ close(fd);
+
+ if(size > 0) {
+ *buf = NULL;
+ free(tmp);
+ size = -1;
+ }
+ else {
+ size = sbuf.st_size;
+ }
+ }
+
+ gfxboot_log("read(%s) = %d\n", name, size);
+
+ return size;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfxboot_screen_update(area_t area)
+{
+ if(opt.x11) {
+ XPutImage(
+ config.display, config.window, config.gc, config.ximage,
+ area.x, area.y, area.x, area.y, (unsigned) area.width, (unsigned) area.height
+ );
+}
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_getkey()
+{
+ int key = 0;
+ struct termios tio_old, tio;
+
+ tcgetattr(0, &tio);
+ tio_old = tio;
+ tio.c_lflag &= (unsigned) ~(ICANON | ECHO);
+ tcsetattr(0, TCSANOW, &tio);
+
+ key = fgetc(stdin);
+
+ tcsetattr(0, TCSANOW, &tio_old);
+
+ if(key == EOF) key = 0;
+
+ return key;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void x11_gfxboot_data_free()
+{
+ if(gfxboot_data) {
+ free(gfxboot_data->menu.entries);
+ free(gfxboot_data->vm.mem.ptr);
+
+ free(gfxboot_data);
+ }
+
+ gfxboot_data = NULL;
+}
diff --git a/gfxboot.c b/gfxboot.c
new file mode 100644
index 0000000..7eb7a83
--- /dev/null
+++ b/gfxboot.c
@@ -0,0 +1,404 @@
+#include
+#include
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// program functions
+
+static uint64_t gfx_decode_number(const void *data, unsigned len);
+static unsigned decode_raw_instr(uint8_t *data, decoded_instr_t *instr);
+
+static inline uint64_t tsc(void)
+{
+#if defined (__x86_64__)
+ uint32_t eax, edx;
+
+ asm volatile (
+ "rdtsc\n"
+ : "=a" (eax), "=d" (edx)
+ );
+
+ return ((uint64_t) edx << 32) + eax;
+#elif defined (__i386__)
+ uint64_t tsc;
+
+ asm volatile (
+ "rdtsc"
+ : "=A" (tsc)
+ );
+
+ return tsc;
+#else
+ #error "OOPS"
+ return 0;
+#endif
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uint64_t gfx_decode_number(const void *data, unsigned len)
+{
+ const uint8_t *p = data;
+ uint64_t val = 0;
+
+ p += len;
+
+ while(len--) {
+ val <<= 8;
+ val += *--p;
+ }
+
+ return val;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_program_init(obj_id_t program)
+{
+ gfxboot_data->vm.error = (error_t ) { };
+
+ gfxboot_data->vm.debug.steps = 0;
+ gfxboot_data->vm.debug.log_prompt = 1;
+
+ gfxboot_data->vm.program.steps = 0;
+
+ gfx_obj_ref_dec(gfxboot_data->vm.program.pstack);
+
+ gfx_obj_ref_dec(gfxboot_data->vm.program.context);
+ gfxboot_data->vm.program.context = 0;
+
+ gfxboot_data->vm.program.pstack = gfx_obj_array_new(0);
+
+ if(!gfxboot_data->vm.program.pstack) {
+ GFX_ERROR(err_no_memory);
+ return 0;
+ }
+
+ if(!gfx_is_code(program)) {
+ GFX_ERROR(err_invalid_code);
+ return 0;
+ }
+
+ obj_id_t ctx_id = gfx_obj_context_new(t_ctx_func);
+ context_t *ctx = gfx_obj_context_ptr(ctx_id);
+ if(!ctx) {
+ GFX_ERROR(err_no_memory);
+ return 0;
+ }
+
+ ctx->dict_id = gfx_obj_ref_inc(gfxboot_data->vm.program.dict);
+ ctx->code_id = gfx_obj_ref_inc(program);
+
+ data_t *code = gfx_obj_mem_ptr(ctx->code_id);
+
+ gfxboot_log("program loaded %s (%u bytes)\n", gfx_obj_id2str(program), code->size);
+
+ gfxboot_data->vm.program.context = ctx_id;
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned decode_raw_instr(uint8_t *data, decoded_instr_t *instr)
+{
+ unsigned inst_size = 1;
+
+ unsigned type = data[0] & 0x0f;
+
+ unsigned n = data[0] >> 4;
+
+ instr->arg2 = 0;
+ instr->type = type;
+
+ if(TYPE_EXPECTS_DATA(type)) {
+ unsigned len;
+ if(n >= 12) {
+ n -= 11;
+ len = gfx_decode_number(data + 1, n);
+ inst_size += n;
+ }
+ else {
+ len = n;
+ }
+ instr->arg1 = len;
+ instr->arg2 = data + inst_size;
+ inst_size += len;
+ }
+ else {
+ int64_t val;
+ if(n >= 8) {
+ n -= 7;
+ val = (int64_t) gfx_decode_number(data + 1, n);
+ inst_size += n;
+ // ints are signed, everything else is encoded as unsigned
+ if(type == t_int) {
+ // expand sign bit
+ val <<= 8 * (8 - n);
+ val >>= 8 * (8 - n);
+ }
+ }
+ else {
+ val = n;
+ }
+ instr->arg1 = val;
+ }
+
+ return inst_size;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// decoded arguments are:
+//
+// int64_t arg1, uint8_t[arg1] arg2
+//
+// arg2 is optional and may be 0; if arg2 is not 0, arg1 is its length
+//
+int gfx_decode_instr(decoded_instr_t *instr)
+{
+ *instr = (decoded_instr_t) { };
+
+ if(gfxboot_data->vm.program.stop || gfxboot_data->vm.error.id) return 0;
+
+ gfxboot_data->vm.program.steps++;
+
+ obj_id_t ctx_id = gfxboot_data->vm.program.context;
+ if(!ctx_id) return 0;
+
+ instr->ctx = gfx_obj_context_ptr(ctx_id);
+ if(!instr->ctx) {
+ GFX_ERROR(err_internal);
+ return 0;
+ }
+
+ instr->code_id = instr->ctx->code_id;
+
+ unsigned ip = instr->ctx->ip;
+ instr->ctx->current_ip = ip;
+
+ obj_t *code_ptr = gfx_obj_ptr(instr->code_id);
+
+ if(!code_ptr || code_ptr->base_type != OTYPE_MEM) {
+ GFX_ERROR(err_invalid_code);
+ return 0;
+ }
+
+ data_t *mem = OBJ_DATA_FROM_PTR(code_ptr);
+
+ unsigned size = mem->size;
+ uint8_t *data = mem->ptr;
+
+ // at code end, do nothing
+ if(ip == size) return 0;
+
+ if(ip + 1 > size) { // encoded instructions are at least 1 bytes
+ GFX_ERROR(err_invalid_code);
+ return 0;
+ }
+
+ unsigned inst_size = decode_raw_instr(data + ip, instr);
+
+ // for a cross reference, look it up and do the same again
+ if(instr->type == t_xref) {
+ // xref_ip may intentionally get negative
+ int xref_ip = ip - instr->arg1;
+
+#if 0
+ if(gfxboot_data->vm.debug.trace.ip) {
+ gfxboot_log("IP: xref ip 0x%x\n", xref_ip);
+ }
+#endif
+
+ decode_raw_instr(data + xref_ip, instr);
+ }
+
+ if(gfxboot_data->vm.debug.trace.ip) {
+ gfxboot_log("IP: #%u:0x%x, type %d, ", OBJ_ID2IDX(instr->ctx->code_id), ip, instr->type);
+ if(instr->arg2) {
+ gfxboot_log("%d[%u]\n", (int) (instr->arg2 - data), (unsigned) instr->arg1);
+ }
+ else {
+ gfxboot_log("%lld (0x%llx)\n", (long long) instr->arg1, (long long) instr->arg1);
+ }
+ }
+
+ unsigned next_ip = ip + inst_size;
+
+ if(next_ip > size) {
+ GFX_ERROR(err_invalid_code);
+ return 0;
+ }
+
+ instr->ctx->ip = next_ip;
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_program_run()
+{
+ uint64_t tsc_start = tsc(), tsc_last = tsc_start;
+
+ gfxboot_data->vm.program.stop = 0;
+
+ // gfxboot_log("running code\n");
+
+ decoded_instr_t instr;
+
+ unsigned steps = gfxboot_data->vm.debug.steps;
+ unsigned steps_set = steps ? 1 : 0;
+
+ for(; (!steps_set || steps) && gfx_decode_instr(&instr); steps--) {
+ switch(instr.type) {
+ case t_int:
+ case t_bool:
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(instr.arg1, instr.type), 0);
+ break;
+
+ case t_nil:
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, 0, 0);
+ break;
+
+ case t_string:
+ case t_ref:
+ case t_code:
+ gfx_obj_array_push(
+ gfxboot_data->vm.program.pstack,
+ gfx_obj_const_mem_nofree_new(instr.arg2, instr.arg1, instr.type, instr.code_id),
+ 0
+ );
+ break;
+
+ case t_prim:
+ gfx_run_prim(instr.arg1);
+ break;
+
+ case t_word:
+ {
+ data_t key = { .ptr = instr.arg2, .size = instr.arg1 };
+ obj_id_pair_t pair = gfx_lookup_dict(&key);
+ if(!pair.id1) {
+ GFX_ERROR(err_invalid_code);
+ }
+ else {
+ gfx_exec_id(0, pair.id2, 0);
+ }
+ }
+ break;
+
+ case t_get:
+ gfx_prim_get_x(& (data_t) { .ptr = instr.arg2, .size = instr.arg1 });
+ break;
+
+ case t_set:
+ {
+ obj_id_t key = gfx_obj_const_mem_nofree_new(instr.arg2, instr.arg1, t_ref, instr.code_id);
+ gfx_prim_put_x(key);
+ gfx_obj_ref_dec(key);
+ }
+ break;
+
+ case t_comment:
+ // do nothing
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_code);
+ }
+
+ gfx_debug_show_trace();
+
+ if(gfxboot_data->vm.debug.trace.time) {
+ uint64_t tmp = tsc();
+ gfxboot_log("TIME: %llu\n", (unsigned long long) tmp - tsc_last);
+ tsc_last = tmp;
+ }
+ }
+
+ gfxboot_data->vm.program.time += tsc() - tsc_start;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_pair_t gfx_lookup_dict(data_t *key)
+{
+ obj_id_pair_t pair = { };
+
+ context_t *context = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+
+ while(context) {
+ if(
+ context &&
+ context->dict_id &&
+ (pair = gfx_obj_hash_get(context->dict_id, key)).id1
+ ) {
+ return pair;
+ }
+ context = gfx_obj_context_ptr(context->parent_id);
+ }
+
+ return gfx_obj_hash_get(gfxboot_data->vm.program.dict, key);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_is_code(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_MEM || !ptr->flags.ro) {
+ return 0;
+ }
+
+ data_t *mem = OBJ_DATA_FROM_PTR(ptr);
+
+ // either we know it's a code blob or we check for the magic
+ if(
+ ptr->sub_type == t_code ||
+ (mem->size >= 8 && gfx_decode_number(mem->ptr, 8) == (GFXBOOT_MAGIC << 8) + 0x70 + t_comment)
+ ) {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+area_t gfx_font_dim(obj_id_t font_id)
+{
+ area_t area = { };
+ font_t *font;
+
+ for(; (font = gfx_obj_font_ptr(font_id)); font_id = font->parent_id) {
+ if(font->width > area.width) area.width = font->width;
+ if(font->height > area.height) area.height = font->line_height;
+ }
+
+ return area;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_image_open(obj_id_t image_file)
+{
+ data_t *mem = gfx_obj_mem_ptr(image_file);
+
+ if(!mem || !mem->size) return 0;
+
+ unsigned u = gfx_jpeg_getsize(mem->ptr);
+
+ int width = u & 0xffff;
+ int height = (int) (u >> 16);
+
+ obj_id_t image_id = gfx_obj_canvas_new(width, height);
+ canvas_t *canvas = gfx_obj_canvas_ptr(image_id);
+
+ if(gfx_jpeg_decode(mem->ptr, (uint8_t *) &canvas->ptr, 0, canvas->width, 0, canvas->height, 32)) {
+ gfx_obj_ref_dec(image_id);
+ image_id = 0;
+ }
+
+ return image_id;
+}
diff --git a/gfxboot.h b/gfxboot.h
new file mode 100644
index 0000000..69518f3
--- /dev/null
+++ b/gfxboot.h
@@ -0,0 +1,538 @@
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#pragma GCC diagnostic ignored "-Wshift-negative-value"
+#pragma GCC diagnostic ignored "-Wpointer-arith"
+#pragma GCC diagnostic ignored "-Waddress-of-packed-member"
+
+// #define FULL_ERROR
+
+typedef __UINT8_TYPE__ uint8_t;
+typedef __INT8_TYPE__ int8_t;
+typedef __UINT16_TYPE__ uint16_t;
+typedef __INT16_TYPE__ int16_t;
+typedef __UINT32_TYPE__ uint32_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __UINT64_TYPE__ uint64_t;
+typedef __INT64_TYPE__ int64_t;
+
+#if __LONG_WIDTH__ == 32
+#define INCLUDE_DIV64 1
+#else
+#define INCLUDE_DIV64 0
+#endif
+
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+
+#define gfx_memset __builtin_memset
+#define gfx_memcpy __builtin_memmove
+#define gfx_memcmp __builtin_memcmp
+#define gfx_strlen gfxboot_sys_strlen
+#define gfx_strcmp gfxboot_sys_strcmp
+#define gfx_strtol gfxboot_sys_strtol
+
+typedef uint32_t color_t;
+typedef uint32_t obj_id_t;
+
+#define COLOR_BYTES ((int) sizeof (color_t))
+#define ALPHA_POS 24
+#define ALPHA_BITS 8
+#define RED_POS 16
+#define RED_BITS 8
+#define GREEN_POS 8
+#define GREEN_BITS 8
+#define BLUE_POS 0
+#define BLUE_BITS 8
+#define COLOR(a, r, g, b) (((unsigned) (a) << ALPHA_POS) + ((r) << RED_POS) + ((g) << GREEN_POS) + ((b) << BLUE_POS))
+
+#define OBJ_ID_GEN_BITS 8
+#define OBJ_ID_GEN_POS (32 - OBJ_ID_GEN_BITS)
+#define OBJ_ID_GEN_MASK ~((1 << OBJ_ID_GEN_POS) - 1)
+
+#define OBJ_TYPE_MASK ((1 << 8) - 1)
+
+#define OBJ_ID(id, gen) ((unsigned) id + (((unsigned) gen) << OBJ_ID_GEN_POS))
+#define OBJ_ID2GEN(id) ((unsigned) id >> OBJ_ID_GEN_POS)
+#define OBJ_ID2IDX(id) ((unsigned) id & ~OBJ_ID_GEN_MASK)
+#define OBJ_ID_ASSIGN(a, b) do { obj_id_t tmp = a; a = gfx_obj_ref_inc(b); gfx_obj_ref_dec(tmp); } while(0)
+
+#define OTYPE_NONE 0 // should be 0
+#define OTYPE_MEM 1
+#define OTYPE_OLIST 2
+#define OTYPE_FONT 3
+#define OTYPE_CANVAS 4
+#define OTYPE_ARRAY 5
+#define OTYPE_HASH 6
+#define OTYPE_CONTEXT 7
+#define OTYPE_NUM 8
+#define OTYPE_GSTATE 9
+#define OTYPE_INVALID 10
+#define OTYPE_ANY 11
+
+// internal memory size of object with size n
+#define OBJ_OLIST_SIZE(n) (sizeof (olist_t) + (n) * sizeof (obj_t))
+#define OBJ_FONT_SIZE() (sizeof (font_t))
+#define OBJ_CANVAS_SIZE(w, h) (sizeof (canvas_t) + (unsigned) (w) * (unsigned) (h) * sizeof *((canvas_t) {0}).ptr)
+#define OBJ_ARRAY_SIZE(n) (sizeof (array_t) + (n) * sizeof *((array_t) {0}).ptr)
+#define OBJ_HASH_SIZE(n) (sizeof (hash_t) + (n) * sizeof *((hash_t) {0}).ptr)
+#define OBJ_CONTEXT_SIZE() (sizeof (context_t))
+#define OBJ_GSTATE_SIZE() (sizeof (gstate_t))
+
+#define OBJ_DATA_FROM_PTR(p) (&(p)->data)
+#define OBJ_MEM_FROM_PTR(p) ((p)->data.ptr)
+#define OBJ_OLIST_FROM_PTR(p) ((olist_t *) (p)->data.ptr)
+#define OBJ_FONT_FROM_PTR(p) ((font_t *) (p)->data.ptr)
+#define OBJ_CANVAS_FROM_PTR(p) ((canvas_t *) (p)->data.ptr)
+#define OBJ_ARRAY_FROM_PTR(p) ((array_t *) (p)->data.ptr)
+#define OBJ_HASH_FROM_PTR(p) ((hash_t *) (p)->data.ptr)
+#define OBJ_CONTEXT_FROM_PTR(p) ((context_t *) (p)->data.ptr)
+#define OBJ_GSTATE_FROM_PTR(p) ((gstate_t *) (p)->data.ptr)
+#define OBJ_VALUE_FROM_PTR(p) ((p)->data.value)
+
+#define ADD_AREA(a, b) (a).x += (b).x, (a).y += (b).y, (a).width += (b).width, (a).height += (b).height
+
+#if 0
+pserr_ok equ 0
+pserr_nocode equ 1
+pserr_invalid_opcode equ 2
+pserr_pstack_underflow equ 3
+pserr_pstack_overflow equ 4
+pserr_invalid_dict equ 7
+pserr_wrong_arg_types equ 8
+pserr_div_by_zero equ 9
+pserr_invalid_range equ 0bh
+pserr_invalid_exit equ 0ch
+pserr_invalid_image_size equ 0dh
+pserr_no_memory equ 0eh
+pserr_invalid_data equ 0fh
+pserr_nop equ 10h
+pserr_invalid_function equ 11h
+pserr_invalid_dict_entry equ 200h
+pserr_invalid_prim equ 201h
+#endif
+
+#ifdef FULL_ERROR
+#define GFX_ERROR(a) gfxboot_data->vm.error = (error_t) { .id = (a), .src_file = __FILE__, .src_line = __LINE__ }
+#else
+#define GFX_ERROR(a) gfxboot_data->vm.error.id = (a)
+#endif
+
+// see gfx_error_msg()
+typedef enum {
+ err_ok = 0, err_invalid_code, err_invalid_instruction,
+ err_no_array_start, err_no_hash_start, err_no_memory,
+ err_invalid_hash_key, err_stack_underflow, err_internal,
+ err_no_loop_context, err_invalid_range, err_invalid_data,
+ err_readonly, err_invalid_arguments, err_div_by_zero
+} error_id_t;
+
+typedef struct {
+ error_id_t id;
+#ifdef FULL_ERROR
+ const char *src_file;
+ int src_line;
+#endif
+} error_t;
+
+typedef struct {
+ unsigned inspect:1;
+ unsigned dump:1;
+ unsigned ref:1;
+ unsigned no_nl:1;
+ unsigned no_head:1;
+ unsigned max;
+} dump_style_t;
+
+typedef struct {
+ uint32_t next; // offset to next header
+ uint32_t id; // 0 = free, otherwise used
+} malloc_header_t;
+
+typedef struct {
+ const char *title;
+} gfxboot_menu_t;
+
+typedef struct {
+ int pos, size;
+} color_bits_t;
+
+typedef struct {
+ void *ptr;
+ obj_id_t id;
+ int width, height;
+ int bytes_per_line;
+ int bytes_per_pixel;
+ int bits_per_pixel;
+ color_bits_t red;
+ color_bits_t green;
+ color_bits_t blue;
+ color_bits_t res;
+} fb_t;
+
+typedef struct {
+ int x, y;
+ int width, height;
+} area_t;
+
+typedef struct {
+ int max_width, max_height;
+ int width, height;
+ color_t ptr[];
+} canvas_t; // __attribute__ ((packed))
+
+typedef union {
+ struct {
+ void *ptr;
+ unsigned size;
+ obj_id_t ref_id; // ptr points into this object
+ };
+ int64_t value;
+} data_t;
+
+typedef struct {
+ data_t data; // either pointer+size or int value, see flags.data_is_ptr
+ uint32_t ref_cnt; // reference count
+ uint8_t gen; // generation
+ uint8_t base_type; // base type
+ uint8_t sub_type; // more detailed type, if != 0
+ struct {
+ uint8_t data_is_ptr:1; // type of data element
+ uint8_t ro:1; // object is read-only
+ uint8_t nofree:1; // data.ptr is unmanaged and must not be freed
+ uint8_t fixed:1; // do not relocate
+ uint8_t utf8:1; // data is utf8 encoded
+ } flags;
+} obj_t;
+
+typedef struct {
+ unsigned next;
+ unsigned max;
+ obj_t ptr[];
+} __attribute__ ((packed)) olist_t;
+
+typedef struct {
+ unsigned type;
+ obj_id_t parent_id;
+ obj_id_t data_id;
+ obj_id_t glyph_id;
+ int width, height;
+ int line_height;
+ int baseline;
+ unsigned glyphs;
+ unsigned glyph_size; // bitmap size per glyph in bytes
+ struct {
+ unsigned offset;
+ unsigned size;
+ } bitmap, unimap;
+} font_t;
+
+typedef struct {
+ unsigned size;
+ unsigned max;
+ obj_id_t ptr[];
+} __attribute__ ((packed)) array_t;
+
+typedef struct key_value_s {
+ obj_id_t key;
+ obj_id_t value;
+} key_value_t;
+
+typedef struct {
+ unsigned size;
+ unsigned max;
+ obj_id_t parent_id;
+ key_value_t ptr[];
+} __attribute__ ((packed)) hash_t;
+
+typedef struct {
+ unsigned type;
+ unsigned ip, current_ip;
+ obj_id_t parent_id;
+ obj_id_t code_id;
+ obj_id_t dict_id;
+ obj_id_t iterate_id;
+ int64_t index;
+ int64_t max;
+ int64_t inc;
+} context_t;
+
+typedef struct {
+ area_t region; // drawing area, relative to screen (in pixel)
+ area_t pos; // drawing position (in x, y) and font char size (in width, height)
+ color_t color; // drawing color
+ color_t bg_color; // background color
+ obj_id_t canvas_id;
+ obj_id_t font_id;
+} gstate_t;
+
+typedef struct {
+ context_t *ctx;
+ int64_t arg1;
+ uint8_t *arg2;
+ unsigned type;
+ obj_id_t code_id;
+} decoded_instr_t;
+
+typedef struct {
+ obj_id_t id1, id2;
+} obj_id_pair_t;
+
+typedef struct {
+ struct {
+ fb_t real;
+ obj_id_t virt_id;
+ } screen;
+
+ struct {
+ obj_id_t gstate_id; // debug console gstate
+ } console;
+
+ obj_id_t gstate_id; // normal gstate
+
+ struct {
+ int nested;
+ int entry;
+ int size;
+ gfxboot_menu_t *entries;
+ struct {
+ int max;
+ int current;
+ } timeout;
+ } menu;
+
+ struct {
+ data_t mem; // memory pool
+ struct {
+ olist_t *ptr; // ptr to object list
+ obj_id_t id; // id of object list
+ } olist;
+ obj_id_t gc_list; // list of objects to garbage collect
+ struct {
+ obj_id_t pstack; // program data stack
+ obj_id_t dict; // global dictionary
+ obj_id_t context; // current program context
+ unsigned stop:1;
+ uint64_t time;
+ uint64_t steps;
+ } program;
+ error_t error;
+ struct {
+ struct {
+ char buf[256];
+ unsigned buf_pos;
+ unsigned show:1;
+ } console;
+ int log_level_serial;
+ int log_level_console;
+ unsigned show_pointer:1;
+ unsigned log_prompt:1;
+ unsigned steps;
+ struct {
+ unsigned ip:1;
+ unsigned pstack:1;
+ unsigned context:1;
+ unsigned gc:1;
+ unsigned time:1;
+ } trace;
+ } debug;
+ } vm;
+
+ struct {
+ unsigned port;
+ } serial;
+} gfxboot_data_t;
+
+typedef int (* dump_function_t)(obj_t *ptr, dump_style_t style);
+typedef unsigned (* gc_function_t)(obj_t *ptr);
+typedef int (* contains_function_t)(obj_t *ptr, obj_id_t id);
+typedef unsigned (* iterate_function_t)(obj_t *ptr, unsigned *idx, obj_id_t *id1, obj_id_t *id2);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+extern gfxboot_data_t *gfxboot_data;
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_sys_read_file(char *name, void **buf);
+void gfxboot_sys_free(void *ptr);
+unsigned long gfxboot_sys_strlen(const char *s);
+int gfxboot_sys_strcmp(const char *s1, const char *s2);
+long int gfxboot_sys_strtol(const char *nptr, char **endptr, int base);
+void gfxboot_screen_update(area_t area);
+int gfxboot_getkey(void);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+#define gfxboot_serial(ls, ...) gfxboot_printf((((ls) & 0xff) << 8) + 1, __VA_ARGS__)
+#define gfxboot_console(lc, ...) gfxboot_printf((((lc) & 0xff) << 16) + 2, __VA_ARGS__)
+#define gfxboot_log(...) gfxboot_printf(3, __VA_ARGS__)
+#define gfxboot_debug(ls, lc, ...) gfxboot_printf((((lc) & 0xff) << 16) + (((ls) & 0xff) << 8) + 3, __VA_ARGS__)
+int gfxboot_printf(int dst, const char *format, ...) __attribute__ ((format (printf, 2, 3)));
+int gfxboot_asprintf(char **str, const char *format, ...) __attribute__ ((format (printf, 2, 3)));
+int gfxboot_snprintf(char *str, unsigned size, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_init(void);
+int gfxboot_process_key(unsigned key);
+void gfxboot_timeout(void);
+void gfxboot_debug_command(char *str);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uint32_t gfx_read_le32(const void *p);
+char *gfx_utf8_enc(unsigned uc);
+int gfx_utf8_dec(char **s, unsigned *len);
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_screen_update(area_t area);
+void gfx_console_putc(unsigned c, int update_pos);
+void gfx_console_puts(char *s);
+void gfx_putc(gstate_t *gstate, unsigned c, int update_pos);
+void gfx_puts(gstate_t *gstate, char *s, unsigned len);
+area_t gfx_clip(area_t *area1, area_t *area2);
+void gfx_blt(int mode, obj_id_t dst_id, area_t dst_area, obj_id_t src_id, area_t src_area);
+int gfx_getpixel(gstate_t *gstate, canvas_t *canvas, int x, int y, color_t *color);
+void gfx_putpixel(gstate_t *gstate, canvas_t *canvas, int x, int y, color_t color);
+void gfx_line(gstate_t *gstate, canvas_t *canvas, int x0, int y0, int x1, int y1, color_t color);
+void gfx_rect(gstate_t *gstate, int x, int y, int width, int height, color_t c);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_malloc_init(void);
+void gfx_malloc_dump(dump_style_t style);
+void *gfx_malloc(uint32_t size, uint32_t id);
+void gfx_free(void *ptr);
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_read_file(char *name);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_init(void);
+void gfx_obj_dump(obj_id_t id, dump_style_t style);
+char *gfx_obj_id2str(obj_id_t id);
+
+obj_id_t gfx_obj_new(unsigned type);
+obj_id_t gfx_obj_alloc(unsigned type, uint32_t size);
+obj_id_t gfx_obj_realloc(obj_id_t id, uint32_t size);
+
+obj_id_t gfx_obj_ref_inc(obj_id_t id);
+void gfx_obj_ref_dec(obj_id_t id);
+unsigned gfx_obj_ref_dec_delay_gc(obj_id_t id);
+void gfx_obj_run_gc(void);
+unsigned gfx_obj_iterate(obj_id_t id, unsigned *idx, obj_id_t *id1, obj_id_t *id2);
+
+obj_t *gfx_obj_ptr_nocheck(obj_id_t id);
+obj_t *gfx_obj_ptr(obj_id_t id);
+contains_function_t gfx_obj_contains_function(unsigned type);
+
+obj_id_t gfx_obj_mem_new(uint32_t size);
+data_t *gfx_obj_mem_ptr(obj_id_t id);
+data_t *gfx_obj_mem_subtype_ptr(obj_id_t id, uint8_t subtype);
+int gfx_obj_mem_get(obj_id_t mem_id, int pos);
+obj_id_t gfx_obj_mem_set(obj_id_t mem_id, uint8_t val, int pos);
+int gfx_obj_mem_dump(obj_t *ptr, dump_style_t style);
+unsigned gfx_obj_mem_iterate(obj_t *ptr, unsigned *idx, obj_id_t *id1, obj_id_t *id2);
+obj_id_t gfx_obj_const_mem_nofree_new(const uint8_t *str, unsigned len, uint8_t subtype, obj_id_t ref_id);
+obj_id_t gfx_obj_asciiz_new(const char *str);
+obj_id_t gfx_obj_mem_dup(obj_id_t id, unsigned extra_bytes);
+int gfx_obj_mem_cmp(data_t *mem1, data_t *mem2);
+void gfx_obj_mem_del(obj_id_t mem_id, int pos);
+unsigned gfx_obj_mem_gc(obj_t *ptr);
+int gfx_obj_mem_contains(obj_t *ptr, obj_id_t id);
+
+obj_id_t gfx_obj_olist_new(unsigned max);
+olist_t *gfx_obj_olist_ptr(obj_id_t id);
+int gfx_obj_olist_dump(obj_t *ptr, dump_style_t style);
+
+obj_id_t gfx_obj_font_new(void);
+font_t *gfx_obj_font_ptr(obj_id_t id);
+int gfx_obj_font_dump(obj_t *ptr, dump_style_t style);
+unsigned gfx_obj_font_gc(obj_t *ptr);
+int gfx_obj_font_contains(obj_t *ptr, obj_id_t id);
+obj_id_t gfx_obj_font_open(obj_id_t font_file);
+
+obj_id_t gfx_obj_canvas_new(int width, int height);
+canvas_t *gfx_obj_canvas_ptr(obj_id_t id);
+int gfx_obj_canvas_dump(obj_t *ptr, dump_style_t style);
+int gfx_canvas_adjust_size(canvas_t *c, int width, int height);
+int gfx_canvas_resize(obj_id_t canvas_id, int width, int height);
+
+obj_id_t gfx_obj_array_new(unsigned max);
+array_t *gfx_obj_array_ptr(obj_id_t id);
+unsigned gfx_obj_array_iterate(obj_t *ptr, unsigned *idx, obj_id_t *id1, obj_id_t *id2);
+int gfx_obj_array_dump(obj_t *ptr, dump_style_t style);
+obj_id_t gfx_obj_array_set(obj_id_t array_id, obj_id_t id, int pos, int do_ref_cnt);
+obj_id_t gfx_obj_array_get(obj_id_t array_id, int pos);
+obj_id_t gfx_obj_array_push(obj_id_t array_id, obj_id_t id, int do_ref_cnt);
+obj_id_t gfx_obj_array_pop(obj_id_t array_id, int do_ref_cnt);
+void gfx_obj_array_pop_n(unsigned n, obj_id_t array_id, int do_ref_cnt);
+obj_id_t gfx_obj_array_add(obj_id_t array_id, obj_id_t id, int do_ref_cnt);
+unsigned gfx_obj_array_gc(obj_t *ptr);
+int gfx_obj_array_contains(obj_t *ptr, obj_id_t id);
+void gfx_obj_array_del(obj_id_t array_id, int pos, int do_ref_cnt);
+
+obj_id_t gfx_obj_hash_new(unsigned max);
+hash_t *gfx_obj_hash_ptr(obj_id_t id);
+unsigned gfx_obj_hash_iterate(obj_t *ptr, unsigned *idx, obj_id_t *id1, obj_id_t *id2);
+int gfx_obj_hash_dump(obj_t *ptr, dump_style_t style);
+obj_id_t gfx_obj_hash_set(obj_id_t hash_id, obj_id_t key_id, obj_id_t value_id, int do_ref_cnt);
+obj_id_pair_t gfx_obj_hash_get(obj_id_t hash_id, data_t *key);
+void gfx_obj_hash_del(obj_id_t hash_id, obj_id_t key_id, int do_ref_cnt);
+unsigned gfx_obj_hash_gc(obj_t *ptr);
+int gfx_obj_hash_contains(obj_t *ptr, obj_id_t id);
+
+obj_id_t gfx_obj_context_new(uint8_t sub_type);
+context_t *gfx_obj_context_ptr(obj_id_t id);
+int gfx_obj_context_dump(obj_t *ptr, dump_style_t style);
+unsigned gfx_obj_context_gc(obj_t *ptr);
+int gfx_obj_context_contains(obj_t *ptr, obj_id_t id);
+
+obj_id_t gfx_obj_num_new(int64_t num, uint8_t subtype);
+int64_t *gfx_obj_num_ptr(obj_id_t id);
+int64_t *gfx_obj_num_subtype_ptr(obj_id_t id, uint8_t subtype);
+int gfx_obj_num_dump(obj_t *ptr, dump_style_t style);
+
+obj_id_t gfx_obj_gstate_new(void);
+gstate_t *gfx_obj_gstate_ptr(obj_id_t id);
+int gfx_obj_gstate_dump(obj_t *ptr, dump_style_t style);
+unsigned gfx_obj_gstate_gc(obj_t *ptr);
+int gfx_obj_gstate_contains(obj_t *ptr, obj_id_t id);
+
+unsigned gfx_program_init(obj_id_t program);
+int gfx_decode_instr(decoded_instr_t *code);
+void gfx_program_run(void);
+void gfx_program_debug(unsigned key);
+void gfx_program_debug_on_off(unsigned state);
+void gfx_program_process_key(unsigned key);
+char *gfx_debug_get_ip(void);
+void gfx_debug_cmd(char *str);
+void gfx_debug_show_trace(void);
+void gfx_vm_status_dump(void);
+obj_id_pair_t gfx_lookup_dict(data_t *key);
+int gfx_is_code(obj_id_t id);
+area_t gfx_font_dim(obj_id_t font_id);
+obj_id_t gfx_image_open(obj_id_t image_file);
+
+const char *gfx_error_msg(error_id_t id);
+void gfx_show_error(void);
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// jpeg
+int gfx_jpeg_decode(uint8_t *jpeg, uint8_t *img, int x_0, int x_1, int y_0, int y_1, int color_bits);
+unsigned gfx_jpeg_getsize(uint8_t *buf);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// gfxboot_prim.c
+//
+int gfx_setup_dict(void);
+error_id_t gfx_run_prim(unsigned prim);
+void gfx_exec_id(obj_id_t dict, obj_id_t id, int on_stack);
+void gfx_prim_get_x(data_t *key);
+void gfx_prim_put_x(obj_id_t id);
diff --git a/gfxboot_array.c b/gfxboot_array.c
new file mode 100644
index 0000000..6d1cfbb
--- /dev/null
+++ b/gfxboot_array.c
@@ -0,0 +1,266 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// array
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_array_new(unsigned max)
+{
+ if(!max) max = 0x10;
+
+ obj_id_t id = gfx_obj_alloc(OTYPE_ARRAY, OBJ_ARRAY_SIZE(max));
+ array_t *a = gfx_obj_array_ptr(id);
+
+ if(a) {
+ a->max = max;
+ a->size = 0;
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+array_t *gfx_obj_array_ptr(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_ARRAY) return 0;
+
+ return (array_t *) ptr->data.ptr;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_array_iterate(obj_t *ptr, unsigned *idx, obj_id_t *id1, obj_id_t *id2)
+{
+ array_t *a = ptr->data.ptr;
+
+ if(ptr->data.size != OBJ_ARRAY_SIZE(a->max)) {
+ GFX_ERROR(err_internal);
+ return 0;
+ }
+
+ if(*idx >= a->size) {
+ return 0;
+ }
+
+ gfx_obj_ref_inc(*id1 = a->ptr[*idx]);
+ (*idx)++;
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_array_dump(obj_t *ptr, dump_style_t style)
+{
+ if(!ptr) return 1;
+
+ array_t *a = ptr->data.ptr;
+ unsigned u;
+ obj_id_t id;
+
+ if(ptr->data.size != OBJ_ARRAY_SIZE(a->max)) {
+ gfxboot_log("\n");
+
+ return 1;
+ }
+
+ if(!style.ref) {
+ if(!style.inspect) return 0;
+
+ gfxboot_log("size %u, max %u", a->size, a->max);
+
+ return 1;
+ }
+
+ for(u = 0; u < a->size && (!style.max || u < style.max); u++) {
+ id = a->ptr[u];
+ if(style.dump) gfxboot_log(" ");
+ gfxboot_log("[%2u] ", u);
+ gfx_obj_dump(id, (dump_style_t) { .inspect = style.inspect, .no_nl = 1 });
+ gfxboot_log("\n");
+ }
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_array_set(obj_id_t array_id, obj_id_t id, int pos, int do_ref_cnt)
+{
+ array_t *a = gfx_obj_array_ptr(array_id);
+
+ if(!a) return 0;
+
+ if(pos < 0) pos = (int) a->size + pos;
+
+ if(pos < 0) return 0;
+
+ unsigned upos = (unsigned) pos;
+
+ if(upos >= a->max) {
+ unsigned max = upos + (upos >> 3) + 0x10;
+ array_id = gfx_obj_realloc(array_id, OBJ_ARRAY_SIZE(max));
+ if(!array_id) return 0;
+ a = gfx_obj_array_ptr(array_id);
+ if(!a) return 0;
+ a->max = max;
+ }
+
+ if(upos >= a->size) {
+ unsigned u;
+
+ // clear new entries
+ for(u = a->size; u <= upos; u++) a->ptr[u] = 0;
+ a->size = upos + 1;
+ }
+
+ if(do_ref_cnt) {
+ gfx_obj_ref_inc(id);
+ gfx_obj_ref_dec(a->ptr[upos]);
+ }
+ a->ptr[upos] = id;
+
+ return array_id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_array_get(obj_id_t array_id, int pos)
+{
+ array_t *a = gfx_obj_array_ptr(array_id);
+
+ if(!a) return 0;
+
+ if(pos < 0) pos = (int) a->size + pos;
+
+ if(pos < 0 || pos >= (int) a->size) return 0;
+
+ return a->ptr[pos];
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_obj_array_del(obj_id_t array_id, int pos, int do_ref_cnt)
+{
+ array_t *a = gfx_obj_array_ptr(array_id);
+
+ if(!a) return;
+
+ if(pos < 0) pos = (int) a->size + pos;
+
+ if(pos < 0 || pos >= (int) a->size) return;
+
+ if(do_ref_cnt) {
+ gfx_obj_ref_dec(a->ptr[pos]);
+ }
+
+ a->size--;
+
+ if(a->size > (unsigned) pos) {
+ gfx_memcpy(&a->ptr[pos], &a->ptr[pos + 1], (sizeof *a->ptr) * (a->size - (unsigned) pos));
+ }
+
+ gfx_memset(&a->ptr[a->size], 0, sizeof *a->ptr);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_array_push(obj_id_t array_id, obj_id_t id, int do_ref_cnt)
+{
+ array_t *a = gfx_obj_array_ptr(array_id);
+
+ if(!a) return 0;
+
+ return gfx_obj_array_set(array_id, id, (int) a->size, do_ref_cnt);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_array_pop(obj_id_t array_id, int do_ref_cnt)
+{
+ array_t *a = gfx_obj_array_ptr(array_id);
+
+ if(!a) return 0;
+
+ if(a->size > 0) {
+ a->size--;
+ obj_id_t id = a->ptr[a->size];
+ // do not leave invalid values behind
+ a->ptr[a->size] = 0;
+ if(do_ref_cnt) gfx_obj_ref_dec(id);
+ return id;
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_obj_array_pop_n(unsigned n, obj_id_t array_id, int do_ref_cnt)
+{
+ unsigned u;
+
+ for(u = 0; u < n; u++) {
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_array_add(obj_id_t array_id, obj_id_t id, int do_ref_cnt)
+{
+ array_t *a = gfx_obj_array_ptr(array_id);
+
+ if(!a) return 0;
+
+ unsigned idx;
+
+ for(idx = 0; idx < a->size; idx++) {
+ if(a->ptr[idx] == id) return array_id;
+ }
+
+ return gfx_obj_array_set(array_id, id, (int) a->size, do_ref_cnt);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_array_gc(obj_t *ptr)
+{
+ if(!ptr) return 0;
+
+ array_t *array = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+ unsigned idx, more_gc = 0;
+
+ if(array && data_size == OBJ_ARRAY_SIZE(array->max)) {
+ for(idx = 0; idx < array->size; idx++) {
+ more_gc += gfx_obj_ref_dec_delay_gc(array->ptr[idx]);
+ }
+ }
+
+ return more_gc;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_array_contains(obj_t *ptr, obj_id_t id)
+{
+ if(!ptr || !id) return 0;
+
+ array_t *array = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+ unsigned idx;
+
+ if(array && data_size == OBJ_ARRAY_SIZE(array->max)) {
+ for(idx = 0; idx < array->size; idx++) {
+ if(id == array->ptr[idx]) return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/gfxboot_canvas.c b/gfxboot_canvas.c
new file mode 100644
index 0000000..9c69481
--- /dev/null
+++ b/gfxboot_canvas.c
@@ -0,0 +1,152 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// canvas
+
+static char gfx_canvas_pixel2char(canvas_t *c, int x_blk, int y_blk, int x, int y);
+static uint32_t gfx_canvas_chksum(canvas_t *c);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_canvas_new(int width, int height)
+{
+ obj_id_t id = gfx_obj_alloc(OTYPE_CANVAS, OBJ_CANVAS_SIZE(width, height));
+ canvas_t *c = gfx_obj_canvas_ptr(id);
+
+ if(c) {
+ c->max_width = c->width = width;
+ c->max_height = c->height = height;
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+canvas_t *gfx_obj_canvas_ptr(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_CANVAS) return 0;
+
+ return (canvas_t *) ptr->data.ptr;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_canvas_dump(obj_t *ptr, dump_style_t style)
+{
+ if(!ptr) return 1;
+
+ canvas_t *c = ptr->data.ptr;
+ unsigned len = ptr->data.size;
+ int w, w_max, h, h_max, x_blk, y_blk;
+
+ if(len < OBJ_CANVAS_SIZE(c->width, c->height)) {
+ gfxboot_log(" \n");
+
+ return 1;
+ }
+
+ len -= sizeof (canvas_t);
+
+ x_blk = (c->width + 79) / 80;
+ if(!x_blk) x_blk = 1;
+ w_max = c->width / x_blk;
+
+ y_blk = (c->height + 19) / 20;
+ if(!y_blk) y_blk = 1;
+ h_max = c->height / y_blk;
+
+ if(!style.ref) {
+ if(!style.inspect) return 0;
+
+ gfxboot_log(
+ "size %dx%d, max %dx%d, unit %dx%d, chk 0x%08x",
+ c->width, c->height, c->max_width, c->max_height, x_blk, y_blk, gfx_canvas_chksum(c)
+ );
+
+ return 1;
+ }
+
+ if(style.dump && len) {
+ for(h = 0; h < h_max; h++) {
+ gfxboot_log(" |");
+ for(w = 0; w < w_max; w++) {
+ gfxboot_log("%c", gfx_canvas_pixel2char(c, x_blk, y_blk, w, h));
+ }
+ gfxboot_log("|\n");
+ }
+ }
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char gfx_canvas_pixel2char(canvas_t *c, int x_blk, int y_blk, int x, int y)
+{
+ const char syms[] = " .,:+ox*%#O@"; // symbols for brightness
+ color_t col, *cp = (color_t *) ((uint8_t *) c + sizeof (canvas_t));
+ int i, j;
+ unsigned val = 0;
+
+ x *= x_blk;
+ y *= y_blk;
+
+ cp += x + y * c->width;
+
+ for(j = 0; j < y_blk; j++, cp += c->width) {
+ for(i = 0; i < x_blk; i++) {
+ col = cp[i];
+ val += (col & 0xff) + ((col >> 8) & 0xff) + ((col >> 16) & 0xff);
+ }
+ }
+
+ val /= (unsigned) (x_blk * y_blk * 3 * 255) / (sizeof syms); // yes, size + 1 !!!
+ if(val > sizeof syms - 2) val = sizeof syms - 2;
+
+ return syms[val];
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_canvas_adjust_size(canvas_t *c, int width, int height)
+{
+ if(c->max_width * c->max_height < width * height) return 0;
+
+ c->width = width;
+ c->height = height;
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_canvas_resize(obj_id_t canvas_id, int width, int height)
+{
+ canvas_t *c = gfx_obj_canvas_ptr(canvas_id);
+
+ if(!c) return 0;
+
+ if(c->width == width && c->height == height) return 1;
+
+ return gfx_obj_realloc(canvas_id, OBJ_CANVAS_SIZE(width, height)) ? 1 : 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uint32_t gfx_canvas_chksum(canvas_t *c)
+{
+ unsigned u, len = (unsigned) c->width * (unsigned) c->height;
+ uint32_t sum = 0, a = 0;
+
+ for(u = 0; u < len; u++) {
+ a = a * 73 + 19;
+ sum += a ^ c->ptr[u];
+ }
+
+ return sum;
+}
diff --git a/gfxboot_context.c b/gfxboot_context.c
new file mode 100644
index 0000000..bcb80a6
--- /dev/null
+++ b/gfxboot_context.c
@@ -0,0 +1,127 @@
+#include
+#include
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// context
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_context_new(uint8_t sub_type)
+{
+ obj_id_t id = gfx_obj_alloc(OTYPE_CONTEXT, OBJ_CONTEXT_SIZE());
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(ptr) {
+ ptr->sub_type = sub_type;
+ ((context_t *) ptr->data.ptr)->type = sub_type;
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+context_t *gfx_obj_context_ptr(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_CONTEXT) return 0;
+
+ return (context_t *) ptr->data.ptr;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_context_dump(obj_t *ptr, dump_style_t style)
+{
+ if(!ptr) return 1;
+
+ context_t *context = ptr->data.ptr;
+ unsigned len = ptr->data.size;
+ if(len != OBJ_CONTEXT_SIZE()) {
+ gfxboot_log(" \n");
+
+ return 1;
+ }
+
+ if(!style.ref) {
+ if(!style.inspect) return 0;
+
+ gfxboot_log("code %s, ip 0x%x (0x%x)", gfx_obj_id2str(context->code_id), context->ip, context->current_ip);
+
+ if(context->type == t_ctx_repeat) {
+ gfxboot_log(", index %lld", (long long) context->index);
+ }
+ else if(context->type == t_ctx_for) {
+ gfxboot_log(
+ ", index %lld, inc %lld, max %lld",
+ (long long) context->index,
+ (long long) context->inc,
+ (long long) context->max
+ );
+ }
+ else if(context->type == t_ctx_forall) {
+ gfxboot_log(
+ ", index %lld, iterate %s",
+ (long long) context->index,
+ gfx_obj_id2str(context->iterate_id)
+ );
+ }
+
+ if(context->dict_id) gfxboot_log(", dict %s", gfx_obj_id2str(context->dict_id));
+
+ return 1;
+ }
+
+ if(style.dump) {
+ gfxboot_log(" type %u, ip 0x%x (0x%x)\n", context->type, context->ip, context->current_ip);
+ gfxboot_log(" code %s\n", gfx_obj_id2str(context->code_id));
+ gfxboot_log(" parent %s\n", gfx_obj_id2str(context->parent_id));
+ gfxboot_log(" dict %s\n", gfx_obj_id2str(context->dict_id));
+ gfxboot_log(" iterate %s\n", gfx_obj_id2str(context->iterate_id));
+ }
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_context_gc(obj_t *ptr)
+{
+ if(!ptr) return 0;
+
+ context_t *context = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+ unsigned more_gc = 0;
+
+ if(context && data_size == OBJ_CONTEXT_SIZE()) {
+ more_gc += gfx_obj_ref_dec_delay_gc(context->parent_id);
+ more_gc += gfx_obj_ref_dec_delay_gc(context->code_id);
+ more_gc += gfx_obj_ref_dec_delay_gc(context->dict_id);
+ more_gc += gfx_obj_ref_dec_delay_gc(context->iterate_id);
+ }
+
+ return more_gc;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_context_contains(obj_t *ptr, obj_id_t id)
+{
+ if(!ptr || !id) return 0;
+
+ context_t *context = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+
+ if(context && data_size == OBJ_CONTEXT_SIZE()) {
+ if(
+ id == context->parent_id ||
+ id == context->code_id ||
+ id == context->dict_id ||
+ id == context->iterate_id
+ ) return 1;
+ }
+
+ return 0;
+}
diff --git a/gfxboot_debug.c b/gfxboot_debug.c
new file mode 100644
index 0000000..deab6df
--- /dev/null
+++ b/gfxboot_debug.c
@@ -0,0 +1,820 @@
+#include
+#include
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+static void gfx_program_debug_putc(unsigned c, unsigned cursor);
+static void gfx_status_dump(void);
+static void gfx_stack_dump(dump_style_t style);
+static void gfx_bt_dump(dump_style_t style);
+
+static int is_num(char *str);
+
+static void debug_cmd_dump(int argc, char **argv);
+static void debug_cmd_log(int argc, char **argv);
+static void debug_cmd_hex(int argc, char **argv);
+static void debug_cmd_find(int argc, char **argv);
+static void debug_cmd_run(int argc, char **argv);
+static void debug_cmd_set(int argc, char **argv);
+
+static char *skip_space(char *str);
+static char *skip_nonspace(char *str);
+
+static struct {
+ char *name;
+ void (* function)(int argc, char **argv);
+} debug_cmds[] = {
+ { "d", debug_cmd_dump },
+ { "dump", debug_cmd_dump },
+ { "find", debug_cmd_find },
+ { "hex", debug_cmd_hex },
+ { "i", debug_cmd_dump },
+ { "inspect", debug_cmd_dump },
+ { "log", debug_cmd_log },
+ { "p", debug_cmd_dump },
+ { "print", debug_cmd_dump },
+ { "r", debug_cmd_run },
+ { "run", debug_cmd_run },
+ { "set", debug_cmd_set },
+ { "t", debug_cmd_run },
+ { "trace", debug_cmd_run },
+};
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// debug functions
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *gfx_debug_get_ip()
+{
+ static char buf[64];
+ unsigned code_id = 0;
+ unsigned ip = 0;
+
+ context_t *code_ctx = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+
+ if(code_ctx) {
+ code_id = OBJ_ID2IDX(code_ctx->code_id);
+ ip = gfxboot_data->vm.error.id ? code_ctx->current_ip : code_ctx->ip;
+ }
+
+ gfxboot_snprintf(buf, sizeof buf, "#%u:0x%x", code_id, ip);
+
+ return buf;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_debug_cmd(char *str)
+{
+ char *argv[16] = { };
+ int i, argc = 0;
+ int err = 0;
+
+ if(gfxboot_data->vm.debug.log_prompt) gfxboot_log("%s>%s\n", gfx_debug_get_ip(), str);
+
+ // log comment
+ if(str[0] == '#' && str[1] == ' ') {
+ gfxboot_log("%s\n", str);
+ return;
+ }
+
+ while(argc < (int) (sizeof argv / sizeof *argv) - 1) {
+ str = skip_space(str);
+ if(!*str) break;
+ argv[argc++] = str;
+ str = skip_nonspace(str);
+ if(*str) *str++ = 0;
+ }
+
+ if(!argv[0]) return;
+
+ for(i = 0; i < (int) (sizeof debug_cmds / sizeof *debug_cmds); i++) {
+ if(!gfx_strcmp(argv[0], debug_cmds[i].name)) {
+ debug_cmds[i].function(argc, argv);
+ break;
+ }
+ }
+
+ if(i != sizeof debug_cmds / sizeof *debug_cmds) return;
+
+ for(i = 0; i < argc; i++) {
+ char *s = 0;
+ char *arg = argv[i];
+ unsigned arg_len = gfx_strlen(arg);
+ unsigned is_id = 0;
+
+ if(arg[0] == '#') {
+ is_id = 1;
+ arg++;
+ }
+
+ long val = gfx_strtol(arg, &s, 0);
+
+ if(!gfx_strcmp(arg, "nil")) {
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, 0, 0);
+ }
+ else if(!gfx_strcmp(arg, "true")) {
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(1, t_bool), 0);
+ }
+ else if(!gfx_strcmp(arg, "false")) {
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(0, t_bool), 0);
+ }
+ else if(s && !*s) {
+ if(is_id) {
+ obj_id_t id = 0;
+ obj_t *ptr = gfx_obj_ptr_nocheck((unsigned) val);
+ if(ptr) {
+ id = OBJ_ID(OBJ_ID2IDX(val), ptr->gen);
+ }
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, id, 1);
+ }
+ else {
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(val, t_int), 0);
+ }
+ }
+ else if(*arg == '"') {
+ if(arg_len >= 2 && arg[arg_len - 1] == '"') arg[--arg_len] = 0;
+ arg++;
+ arg_len--;
+ obj_id_t id = gfx_obj_mem_new(arg_len);
+ obj_t *ptr = gfx_obj_ptr(id);
+ if(ptr) {
+ ptr->sub_type = t_string;
+ gfx_memcpy(OBJ_MEM_FROM_PTR(ptr), arg, arg_len);
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, id, 0);
+ }
+ }
+ else if(*argv[i] == '/') {
+ arg++;
+ arg_len--;
+ obj_id_t id = gfx_obj_mem_new(arg_len);
+ obj_t *ptr = gfx_obj_ptr(id);
+ if(ptr) {
+ ptr->sub_type = t_ref;
+ gfx_memcpy(OBJ_MEM_FROM_PTR(ptr), arg, arg_len);
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, id, 0);
+ }
+ }
+ else {
+ data_t key = { .ptr = arg, .size = arg_len };
+ obj_id_t id = gfx_lookup_dict(&key).id2;
+ if(id) {
+ gfxboot_log("%s -> %s\n", arg, gfx_obj_id2str(id));
+ gfx_exec_id(0, id, 0);
+ }
+ else {
+ gfxboot_log("unknown command: %s\n", arg);
+ err = 1;
+ }
+ }
+ }
+
+ if(!err) {
+ gfx_debug_show_trace();
+ gfx_show_error();
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_debug_show_trace()
+{
+ if(gfxboot_data->vm.debug.trace.context) {
+ gfx_bt_dump((dump_style_t) {});
+ }
+
+ if(gfxboot_data->vm.debug.trace.pstack) {
+ gfx_stack_dump((dump_style_t) {});
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_program_debug_on_off(unsigned state)
+{
+ if(gfxboot_data->vm.debug.console.show == state) return;
+
+ gfxboot_data->vm.debug.console.show = state;
+
+ if(gfxboot_data->vm.debug.console.show && state < 2) {
+ // turn on minimal logging
+ if(gfxboot_data->vm.debug.log_level_console < 0) {
+ gfxboot_data->vm.debug.log_level_console = 0;
+ }
+ }
+
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->console.gstate_id);
+
+ if(!gstate) return;
+
+ gfx_rect(
+ gstate,
+ 0,
+ gstate->region.height - gstate->pos.height,
+ gstate->region.width,
+ gstate->pos.height,
+ gstate->bg_color
+ );
+
+ gfxboot_data->vm.debug.console.buf_pos = 0;
+ gfxboot_data->vm.debug.console.buf[0] = 0;
+
+ gstate->pos.x = 0;
+ gstate->pos.y = gstate->region.height - gstate->pos.height;
+
+ if(gfxboot_data->vm.debug.console.show) {
+ char buf[64];
+
+ gfxboot_snprintf(buf, sizeof buf, "%s>", gfx_debug_get_ip());
+
+ gfx_console_puts(buf);
+
+ gfx_console_putc('_', 0);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_program_debug_putc(unsigned c, unsigned cursor)
+{
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->console.gstate_id);
+
+ if(!gstate) return;
+
+ if(c) gfx_console_putc(c, 1);
+
+ if(cursor) gfx_console_putc(cursor, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_program_debug(unsigned key)
+{
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->console.gstate_id);
+
+ unsigned pos = gfxboot_data->vm.debug.console.buf_pos;
+
+ if(key == 0x04) { // ^D
+ gfx_program_debug_on_off(0);
+ return;
+ }
+
+ if(key == 0x0d) {
+ gfx_program_debug_putc(' ', 0);
+ gfx_program_debug_putc(key, 0);
+ gfxboot_data->vm.debug.console.buf[pos] = 0;
+ gfx_debug_cmd(gfxboot_data->vm.debug.console.buf);
+ gfx_program_debug_on_off(3);
+ return;
+ }
+
+ if(key == 0x08) {
+ if(!pos) return;
+ pos--;
+ gfx_program_debug_putc(key, 0);
+ key = 0;
+ }
+
+ if(key && key < ' ') return;
+
+ if(
+ !gstate ||
+ pos >= sizeof gfxboot_data->vm.debug.console.buf - 1 ||
+ gstate->pos.x >= gstate->region.width - gstate->pos.width
+ ) {
+ return;
+ }
+
+ gfx_program_debug_putc(key, '_');
+
+ if(key) {
+ // FIXME: unicode chars!
+ gfxboot_data->vm.debug.console.buf[pos++] = key;
+ }
+
+ gfxboot_data->vm.debug.console.buf_pos = pos;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_program_process_key(unsigned key)
+{
+ gfxboot_debug(2, 2, "gfx_program_process_key: 0x%x\n", key);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_vm_status_dump()
+{
+ int i;
+
+ gfxboot_log("arch bits = %d, gfxboot_data = %p\n", (int) sizeof (void *) * 8, gfxboot_data);
+
+ gfxboot_log(
+ "physical screen:\n fb = %p, size = %d x %d (+%d)\n pixel bytes = %d, bits = %d\n red = %d +%d, green = %d +%d, blue = %d +%d, reserved = %d +%d\n",
+ gfxboot_data->screen.real.ptr,
+ gfxboot_data->screen.real.width,
+ gfxboot_data->screen.real.height,
+ gfxboot_data->screen.real.bytes_per_line,
+ gfxboot_data->screen.real.bytes_per_pixel,
+ gfxboot_data->screen.real.bits_per_pixel,
+ gfxboot_data->screen.real.red.size,
+ gfxboot_data->screen.real.red.pos,
+ gfxboot_data->screen.real.green.size,
+ gfxboot_data->screen.real.green.pos,
+ gfxboot_data->screen.real.blue.size,
+ gfxboot_data->screen.real.blue.pos,
+ gfxboot_data->screen.real.res.size,
+ gfxboot_data->screen.real.res.pos
+ );
+
+ gfxboot_log("virtual screen:\n id = %s\n", gfx_obj_id2str(gfxboot_data->screen.virt_id));
+
+ canvas_t *c = gfx_obj_canvas_ptr(gfxboot_data->screen.virt_id);
+
+ if(c) {
+ gfxboot_log(
+ " fb = %p, size = %d x %d (+%d)\n pixel bytes = %d, bits = %d\n red = %d +%d, green = %d +%d, blue = %d +%d, alpha = %d +%d\n",
+ c->ptr, c->width, c->height, c->width * COLOR_BYTES,
+ COLOR_BYTES, COLOR_BYTES * 8,
+ RED_BITS, RED_POS,
+ GREEN_BITS, GREEN_POS,
+ BLUE_BITS, BLUE_POS,
+ ALPHA_BITS, ALPHA_POS
+ );
+ }
+
+ gfxboot_log(
+ "menu\n default = %d, nested = %d\n size = %d\n",
+ gfxboot_data->menu.entry,
+ gfxboot_data->menu.nested,
+ gfxboot_data->menu.size
+ );
+
+ for(i = 0; i < gfxboot_data->menu.size; i++) {
+ gfxboot_log(" %d: title = \"%s\"\n", i, gfxboot_data->menu.entries[i].title);
+ }
+
+ gfxboot_log(
+ "vm\n size = %d, ptr = %p\n",
+ gfxboot_data->vm.mem.size,
+ gfxboot_data->vm.mem.ptr
+ );
+
+ gfxboot_log(
+ "debug\n log console = %d, log serial = %d, pointer = %s, prompt = %s\n trace =%s%s%s%s%s\n",
+ gfxboot_data->vm.debug.log_level_console,
+ gfxboot_data->vm.debug.log_level_serial,
+ gfxboot_data->vm.debug.show_pointer ? "on" : "off",
+ gfxboot_data->vm.debug.log_prompt ? "on" : "off",
+ gfxboot_data->vm.debug.trace.ip ? " ip" : "",
+ gfxboot_data->vm.debug.trace.pstack ? " stack" : "",
+ gfxboot_data->vm.debug.trace.context ? " context" : "",
+ gfxboot_data->vm.debug.trace.gc ? " gc" : "",
+ gfxboot_data->vm.debug.trace.time ? " time" : ""
+ );
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_status_dump()
+{
+ gstate_t *console_gstate = gfx_obj_gstate_ptr(gfxboot_data->console.gstate_id);
+ gstate_t *gfx_gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ gfxboot_log("graphics screen:\n");
+ if(gfx_gstate) {
+ gfxboot_log(" gstate = ");
+ gfx_obj_dump(gfxboot_data->gstate_id, (dump_style_t) { .inspect = 1 });
+ gfxboot_log(" pos = %dx%d, color #%08x, bg_color #%08x\n",
+ gfx_gstate->pos.x, gfx_gstate->pos.y,
+ gfx_gstate->color, gfx_gstate->bg_color
+ );
+ gfxboot_log(" font = ");
+ gfx_obj_dump(gfx_gstate->font_id, (dump_style_t) { .inspect = 1 });
+ }
+
+ gfxboot_log("text console:\n");
+ if(console_gstate) {
+ gfxboot_log(" gstate = ");
+ gfx_obj_dump(gfxboot_data->console.gstate_id, (dump_style_t) { .inspect = 1 });
+ gfxboot_log(" pos = %dx%d, color #%08x, bg_color #%08x\n",
+ console_gstate->pos.x, console_gstate->pos.y,
+ console_gstate->color, console_gstate->bg_color
+ );
+ gfxboot_log(" font = ");
+ gfx_obj_dump(console_gstate->font_id, (dump_style_t) { .inspect = 1 });
+ }
+
+ gfxboot_log("garbage collector:\n");
+ gfxboot_log(" list = ");
+ gfx_obj_dump(gfxboot_data->vm.gc_list, (dump_style_t) { .inspect = 1 });
+
+ gfxboot_log("program:\n");
+ gfxboot_log(
+ " ip = %s\n",
+ gfx_debug_get_ip()
+ );
+ gfxboot_log(" global dict = ");
+ gfx_obj_dump(gfxboot_data->vm.program.dict, (dump_style_t) { .inspect = 1 });
+ gfxboot_log(" stack = ");
+ gfx_obj_dump(gfxboot_data->vm.program.pstack, (dump_style_t) { .inspect = 1 });
+ gfxboot_log(" context = ");
+ gfx_obj_dump(gfxboot_data->vm.program.context, (dump_style_t) { .inspect = 1 });
+#ifdef FULL_ERROR
+ gfxboot_log(
+ " error %d (%s), src = %s:%d\n",
+ gfxboot_data->vm.error.id,
+ gfx_error_msg(gfxboot_data->vm.error.id),
+ gfxboot_data->vm.error.src_file,
+ gfxboot_data->vm.error.src_line
+ );
+#else
+ gfxboot_log(
+ " error %d (%s)\n",
+ gfxboot_data->vm.error.id,
+ gfx_error_msg(gfxboot_data->vm.error.id)
+ );
+#endif
+
+ uint64_t avg = gfxboot_data->vm.program.steps ? gfxboot_data->vm.program.time / gfxboot_data->vm.program.steps : 0;
+ gfxboot_log(
+ " time = %llu, steps = %llu, avg = %llu/step\n",
+ (unsigned long long) gfxboot_data->vm.program.time,
+ (unsigned long long) gfxboot_data->vm.program.steps,
+ (unsigned long long) avg
+ );
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_stack_dump(dump_style_t style)
+{
+ unsigned u;
+ obj_id_t stack_id = gfxboot_data->vm.program.pstack;
+
+ gfxboot_log("== stack (%s) ==\n", gfx_obj_id2str(stack_id));
+
+ array_t *stack = gfx_obj_array_ptr(stack_id);
+
+ if(!stack) return;
+
+ for(u = 0; u < stack->size && (!style.max || u < style.max); u++) {
+ gfxboot_log(" [%u] ", u);
+ gfx_obj_dump(stack->ptr[stack->size - u - 1], (dump_style_t) { .inspect = 1 });
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_bt_dump(dump_style_t style)
+{
+ unsigned u = 0;
+ obj_id_t context_id = gfxboot_data->vm.program.context;
+ context_t *context;
+
+ gfxboot_log("== backtrace ==\n");
+
+ while((!style.max || u < style.max) && (context = gfx_obj_context_ptr(context_id))) {
+ gfxboot_log(" [%u] ", u);
+ gfx_obj_dump(context_id, (dump_style_t) { .inspect = 1 });
+ u++;
+ context_id = context->parent_id;
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void debug_cmd_dump(int argc, char **argv)
+{
+ dump_style_t style = { };
+
+ if(!argv[1]) return;
+
+ if(!gfx_strcmp(argv[0], "i")) style.inspect =1;
+ if(!gfx_strcmp(argv[0], "d")) style.dump = 1;
+
+ if(*argv[1] == '*') {
+ style.ref = 1;
+ argv[1]++;
+ }
+
+ if(*argv[1] == '#') argv[1]++;
+
+ if(argv[2]) {
+ style.max = (unsigned) gfx_strtol(argv[2], 0, 0);
+ }
+
+ if(!gfx_strcmp(argv[1], "st")) {
+ gfx_status_dump();
+ }
+ else if(!gfx_strcmp(argv[1], "vm")) {
+ gfx_vm_status_dump();
+ }
+ else if(!gfx_strcmp(argv[1], "mem")) {
+ gfx_malloc_dump(style);
+ }
+ else if(!gfx_strcmp(argv[1], "stack")) {
+ gfx_stack_dump(style);
+ }
+ else if(!gfx_strcmp(argv[1], "bt")) {
+ gfx_bt_dump(style);
+ }
+ else {
+ obj_id_t id = 0;
+ int show_id = 1;
+ char *s = 0;
+
+ if(!gfx_strcmp(argv[1], "stack")) {
+ id = gfxboot_data->vm.program.pstack;
+ }
+ else if(!gfx_strcmp(argv[1], "context")) {
+ id = gfxboot_data->vm.program.context;
+ }
+ else if(!gfx_strcmp(argv[1], "dict")) {
+ id = gfxboot_data->vm.program.dict;
+ }
+ else if(!gfx_strcmp(argv[1], "gc")) {
+ id = gfxboot_data->vm.gc_list;
+ }
+ else if(!gfx_strcmp(argv[1], "screen")) {
+ id = gfxboot_data->screen.virt_id;
+ }
+ else if(!gfx_strcmp(argv[1], "gstate")) {
+ id = gfxboot_data->gstate_id;
+ }
+ else if(!gfx_strcmp(argv[1], "consolegstate")) {
+ id = gfxboot_data->console.gstate_id;
+ }
+ else if(!gfx_strcmp(argv[1], "ip")) {
+ gfxboot_log("ip = %s\n", gfx_debug_get_ip());
+ show_id = 0;
+ }
+ else if(!gfx_strcmp(argv[1], "err")) {
+ gfx_show_error();
+ show_id = 0;
+ }
+ else {
+ unsigned idx = (unsigned) gfx_strtol(argv[1], &s, 0);
+ if(*s) return;
+ obj_t *ptr = gfx_obj_ptr_nocheck(idx);
+ if(ptr) {
+ id = OBJ_ID(OBJ_ID2IDX(idx), ptr->gen);
+ }
+ else {
+ show_id = 0;
+ }
+ }
+
+ if(show_id) gfx_obj_dump(id, style);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void debug_cmd_hex(int argc, char **argv)
+{
+ dump_style_t style = { .dump = 1 };
+ int x = 0;
+ unsigned len = 0;
+ obj_t tmp = { };
+
+ if(!argv[1]) return;
+
+ if(*argv[1] == '*') {
+ x = 1;
+ argv[1]++;
+ }
+
+ if(*argv[1] == '#') argv[1]++;
+
+ if(argv[2]) {
+ len = (unsigned) gfx_strtol(argv[2], 0, 0);
+ }
+
+ char *s = 0;
+ obj_id_t idx = (obj_id_t) gfx_strtol(argv[1], &s, 0);
+
+ if(!s || !*s) {
+ obj_t *ptr = gfx_obj_ptr_nocheck(idx);
+ if(ptr) {
+ if(x) {
+ tmp.data.ptr = ptr;
+ tmp.data.size = sizeof *ptr;
+ }
+ else {
+ if(ptr->flags.data_is_ptr) {
+ tmp.data.ptr = ptr->data.ptr;
+ tmp.data.size = ptr->data.size;
+ }
+ else {
+ tmp.data.ptr = &ptr->data.value;
+ tmp.data.size = 8;
+ }
+ }
+ if(len) tmp.data.size = len;
+ gfx_obj_mem_dump(&tmp, style);
+ }
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int is_num(char *str)
+{
+ return str && *str >= '0' && *str <= '9';
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void debug_cmd_log(int argc, char **argv)
+{
+ argc--;
+ argv++;
+
+ while(*argv) {
+ if(!gfx_strcmp(*argv, "serial")) {
+ gfxboot_data->vm.debug.log_level_serial = is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 0;
+ }
+ else if(!gfx_strcmp(*argv, "console")) {
+ gfxboot_data->vm.debug.log_level_console = is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 0;
+ }
+ else if(!gfx_strcmp(*argv, "pointer")) {
+ gfxboot_data->vm.debug.show_pointer = is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 1;
+ }
+ else if(!gfx_strcmp(*argv, "prompt")) {
+ gfxboot_data->vm.debug.log_prompt = is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 1;
+ }
+ else if(!gfx_strcmp(*argv, "ip")) {
+ gfxboot_data->vm.debug.trace.ip = is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 1;
+ }
+ else if(!gfx_strcmp(*argv, "stack")) {
+ gfxboot_data->vm.debug.trace.pstack = is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 1;
+ }
+ else if(!gfx_strcmp(*argv, "context")) {
+ gfxboot_data->vm.debug.trace.context = is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 1;
+ }
+ else if(!gfx_strcmp(*argv, "gc")) {
+ gfxboot_data->vm.debug.trace.gc = is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 1;
+ }
+ else if(!gfx_strcmp(*argv, "time")) {
+ gfxboot_data->vm.debug.trace.time = is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 1;
+ }
+ else if(!gfx_strcmp(*argv, "all")) {
+ gfxboot_data->vm.debug.trace.pstack =
+ gfxboot_data->vm.debug.trace.context =
+ gfxboot_data->vm.debug.trace.ip =
+ gfxboot_data->vm.debug.trace.gc =
+ gfxboot_data->vm.debug.trace.time =
+ gfxboot_data->vm.debug.log_prompt =
+ is_num(argv[1]) ? gfx_strtol(*++argv, 0, 0) : 1;
+ }
+
+ argv++;
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void debug_cmd_run(int argc, char **argv)
+{
+ unsigned steps = 0;
+
+ if(*argv[0] == 't') steps = 1;
+
+ if(argv[1]) steps = (unsigned) gfx_strtol(argv[1], 0, 0);
+
+ gfxboot_data->vm.debug.steps = steps;
+
+ gfx_program_run();
+
+ gfx_show_error();
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void debug_cmd_find(int argc, char **argv)
+{
+ if(argc < 2) return;
+
+ uint32_t idx = (uint32_t) gfx_strtol(argv[1], 0, 0);
+ if(!idx) return;
+
+ obj_t *ptr = gfx_obj_ptr_nocheck(idx);
+ if(!ptr) return;
+
+ obj_id_t id = OBJ_ID(idx, ptr->gen);
+
+ ptr = gfx_obj_ptr(OBJ_ID(0, 1));
+ if(!ptr) return;
+
+ olist_t *olist = ptr->data.ptr;
+ if(ptr->data.size != OBJ_OLIST_SIZE(olist->max)) {
+ return;
+ }
+
+ unsigned u;
+
+ for(u = 0; u < olist->max; u++) {
+ ptr = olist->ptr + u;
+ if(gfx_obj_contains_function(ptr->base_type)(ptr, id)) {
+ gfxboot_log("%s\n", gfx_obj_id2str(OBJ_ID(u, ptr->gen)));
+ }
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void debug_cmd_set(int argc, char **argv)
+{
+ if(argc < 3) return;
+
+ argc--;
+ argv++;
+
+ char *s = 0;
+ unsigned val = (unsigned) gfx_strtol(argv[1], &s, 0);
+
+ if(*s) return;
+
+ obj_id_t id = 0;
+
+ if(val) {
+ obj_t *ptr = gfx_obj_ptr_nocheck(val);
+ if(ptr) {
+ id = OBJ_ID(OBJ_ID2IDX(val), ptr->gen);
+ }
+ }
+
+ if(!gfx_strcmp(argv[0], "stack")) {
+ obj_id_t old = gfxboot_data->vm.program.pstack;
+ gfxboot_data->vm.program.pstack = gfx_obj_ref_inc(id);
+ gfx_obj_ref_dec(old);
+ }
+ else if(!gfx_strcmp(argv[0], "context")) {
+ obj_id_t old = gfxboot_data->vm.program.context;
+ gfxboot_data->vm.program.context = gfx_obj_ref_inc(id);
+ gfx_obj_ref_dec(old);
+ }
+ else if(!gfx_strcmp(argv[0], "dict")) {
+ obj_id_t old = gfxboot_data->vm.program.dict;
+ gfxboot_data->vm.program.dict = gfx_obj_ref_inc(id);
+ gfx_obj_ref_dec(old);
+ }
+ else if(!gfx_strcmp(argv[0], "gc")) {
+ obj_id_t old = gfxboot_data->vm.gc_list;
+ gfxboot_data->vm.gc_list = gfx_obj_ref_inc(id);
+ gfx_obj_ref_dec(old);
+ }
+ else if(!gfx_strcmp(argv[0], "screen")) {
+ obj_id_t old = gfxboot_data->screen.virt_id;
+ gfxboot_data->screen.virt_id = gfx_obj_ref_inc(id);
+ gfx_obj_ref_dec(old);
+ }
+ else if(!gfx_strcmp(argv[0], "gstate")) {
+ obj_id_t old = gfxboot_data->gstate_id;
+ gfxboot_data->gstate_id = gfx_obj_ref_inc(id);
+ gfx_obj_ref_dec(old);
+ }
+ else if(!gfx_strcmp(argv[0], "consolegstate")) {
+ obj_id_t old = gfxboot_data->console.gstate_id;
+ gfxboot_data->console.gstate_id = gfx_obj_ref_inc(id);
+ gfx_obj_ref_dec(old);
+ }
+ else if(!gfx_strcmp(argv[0], "ip")) {
+ context_t *code_ctx = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+ if(code_ctx) {
+ code_ctx->ip = val;
+ }
+ gfxboot_log("ip = %s\n", gfx_debug_get_ip());
+ }
+ else if(!gfx_strcmp(argv[0], "err")) {
+ gfxboot_data->vm.error.id = val;
+ gfx_show_error();
+ }
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *skip_space(char *str)
+{
+ while(*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n') str++;
+
+ return str;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *skip_nonspace(char *str)
+{
+ if(str[0] == '"') {
+ str++;
+ while(*str != 0 && *str != '"') str++;
+ if(*str == '"') str++;
+ }
+ else {
+ while(*str && !(*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n')) str++;
+ }
+
+ return str;
+}
diff --git a/gfxboot_draw.c b/gfxboot_draw.c
new file mode 100644
index 0000000..3925ed9
--- /dev/null
+++ b/gfxboot_draw.c
@@ -0,0 +1,766 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// drawing functions
+//
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static obj_id_t gfx_font_render_glyph(gstate_t *gstate, area_t *geo, unsigned c);
+static obj_id_t gfx_font_render_font1_glyph(gstate_t *gstate, font_t *font, area_t *geo, unsigned c);
+static obj_id_t gfx_font_render_font2_glyph(gstate_t *gstate, font_t *font, area_t *geo, unsigned c);
+static unsigned read_unsigned_bits(uint8_t *buf, unsigned *bit_ofs, unsigned bits);
+static int read_signed_bits(uint8_t *buf, unsigned *bit_ofs, unsigned bits);
+
+static color_t gfx_color_merge(color_t dst, color_t src);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_screen_update(area_t area)
+{
+ int i, j;
+ uint8_t *r8, *rc8;
+ color_t c;
+ uint32_t rc;
+ uint32_t rm, gm, bm;
+ int rs, gs, bs;
+ int rp, gp, bp;
+ int rsize;
+
+ canvas_t *virt_fb = gfx_obj_canvas_ptr(gfxboot_data->screen.virt_id);
+ if(!virt_fb) return;
+
+ color_t *pixel = virt_fb->ptr;
+
+ rm = (1u << gfxboot_data->screen.real.red.size) - 1;
+ gm = (1u << gfxboot_data->screen.real.green.size) - 1;
+ bm = (1u << gfxboot_data->screen.real.blue.size) - 1;
+
+ rs = 24 - gfxboot_data->screen.real.red.size;
+ gs = 16 - gfxboot_data->screen.real.green.size;
+ bs = 8 - gfxboot_data->screen.real.blue.size;
+
+ rp = gfxboot_data->screen.real.red.pos;
+ gp = gfxboot_data->screen.real.green.pos;
+ bp = gfxboot_data->screen.real.blue.pos;
+
+ rsize = gfxboot_data->screen.real.bytes_per_pixel;
+
+#if 0
+ gfxboot_log(
+ "rm = 0x%02x, gm = 0x%02x, bm = 0x%02x, rs = %d, gs = %d, bs = %d, rp = %d, gp = %d, bp = %d\n",
+ rm, gm, bm, rs, gs, bs, rp, gp, bp
+ );
+#endif
+
+ r8 = (uint8_t *) gfxboot_data->screen.real.ptr +
+ area.y * gfxboot_data->screen.real.bytes_per_line +
+ area.x * gfxboot_data->screen.real.bytes_per_pixel;
+
+ pixel += area.y * virt_fb->width + area.x;
+
+ for(j = 0; j < area.height; j++, r8 += gfxboot_data->screen.real.bytes_per_line, pixel += virt_fb->width) {
+ for(rc8 = r8, i = 0; i < area.width; i++) {
+ c = pixel[i];
+ rc = (((c >> rs) & rm) << rp) + (((c >> gs) & gm) << gp) + (((c >> bs) & bm) << bp);
+ switch(rsize) {
+ case 4:
+ *rc8++ = rc;
+ rc >>= 8;
+ case 3:
+ *rc8++ = rc;
+ rc >>= 8;
+ case 2:
+ *rc8++ = rc;
+ rc >>= 8;
+ case 1:
+ *rc8++ = rc;
+ rc >>= 8;
+ }
+ }
+ }
+
+ gfxboot_screen_update(area);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_font_render_glyph(gstate_t *gstate, area_t *geo, unsigned c)
+{
+ font_t *font;
+ obj_id_t font_id, glyph_id = 0;
+
+ while(1) {
+ for(font_id = gstate->font_id; (font = gfx_obj_font_ptr(font_id)); font_id = font->parent_id) {
+ switch(font->type) {
+ case 1:
+ glyph_id = gfx_font_render_font1_glyph(gstate, font, geo, c);
+ break;
+
+ case 2:
+ glyph_id = gfx_font_render_font2_glyph(gstate, font, geo, c);
+ break;
+ }
+ if(glyph_id) return glyph_id;
+ }
+
+ if(c == 0xfffd) return 0;
+
+ c = 0xfffd;
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_font_render_font1_glyph(gstate_t *gstate, font_t *font, area_t *geo, unsigned c)
+{
+ data_t *data = gfx_obj_mem_ptr(font->data_id);
+ if(!data) return 0;
+
+ char *uni = data->ptr + font->unimap.offset;
+ char *uni_end = uni + font->unimap.size;
+
+ int i, j;
+ uint8_t *bitmap = 0;
+ unsigned uni_len = font->unimap.size;
+ unsigned idx;
+
+ for(idx = 0; uni < uni_end; ) {
+ i = gfx_utf8_dec(&uni, &uni_len);
+ if(i == -0xff) {
+ idx++;
+ continue;
+ }
+ if(i == (int) c) {
+ if(idx < font->glyphs) {
+ bitmap = data->ptr + font->bitmap.offset + idx * font->glyph_size;
+ }
+ break;
+ }
+ }
+
+ if(!bitmap) return 0;
+
+ *geo = (area_t) { .x = 0, .y = 0, .width = font->width, .height = 0 };
+
+ canvas_t *glyph = gfx_obj_canvas_ptr(font->glyph_id);
+
+ if(
+ !glyph ||
+ !gfx_canvas_adjust_size(glyph, font->width, font->height)
+ ) {
+ return 0;
+ }
+
+ // gfxboot_log("char 0x%04x, idx %u, bitmap %p\n", c, idx, bitmap);
+
+ // got bitmap, now go for it
+
+ color_t fg = gstate->color;
+ color_t bg = gstate->bg_color;
+
+ color_t *col = glyph->ptr;
+ uint8_t cb;
+
+ for(j = 0; j < font->height; j++) {
+ for(i = 0, cb = *bitmap++; i < font->width; i++) {
+ *col++ = (cb & 0x80) ? fg : bg;
+ cb <<= 1;
+ }
+ }
+
+ return font->glyph_id;
+}
+
+
+#define GRAY_BITS 4
+#define GRAY_BIT_COUNT 3
+#define MAX_GRAY ((1 << GRAY_BITS) - 3)
+#define REP_BG (MAX_GRAY + 1)
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_font_render_font2_glyph(gstate_t *gstate, font_t *font, area_t *geo, unsigned c)
+{
+ data_t *data = gfx_obj_mem_ptr(font->data_id);
+ if(!data) return 0;
+
+ uint8_t *ofs_table = data->ptr + font->unimap.offset;
+ unsigned u, ofs = 0;
+
+ for(u = 0; u < font->glyphs; u++) {
+ unsigned g = gfx_read_le32(ofs_table + 5 * u) & ((1 << 21) - 1);
+ if(g == c) {
+ ofs = gfx_read_le32(ofs_table + 5 * u + 1);
+ ofs >>= 21 - 8;
+ break;
+ }
+ }
+
+ // gfxboot_serial(0, "font2: char %u, ofs %u\n", c, ofs);
+
+ if(
+ u == font->glyphs || ofs < font->bitmap.offset || ofs >= data->size
+ ) {
+ return 0;
+ }
+
+ uint8_t *glyph_data = data->ptr + ofs;
+ unsigned bit_ofs = 0;
+
+ unsigned type = read_unsigned_bits(glyph_data, &bit_ofs, 2);
+
+ if(type != 1) return 0;
+
+ unsigned bits = read_unsigned_bits(glyph_data, &bit_ofs, 3) + 1;
+
+ int bitmap_width = (int) read_unsigned_bits(glyph_data, &bit_ofs, bits);
+ int bitmap_height = (int) read_unsigned_bits(glyph_data, &bit_ofs, bits);
+ int x_ofs = read_signed_bits(glyph_data, &bit_ofs, bits);
+ int y_ofs = read_signed_bits(glyph_data, &bit_ofs, bits);
+ int x_advance = read_signed_bits(glyph_data, &bit_ofs, bits);
+
+#if 0
+ gfxboot_serial(0, "font2: bits %u, bitmap %dx%d, ofs %dx%d, advance %d\n",
+ bits, bitmap_width, bitmap_height, x_ofs, y_ofs, x_advance
+ );
+#endif
+
+ canvas_t *glyph = gfx_obj_canvas_ptr(font->glyph_id);
+
+ if(
+ !glyph ||
+ !gfx_canvas_adjust_size(glyph, bitmap_width, bitmap_height)
+ ) {
+ return 0;
+ }
+
+ // recalculate offset relative to top of glyph
+ int d_y = font->height - font->baseline - y_ofs - bitmap_height;
+
+ *geo = (area_t) { .x = x_ofs, .y = d_y, .width = x_advance, .height = 0 };
+
+ unsigned len = (unsigned) (bitmap_height * bitmap_width);
+
+ static color_t last_fg = COLOR(0, 0, 0, 0);
+ static color_t color_map[MAX_GRAY + 1] = { };
+ static int first_time_ever = 1;
+
+ union {
+ color_t c;
+ struct {
+ uint8_t b, g, r, a;
+ } __attribute__ ((packed));
+ } __attribute__ ((packed)) fg, tmp;
+
+ fg.c = gstate->color;
+
+ // if drawing color changed, recalculate color mapping table
+ if(fg.c != last_fg || first_time_ever) {
+ first_time_ever = 0;
+ last_fg = fg.c;
+
+ for(int i = 0; i <= MAX_GRAY; i++) {
+ tmp.c = fg.c;
+ tmp.a = 255 - i * (255 - fg.a) / MAX_GRAY;
+ color_map[i] = tmp.c;
+ }
+ }
+
+ for(u = 0; u < len;) {
+ unsigned lc = read_unsigned_bits(glyph_data, &bit_ofs, GRAY_BITS);
+ // gfxboot_serial(0, "(%u)", lc);
+ if(lc <= MAX_GRAY) {
+ glyph->ptr[u++] = color_map[lc];
+ continue;
+ }
+ lc = lc == REP_BG ? 0 : MAX_GRAY;
+ unsigned lc_cnt = read_unsigned_bits(glyph_data, &bit_ofs, GRAY_BIT_COUNT) + 3;
+ // gfxboot_serial(0, "(%u)", lc_cnt);
+ while(u < len && lc_cnt--) glyph->ptr[u++] = color_map[lc];
+ }
+
+ return font->glyph_id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_console_putc(unsigned c, int update_pos)
+{
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->console.gstate_id);
+
+ if(!gstate) return;
+
+ switch(c) {
+ case 0x08:
+ if(update_pos) {
+ gfx_putc(gstate, ' ', 0);
+ if(gstate->pos.x >= gstate->pos.width) {
+ gstate->pos.x -= gstate->pos.width;
+ }
+ else {
+ gstate->pos.x = 0;
+ }
+ }
+ break;
+
+ case 0x0a:
+ if(update_pos) {
+ gstate->pos.x = 0;
+ gstate->pos.y += gstate->pos.height;
+ }
+
+ area_t src_area = gstate->region;
+ area_t dst_area = gstate->region;
+
+ dst_area.height = src_area.height -= gstate->pos.height;
+ src_area.y += gstate->pos.height;
+
+ if(gstate->pos.y + gstate->pos.height > gstate->region.height) {
+ gstate->pos.y -= gstate->pos.height;
+
+ // scroll up
+ gfx_blt(0, gstate->canvas_id, dst_area, gstate->canvas_id, src_area);
+
+ gfx_rect(
+ gstate,
+ 0,
+ gstate->region.height - gstate->pos.height,
+ gstate->region.width,
+ gstate->pos.height,
+ gstate->bg_color
+ );
+ }
+ break;
+
+ case 0x0d:
+ if(update_pos) {
+ gstate->pos.x = 0;
+ }
+ break;
+
+ default:
+ gfx_putc(gstate, c, update_pos);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_console_puts(char *s)
+{
+ int c;
+
+ while((c = gfx_utf8_dec(&s, 0))) {
+ gfx_console_putc((unsigned) c, 1);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_putc(gstate_t *gstate, unsigned c, int update_pos)
+{
+ obj_id_t glyph_id;
+ area_t geo;
+
+ if((glyph_id = gfx_font_render_glyph(gstate, &geo, c))) {
+ canvas_t *glyph = gfx_obj_canvas_ptr(glyph_id);
+ if(!glyph) return;
+
+ area_t area = {
+ .x = gstate->region.x + gstate->pos.x + geo.x,
+ .y = gstate->region.y + gstate->pos.y + geo.y,
+ .width = glyph->width,
+ .height = glyph->height
+ };
+
+ area_t glyph_area = {
+ .x = 0,
+ .y = 0,
+ .width = glyph->width,
+ .height = glyph->height
+ };
+
+ area_t diff = gfx_clip(&area, &gstate->region);
+
+ ADD_AREA(glyph_area, diff);
+
+ gfx_blt(1, gstate->canvas_id, area, glyph_id, glyph_area);
+
+ if(update_pos) gstate->pos.x += geo.width;
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_puts(gstate_t *gstate, char *s, unsigned len)
+{
+ int c;
+ int start = gstate->pos.x;
+
+ while(len) {
+ c = gfx_utf8_dec(&s, &len);
+ if(c < 0) c = 0xfffd;
+
+ switch(c) {
+ case 0x0a:
+ gstate->pos.y += gstate->pos.height;
+
+ case 0x0d:
+ gstate->pos.x = start;
+ break;
+
+ default:
+ gfx_putc(gstate, (unsigned) c, 1);
+ }
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// clip area1 to difference of area1 and area2
+//
+// return difference (diff = area1_clipped - area1_unclipped)
+//
+area_t gfx_clip(area_t *area1, area_t *area2)
+{
+ area_t diff = {};
+ int d;
+
+ d = area1->x - area2->x;
+
+ if(d < 0) {
+ diff.x = -d;
+ diff.width = d;
+ }
+
+ d = area1->y - area2->y;
+
+ if(d < 0) {
+ diff.y = -d;
+ diff.height = d;
+ }
+
+ d = area2->x + area2->width - (area1->x + area1->width);
+
+ if(d < 0) diff.width += d;
+
+ d = area2->y + area2->height - (area1->y + area1->height);
+
+ if(d < 0) diff.height += d;
+
+ area1->x += diff.x;
+ area1->y += diff.y;
+ area1->width += diff.width;
+ area1->height += diff.height;
+
+ if(area1->width < 0) {
+ diff.width = -area1->width;
+ area1->width = 0;
+ }
+
+ if(area1->height < 0) {
+ diff.height = -area1->height;
+ area1->height = 0;
+ }
+
+ return diff;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_blt(int mode, obj_id_t dst_id, area_t dst_area, obj_id_t src_id, area_t src_area)
+{
+ canvas_t *dst_c = gfx_obj_canvas_ptr(dst_id);
+ canvas_t *src_c = gfx_obj_canvas_ptr(src_id);
+
+ gfxboot_serial(4, "dst #%d (%dx%d_%dx%d), src #%d (%dx%d_%dx%d)\n",
+ OBJ_ID2IDX(dst_id),
+ dst_area.x, dst_area.y, dst_area.width, dst_area.height,
+ OBJ_ID2IDX(src_id),
+ src_area.x, src_area.y, src_area.width, src_area.height
+ );
+
+ if(!dst_c || !src_c) return;
+
+ dst_area.width = MIN(src_area.width, dst_area.width);
+ dst_area.height = MIN(src_area.height, dst_area.height);
+
+#if 0
+ // additional clipping needed?
+ area_t tmp_area, diff;
+
+ tmp_area = (area_t) { .width = dst_c->width, .height = dst_c->height };
+
+ diff = gfx_clip(&dst_area, &tmp_area);
+
+ ADD_AREA(src_area, diff);
+
+ tmp_area = (area_t) { .width = src_c->width, .height = src_c->height };
+
+ diff = gfx_clip(&src_area, &tmp_area);
+
+ ADD_AREA(dst_area, diff);
+#endif
+
+ if(dst_area.width <= 0 || dst_area.height <= 0) return;
+
+ color_t *dst_pixel = dst_c->ptr;
+ color_t *src_pixel = src_c->ptr;
+
+ dst_pixel += dst_area.y * dst_c->width + dst_area.x;
+ src_pixel += src_area.y * src_c->width + src_area.x;
+
+ if(mode == 0) {
+ int i;
+ for(i = 0; i < dst_area.height; i++, dst_pixel += dst_c->width, src_pixel += src_c->width) {
+ gfx_memcpy(dst_pixel, src_pixel, (unsigned) dst_area.width * COLOR_BYTES);
+ }
+ }
+ else if(mode == 1) {
+ int i, j;
+ for(j = 0; j < dst_area.height; j++, dst_pixel += dst_c->width, src_pixel += src_c->width) {
+ for(i = 0; i < dst_area.width; i++) {
+ dst_pixel[i] = gfx_color_merge(dst_pixel[i], src_pixel[i]);
+ }
+ }
+ }
+
+ if(dst_id == gfxboot_data->screen.virt_id) {
+ gfx_screen_update(dst_area);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+color_t gfx_color_merge(color_t dst, color_t src)
+{
+ union {
+ color_t c;
+ struct {
+ uint8_t b, g, r, a;
+ } __attribute__ ((packed));
+ } __attribute__ ((packed)) d, s;
+
+ d.c = dst;
+ s.c = src;
+
+ if(s.a == 0xff) return d.c;
+ if(s.a == 0) {
+ s.a = d.a;
+ return s.c;
+ }
+
+ s.b += (((int) d.b - s.b + 1) * s.a) >> 8;
+ s.g += (((int) d.g - s.g + 1) * s.a) >> 8;
+ s.r += (((int) d.r - s.r + 1) * s.a) >> 8;
+
+#if 0
+ // slightly more correct would be
+ s.b += (((int) d.b - s.b + (d.b > s.b ? 1 : 0)) * s.a) >> 8;
+ s.g += (((int) d.g - s.g + (d.g > s.g ? 1 : 0)) * s.a) >> 8;
+ s.r += (((int) d.r - s.r + (d.r > s.r ? 1 : 0)) * s.a) >> 8;
+#endif
+
+ s.a = d.a;
+
+ return s.c;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_getpixel(gstate_t *gstate, canvas_t *canvas, int x, int y, color_t *color)
+{
+ int ok = 0;
+
+ if(x >= 0 && y >= 0 && x < gstate->region.width && y < gstate->region.height) {
+ x += gstate->region.x;
+ y += gstate->region.y;
+ if(x >= 0 && y >= 0 && x < canvas->width && y < canvas->height) {
+ *color = canvas->ptr[x + y * canvas->width];
+ ok = 1;
+ }
+ }
+
+ return ok;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_putpixel(gstate_t *gstate, canvas_t *canvas, int x, int y, color_t color)
+{
+ // gfxboot_serial(0, "X putpixel %dx%d\n", x, y);
+
+ if(x >= 0 && y >= 0 && x < gstate->region.width && y < gstate->region.height) {
+ x += gstate->region.x;
+ y += gstate->region.y;
+ if(x >= 0 && y >= 0 && x < canvas->width && y < canvas->height) {
+ int ofs = x + y * canvas->width;
+ canvas->ptr[ofs] = gfx_color_merge(canvas->ptr[ofs], color);
+ if(gstate->canvas_id == gfxboot_data->screen.virt_id) {
+ gfx_screen_update((area_t) { .x = x, .y = y, .width = 1, .height = 1 });
+ }
+ }
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_line(gstate_t *gstate, canvas_t *canvas, int x0, int y0, int x1, int y1, color_t color)
+{
+ if(x1 < x0) {
+ int tmp;
+ tmp = x0; x0 = x1; x1 = tmp;
+ tmp = y0; y0 = y1; y1 = tmp;
+ }
+
+ int dx = x1 - x0;
+ int dy = y1 - y0;
+ int acc;
+
+ // shortcut for horizontal and vertical lines
+ if(dy == 0 || dx == 0) {
+ if(dy < 0) {
+ int tmp = y0; y0 = y1; y1 = tmp;
+ dy = -dy;
+ }
+ if(dx) {
+ gfx_rect(gstate, x0, y0, dx + 1, 1, color);
+ }
+ else if(dy) {
+ gfx_rect(gstate, x0, y0, 1, dy + 1, color);
+ }
+
+ return;
+ }
+
+ if(dy >= 0) {
+ if(dy <= dx) {
+ for(acc = -(dx / 2); x0 <= x1; x0++) {
+ gfx_putpixel(gstate, canvas, x0, y0, color);
+ acc += dy;
+ if(acc >= 0) {
+ acc -= dx;
+ y0++;
+ }
+ }
+ }
+ else {
+ for(acc = -(dy / 2); y0 <= y1; y0++) {
+ gfx_putpixel(gstate, canvas, x0, y0, color);
+ acc += dx;
+ if(acc >= 0) {
+ acc -= dy;
+ x0++;
+ }
+ }
+ }
+ }
+ else {
+ dy = -dy;
+ if(dy <= dx) {
+ for(acc = -(dx / 2); x0 <= x1; x0++) {
+ gfx_putpixel(gstate, canvas, x0, y0, color);
+ acc += dy;
+ if(acc >= 0) {
+ acc -= dx;
+ y0--;
+ }
+ }
+ }
+ else {
+ for(acc = -(dy / 2); y0 >= y1; y0--) {
+ gfx_putpixel(gstate, canvas, x0, y0, color);
+ acc += dx;
+ if(acc >= 0) {
+ acc -= dy;
+ x0++;
+ }
+ }
+ }
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_rect(gstate_t *gstate, int x, int y, int width, int height, color_t c)
+{
+ canvas_t *canvas = gfx_obj_canvas_ptr(gstate->canvas_id);
+
+ if(!canvas) return;
+
+ area_t area = {
+ .x = gstate->region.x + x,
+ .y = gstate->region.y + y,
+ .width = width,
+ .height = height
+ };
+
+#if 0
+ gfxboot_serial(0, "rect before: %dx%d_%dx%d / %dx%d_%dx%d\n",
+ area.x, area.y, area.width, area.height,
+ gstate->region.x, gstate->region.y, gstate->region.width, gstate->region.height
+ );
+#endif
+
+ gfx_clip(&area, &gstate->region);
+
+#if 0
+ gfxboot_serial(0, "rect after: %dx%d_%dx%d\n",
+ area.x, area.y, area.width, area.height
+ );
+#endif
+
+ color_t *pixel = canvas->ptr;
+
+ pixel += area.y * canvas->width + area.x;
+
+ int i, j;
+
+ for(j = 0; j < area.height; j++, pixel += canvas->width) {
+ for(i = 0; i < area.width; i++) {
+ pixel[i] = gfx_color_merge(pixel[i], c);
+ }
+ }
+
+ if(gstate->canvas_id == gfxboot_data->screen.virt_id) {
+ gfx_screen_update(area);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned read_unsigned_bits(uint8_t *buf, unsigned *bit_ofs, unsigned bits)
+{
+ unsigned rem, ptr;
+ unsigned data = 0, dptr = 0;
+
+ while(bits > 0) {
+ ptr = *bit_ofs >> 3;
+ rem = 8 - (*bit_ofs & 7);
+ if(rem > bits) rem = bits;
+ data += (((unsigned) buf[ptr] >> (*bit_ofs & 7)) & ((1u << rem) - 1)) << dptr;
+ dptr += rem;
+ *bit_ofs += rem;
+ bits -= rem;
+ }
+
+ return data;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int read_signed_bits(uint8_t *buf, unsigned *bit_ofs, unsigned bits)
+{
+ int i;
+
+ i = (int) read_unsigned_bits(buf, bit_ofs, bits);
+
+ if(bits == 0) return i;
+
+ if((i & (1 << (bits - 1)))) {
+ i += -1 << bits;
+ }
+
+ return i;
+}
diff --git a/gfxboot_font.c b/gfxboot_font.c
new file mode 100644
index 0000000..8213c82
--- /dev/null
+++ b/gfxboot_font.c
@@ -0,0 +1,199 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// font
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_font_new()
+{
+ return gfx_obj_alloc(OTYPE_FONT, OBJ_FONT_SIZE());
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+font_t *gfx_obj_font_ptr(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_FONT) return 0;
+
+ return (font_t *) ptr->data.ptr;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_font_dump(obj_t *ptr, dump_style_t style)
+{
+ if(!ptr) return 1;
+
+ font_t *f = ptr->data.ptr;
+ unsigned len = ptr->data.size;
+ if(len != OBJ_FONT_SIZE()) {
+ gfxboot_log(" \n");
+
+ return 1;
+ }
+
+ if(!style.ref) {
+ if(!style.inspect) return 0;
+
+ if(f->type == 1) {
+ gfxboot_log("glyphs %d, size %dx%d, line height %d", f->glyphs, f->width, f->height, f->line_height);
+ }
+ else if(f->type == 2) {
+ gfxboot_log("glyphs %d, height %d, line height %d, base %d", f->glyphs, f->height, f->line_height, f->baseline);
+ }
+ if(f->parent_id) gfxboot_log(", parent %s", gfx_obj_id2str(f->parent_id));
+
+ return 1;
+ }
+
+ if(style.dump) {
+ canvas_t *glyph = gfx_obj_canvas_ptr(f->glyph_id);
+ gfxboot_log(" type %u, glyphs %d\n", f->type, f->glyphs);
+ gfxboot_log(" font size %dx%d, line height %d, baseline %d\n", f->width, f->height, f->line_height, f->baseline);
+ if(glyph) {
+ gfxboot_log(" bitmap size %dx%d\n", glyph->max_width, glyph->max_height);
+ }
+ gfxboot_log(" bitmap table: offset %u, size %u\n", f->bitmap.offset, f->bitmap.size);
+ gfxboot_log(" char index: offset %u, size %u\n", f->unimap.offset, f->unimap.size);
+ gfxboot_log(" data_id %s\n", gfx_obj_id2str(f->data_id));
+ gfxboot_log(" glyph_id %s\n", gfx_obj_id2str(f->glyph_id));
+ }
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_font_gc(obj_t *ptr)
+{
+ if(!ptr) return 0;
+
+ font_t *font = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+ unsigned more_gc = 0;
+
+ if(font && data_size == OBJ_FONT_SIZE()) {
+ more_gc += gfx_obj_ref_dec_delay_gc(font->parent_id);
+ more_gc += gfx_obj_ref_dec_delay_gc(font->data_id);
+ more_gc += gfx_obj_ref_dec_delay_gc(font->glyph_id);
+ }
+
+ return more_gc;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_font_contains(obj_t *ptr, obj_id_t id)
+{
+ if(!ptr || !id) return 0;
+
+ font_t *font = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+
+ if(font && data_size == OBJ_FONT_SIZE()) {
+ if(id == font->parent_id || id == font->data_id || id == font->glyph_id) return 1;
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_font_open(obj_id_t font_file)
+{
+ data_t *mem = gfx_obj_mem_ptr(font_file);
+ unsigned ok = 0;
+
+ if(!mem) return 0;
+
+ obj_id_t font_id = gfx_obj_font_new();
+
+ font_t *font = gfx_obj_font_ptr(font_id);
+
+ if(!font) return 0;
+
+ font->data_id = font_file;
+
+ uint8_t *ptr = mem->ptr;
+ unsigned size = mem->size;
+
+ int max_bitmap_width = 0;
+ int max_bitmap_height = 0;
+
+ if(
+ size >= 0x20 &&
+ gfx_read_le32(ptr) == 0x864ab572
+ ) {
+ unsigned header_size = gfx_read_le32(ptr + 8);
+
+ font->glyphs = gfx_read_le32(ptr + 16);
+ font->glyph_size = gfx_read_le32(ptr + 20);
+ font->height = (int) gfx_read_le32(ptr + 24);
+ font->width = (int) gfx_read_le32(ptr + 28);
+ font->line_height = font->height;
+
+ font->bitmap.offset = header_size;
+ font->bitmap.size = font->glyphs * font->glyph_size;
+
+ font->unimap.offset = font->bitmap.offset + font->bitmap.size;
+ font->unimap.size = size - font->unimap.offset;
+
+ max_bitmap_width = font->width;
+ max_bitmap_height = font->height;
+
+ if(
+ font->height == (int) font->glyph_size &&
+ font->width <= 8 &&
+ font->unimap.size > 0
+ ) {
+ font->type = 1;
+ ok = 1;
+ }
+ }
+ else if(
+ size >= 0x10 &&
+ gfx_read_le32(ptr) == 0xa42a9123
+ ) {
+ unsigned header_size = 0x10;
+
+ font->glyphs = gfx_read_le32(ptr + 12);
+ max_bitmap_width = ptr[7];
+ max_bitmap_height = ptr[8];
+ font->height = ptr[9];
+ font->line_height = ptr[10];
+ font->baseline = (int8_t) ptr[11];
+ font->glyphs = gfx_read_le32(ptr + 12);
+
+ font->unimap.offset = header_size;
+ font->unimap.size = 5 * font->glyphs;
+
+ font->bitmap.offset = font->unimap.offset + font->unimap.size;
+ font->bitmap.size = size - font->bitmap.offset;
+
+ if(font->bitmap.size > 0) {
+ font->type = 2;
+ ok = 1;
+ }
+ }
+
+ if(ok) {
+ font->glyph_id = gfx_obj_canvas_new(max_bitmap_width, max_bitmap_height);
+ if(!font->glyph_id) ok = 0;
+ }
+
+ if(ok) {
+ gfx_obj_ref_inc(font->data_id);
+ }
+ else {
+ gfx_obj_ref_dec(font->glyph_id);
+ gfx_obj_ref_dec(font_id);
+ font_id = 0;
+ }
+
+ return font_id;
+}
diff --git a/gfxboot_grub.c b/gfxboot_grub.c
new file mode 100644
index 0000000..31fcbd0
--- /dev/null
+++ b/gfxboot_grub.c
@@ -0,0 +1,511 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+gfxboot_data_t *gfxboot_data;
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+static void serial_init(void);
+static void serial_putc(int key);
+
+static void grub_gfxboot_page_0(void);
+static grub_err_t grub_gfxboot_init(int entry, grub_menu_t menu, int nested);
+static int grub_gfxboot_process_key(int *key);
+static void grub_gfxboot_set_chosen_entry(int entry, void *data);
+static void grub_gfxboot_print_timeout(int timeout, void *data);
+static void grub_gfxboot_clear_timeout(void *data);
+static void grub_gfxboot_data_free(void);
+static void grub_gfxboot_fini(void *data);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void serial_init()
+{
+ int x;
+ unsigned port = 0x3f8;
+
+ gfxboot_data->serial.port = port;
+
+ // DLAB = 1
+ grub_outb(0x83, port + 3);
+
+ // set to 115200 baud
+ grub_outb(1, port + 0);
+ grub_outb(0, port + 1);
+
+ // DLAB = 0, 8 bits, no parity
+ grub_outb(0x03, port + 3);
+
+ x = grub_inb(port + 3);
+
+ // gfxboot_log("x = 0x%02x\n", x);
+
+ if(x == 0xff) {
+ gfxboot_data->serial.port = 0;
+ return;
+ }
+
+ // IRQ disable
+ grub_outb(0x00, port + 1);
+
+ // FIFO enable
+ grub_outb(0x01, port + 2);
+
+ // set terminal colors to normal, looks better
+ gfxboot_log("\033[00m");
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void serial_putc(int key)
+{
+ unsigned cnt = 1 << 20;
+
+ if(!gfxboot_data || !gfxboot_data->serial.port) return;
+
+ // wait until ready, but not indefinitely...
+ while(!(grub_inb(gfxboot_data->serial.port + 5) & 0x20) && cnt--);
+
+ grub_outb(key, gfxboot_data->serial.port);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Print to serial line and/or console.
+//
+// dst - bitmask; bit 0: serial, bit 1: console
+//
+int gfxboot_printf(int dst, const char *format, ...)
+{
+ char *s;
+ int log_level_serial = (signed char) ((dst >> 8) & 0xff);
+ int log_level_console = (signed char) ((dst >> 16) & 0xff);
+
+ if(!format) return 0;
+
+ va_list args;
+ va_start(args, format);
+ s = grub_xvasprintf(format, args);
+ va_end(args);
+
+ if(
+ (dst & 1) &&
+ gfxboot_data &&
+ log_level_serial <= gfxboot_data->vm.debug.log_level_serial
+ ) {
+ char *t = s;
+ while(*t) {
+ if(*t == '\n') serial_putc('\r');
+ serial_putc(*t++);
+ }
+ }
+
+ if(
+ (dst & 2) &&
+ gfxboot_data &&
+ log_level_console <= gfxboot_data->vm.debug.log_level_console
+ ) {
+ char *t = s;
+ while(*t) {
+ if(*t == '\n') gfx_console_putc('\r', 1);
+ gfx_console_putc(*t++, 1);
+ }
+ }
+
+ grub_free(s);
+
+ return grub_strlen(s);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Print to buffer.
+//
+int gfxboot_asprintf(char **str, const char *format, ...)
+{
+ char *s;
+
+ va_list args;
+ va_start(args, format);
+ *str = s = grub_xvasprintf(format, args);
+ va_end(args);
+
+ return s ? (int) grub_strlen(s) : -1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Print to static buffer.
+//
+int gfxboot_snprintf(char *str, unsigned size, const char *format, ...)
+{
+ int len;
+
+ va_list args;
+ va_start(args, format);
+ len = grub_vsnprintf(str, size, format, args);
+ va_end(args);
+
+ return len;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void grub_gfxboot_page_0()
+{
+ void *fb;
+
+ grub_video_get_raw_info(NULL, &fb);
+
+ if(fb != gfxboot_data->screen.real.ptr) grub_video_swap_buffers();
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+grub_err_t grub_gfxboot_init(int entry, grub_menu_t menu, int nested)
+{
+ struct grub_video_mode_info mode_info;
+ struct grub_menu_viewer *instance;
+ grub_err_t err = GRUB_ERR_NONE;
+ void *fb0 = NULL, *fb1 = NULL;
+
+ int i;
+
+ grub_gfxboot_data_free();
+
+ gfxboot_data = grub_zalloc(sizeof *gfxboot_data);
+ if(!gfxboot_data) return grub_errno;
+
+ instance = grub_zalloc(sizeof *instance);
+ if(!instance) return grub_errno;
+
+ // don't log to console
+ gfxboot_data->vm.debug.log_level_console = -1;
+
+ gfxboot_data->menu.entry = entry;
+ gfxboot_data->menu.nested = nested;
+ gfxboot_data->menu.timeout.max = grub_menu_get_timeout();
+
+ if(menu->size > 0) {
+ grub_menu_entry_t me;
+
+ gfxboot_data->menu.size = menu->size;
+ gfxboot_data->menu.entries = grub_zalloc(menu->size * sizeof *gfxboot_data->menu.entries);
+ if(!gfxboot_data->menu.entries) return grub_errno;
+
+ for(i = 0, me = menu->entry_list; me && i < menu->size; me = me->next, i++) {
+ gfxboot_data->menu.entries[i].title = me->title;
+ }
+ }
+
+ err = grub_video_get_raw_info(&mode_info, &fb0);
+
+ if(err) return err;
+
+ grub_video_swap_buffers();
+
+ grub_video_get_raw_info(NULL, &fb1);
+
+ if(fb1 == NULL || fb0 <= fb1) {
+ grub_video_swap_buffers();
+ gfxboot_data->screen.real.ptr = fb0;
+ }
+ else {
+ gfxboot_data->screen.real.ptr = fb1;
+ }
+
+ if(!gfxboot_data->screen.real.ptr) return GRUB_ERR_BAD_DEVICE;
+
+ gfxboot_data->screen.real.width = mode_info.width;
+ gfxboot_data->screen.real.height = mode_info.height;
+ gfxboot_data->screen.real.bytes_per_line = mode_info.pitch;
+ gfxboot_data->screen.real.bytes_per_pixel = mode_info.bytes_per_pixel;
+ gfxboot_data->screen.real.bits_per_pixel = mode_info.bpp;
+
+ gfxboot_data->screen.real.red.pos = mode_info.red_field_pos;
+ gfxboot_data->screen.real.red.size = mode_info.red_mask_size;
+ gfxboot_data->screen.real.green.pos = mode_info.green_field_pos;
+ gfxboot_data->screen.real.green.size = mode_info.green_mask_size;
+ gfxboot_data->screen.real.blue.pos = mode_info.blue_field_pos;
+ gfxboot_data->screen.real.blue.size = mode_info.blue_mask_size;
+ gfxboot_data->screen.real.res.pos = mode_info.reserved_field_pos;
+ gfxboot_data->screen.real.res.size = mode_info.reserved_mask_size;
+
+ grub_video_set_viewport(0, 0, gfxboot_data->screen.real.width, gfxboot_data->screen.real.height);
+
+#if 0
+ // setup a commandline terminal window
+ // cf. gfxmenu/init_terminal()
+ grub_gfxterm_set_window(
+ GRUB_VIDEO_RENDER_TARGET_DISPLAY,
+ 100, 100,
+ 300, 200,
+ 0,
+ grub_font_get(""),
+ 0
+ );
+#endif
+
+ instance->data = NULL;
+ instance->set_chosen_entry = grub_gfxboot_set_chosen_entry;
+ instance->print_timeout = grub_gfxboot_print_timeout;
+ instance->clear_timeout = grub_gfxboot_clear_timeout;
+ instance->process_key = grub_gfxboot_process_key;
+ instance->fini = grub_gfxboot_fini;
+
+ serial_init();
+
+ // reserve 16 MiB for our VM
+ gfxboot_data->vm.mem.size = 16 * (1 << 20);
+ gfxboot_data->vm.mem.ptr = grub_zalloc(gfxboot_data->vm.mem.size);
+ if(!gfxboot_data->vm.mem.ptr) return grub_errno;
+
+ if(grub_video_adapter_active) {
+ gfxboot_log("adapter name: %s\n", grub_video_adapter_active->name);
+ }
+
+ gfxboot_log(
+ "video mode info:\n type = 0x%x\n fb0 = %p, fb1 = %p\n",
+ mode_info.mode_type, fb0, fb1
+ );
+
+ if(gfxboot_init()) {
+ err = GRUB_ERR_MENU;
+ }
+ else {
+ grub_menu_register_viewer(instance);
+ }
+
+ return err;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int grub_gfxboot_process_key(int *key)
+{
+ int action;
+ int key2;
+
+ grub_gfxboot_page_0();
+
+ if(!gfxboot_data || !key) return 0;
+
+ key2 = *key;
+
+ if((key2 & GRUB_TERM_CTRL)) {
+ key2 &= 0x1f;
+ }
+
+ gfxboot_debug(2, 2, "grub_gfxboot_process_key: key = 0x%x '%c'\n", key2, key2 >= ' ' ? key2 : ' ');
+
+ if(gfxboot_data->menu.timeout.current > 0) {
+ grub_env_unset("timeout");
+ grub_env_unset("fallback");
+ grub_gfxboot_clear_timeout(NULL);
+ }
+
+ action = gfxboot_process_key(key2);
+
+ gfxboot_debug(1, 2, "grub_gfxboot_process_key: action = %d.0x%02x\n", action >> 8, action & 0xff);
+
+ *key = 0;
+
+ return action;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void grub_gfxboot_set_chosen_entry(int entry, void *data __attribute__ ((unused)))
+{
+ gfxboot_data->menu.entry = entry;
+
+ gfxboot_log("set_chosen_entry: %d\n", entry);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void grub_gfxboot_print_timeout(int timeout, void *data __attribute__ ((unused)))
+{
+ gfxboot_data->menu.timeout.current = timeout;
+
+ gfxboot_timeout();
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void grub_gfxboot_clear_timeout(void *data __attribute__ ((unused)))
+{
+ gfxboot_data->menu.timeout.current = 0;
+
+ gfxboot_timeout();
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_sys_read_file(char *name, void **buf)
+{
+ int size = -1;
+ char *tmp = NULL;
+
+ *buf = NULL;
+
+ grub_errno = 0;
+
+ if(!name) return size;
+
+ if(*name != '/' && *name != '(') {
+ const char *prefix = grub_env_get("prefix");
+ if(!prefix) prefix = "";
+
+ int prefix_len = grub_strlen(prefix);
+ int name_len = grub_strlen(name);
+
+ tmp = grub_zalloc(prefix_len + name_len + 2);
+
+ if(!tmp) return size;
+
+ gfx_memcpy(tmp, prefix, prefix_len);
+ gfx_memcpy(tmp + prefix_len, "/", 1);
+ gfx_memcpy(tmp + prefix_len + 1, name, name_len);
+
+ name = tmp;
+ }
+
+ grub_file_t file = grub_file_open(name, GRUB_FILE_TYPE_NONE);
+
+ gfxboot_log("open(%s) = %p\n", name, file);
+
+ if(file) {
+ if(file->size == 0) {
+ size = 0;
+ }
+ else if(file->size > 0) {
+ *buf = grub_zalloc(file->size);
+ if(*buf) {
+ size = grub_file_read(file, *buf, file->size);
+ if(size != (int) file->size) {
+ grub_free(*buf);
+ *buf = NULL;
+ size = -1;
+ }
+ }
+ }
+
+ grub_file_close(file);
+ }
+
+ gfxboot_log("read(%s) = %d\n", name, size);
+
+ grub_free(tmp);
+
+ return size;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfxboot_sys_free(void *ptr)
+{
+ if(ptr) grub_free(ptr);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned long gfxboot_sys_strlen(const char *s)
+{
+ return grub_strlen(s);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_sys_strcmp(const char *s1, const char *s2)
+{
+ return grub_strcmp(s1, s2);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+long int gfxboot_sys_strtol(const char *nptr, char **endptr, int base)
+{
+ return grub_strtol(nptr, endptr, base);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfxboot_screen_update(area_t area __attribute__ ((unused)))
+{
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_getkey()
+{
+ return grub_getkey();
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void grub_gfxboot_data_free()
+{
+ if(gfxboot_data) {
+ grub_free(gfxboot_data->menu.entries);
+ grub_free(gfxboot_data->vm.mem.ptr);
+
+ grub_free(gfxboot_data);
+ }
+
+ gfxboot_data = NULL;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void grub_gfxboot_fini(void *data __attribute__ ((unused)))
+{
+ gfxboot_log("grub_gfxboot_fini %p\n", gfxboot_data);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+GRUB_MOD_INIT(gfxboot)
+{
+ struct grub_term_output *term;
+
+ FOR_ACTIVE_TERM_OUTPUTS(term) {
+ if(grub_gfxmenu_try_hook && term->fullscreen) {
+ term->fullscreen();
+ break;
+ }
+ }
+
+ grub_gfxmenu_try_hook = grub_gfxboot_init;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+GRUB_MOD_FINI(gfxboot)
+{
+ gfxboot_log("gfxboot fini %p\n", gfxboot_data);
+
+ grub_gfxboot_data_free();
+
+ grub_gfxmenu_try_hook = NULL;
+}
+
diff --git a/gfxboot_gstate.c b/gfxboot_gstate.c
new file mode 100644
index 0000000..1764c89
--- /dev/null
+++ b/gfxboot_gstate.c
@@ -0,0 +1,102 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// gstate
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_gstate_new()
+{
+ return gfx_obj_alloc(OTYPE_GSTATE, OBJ_GSTATE_SIZE());
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+gstate_t *gfx_obj_gstate_ptr(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_GSTATE) return 0;
+
+ return (gstate_t *) ptr->data.ptr;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_gstate_dump(obj_t *ptr, dump_style_t style)
+{
+ if(!ptr) return 1;
+
+ gstate_t *gstate = ptr->data.ptr;
+ unsigned len = ptr->data.size;
+ if(len != OBJ_GSTATE_SIZE()) {
+ gfxboot_log(" \n");
+
+ return 1;
+ }
+
+ if(!style.ref) {
+ if(!style.inspect) return 0;
+
+ gfxboot_log("region %dx%d_%dx%d", gstate->region.x, gstate->region.y, gstate->region.width, gstate->region.height);
+
+ return 1;
+ }
+
+ if(style.dump) {
+ int width = 0;
+ int height = 0;
+ canvas_t *canvas = gfx_obj_canvas_ptr(gstate->canvas_id);
+
+ if(canvas) {
+ width = canvas->width;
+ height = canvas->height;
+ }
+
+ gfxboot_log(" pos %dx%d", gstate->pos.x, gstate->pos.y);
+ if(gstate->pos.width || gstate->pos.height) {
+ gfxboot_log(", char size %dx%d", gstate->pos.width, gstate->pos.height);
+ }
+ gfxboot_log("\n color #%08x, bg_color #%08x\n", gstate->color, gstate->bg_color);
+ gfxboot_log(" canvas %s (%dx%d)\n", gfx_obj_id2str(gstate->canvas_id), width, height);
+ gfxboot_log(" font %s\n", gfx_obj_id2str(gstate->font_id));
+ }
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_gstate_gc(obj_t *ptr)
+{
+ if(!ptr) return 0;
+
+ gstate_t *gstate = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+ unsigned more_gc = 0;
+
+ if(gstate && data_size == OBJ_GSTATE_SIZE()) {
+ more_gc += gfx_obj_ref_dec_delay_gc(gstate->canvas_id);
+ more_gc += gfx_obj_ref_dec_delay_gc(gstate->font_id);
+ }
+
+ return more_gc;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_gstate_contains(obj_t *ptr, obj_id_t id)
+{
+ if(!ptr || !id) return 0;
+
+ gstate_t *gstate = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+
+ if(gstate && data_size == OBJ_GSTATE_SIZE()) {
+ if(id == gstate->canvas_id || id == gstate->font_id) return 1;
+ }
+
+ return 0;
+}
diff --git a/gfxboot_hash.c b/gfxboot_hash.c
new file mode 100644
index 0000000..ca37e1f
--- /dev/null
+++ b/gfxboot_hash.c
@@ -0,0 +1,334 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// hash
+
+static unsigned find_key(hash_t *hash, data_t *key, int *match);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_hash_new(unsigned max)
+{
+ if(!max) max = 0x10;
+
+ obj_id_t id = gfx_obj_alloc(OTYPE_HASH, OBJ_HASH_SIZE(max));
+ hash_t *h = gfx_obj_hash_ptr(id);
+
+ if(h) {
+ h->max = max;
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+hash_t *gfx_obj_hash_ptr(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_HASH) return 0;
+
+ return (hash_t *) ptr->data.ptr;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_hash_iterate(obj_t *ptr, unsigned *idx, obj_id_t *id1, obj_id_t *id2)
+{
+ hash_t *h = ptr->data.ptr;
+
+ if(ptr->data.size != OBJ_HASH_SIZE(h->max)) {
+ GFX_ERROR(err_internal);
+ return *idx = 0;
+ }
+
+ if(*idx >= h->size) {
+ return 0;
+ }
+
+ gfx_obj_ref_inc(*id1 = h->ptr[*idx].key);
+ gfx_obj_ref_inc(*id2 = h->ptr[*idx].value);
+ (*idx)++;
+
+ return 2;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_hash_dump(obj_t *ptr, dump_style_t style)
+{
+ if(!ptr) return 1;
+
+ hash_t *h = ptr->data.ptr;
+ unsigned u;
+ obj_id_t key, value;
+
+ if(ptr->data.size != OBJ_HASH_SIZE(h->max)) {
+ gfxboot_log(" \n");
+
+ return 1;
+ }
+
+ if(!style.ref) {
+ if(!style.inspect) return 0;
+
+ gfxboot_log("size %u, max %u", h->size, h->max);
+ if(h->parent_id) gfxboot_log(", parent %s", gfx_obj_id2str(h->parent_id));
+
+ return 1;
+ }
+
+ for(u = 0; u < h->size && (!style.max || u < style.max); u++) {
+ key = h->ptr[u].key;
+ if(!key) continue;
+ value = h->ptr[u].value;
+ if(style.dump) gfxboot_log(" ");
+ gfx_obj_dump(key, (dump_style_t) { .inspect = style.inspect, .no_nl = 1 });
+ gfxboot_log(" => ");
+ gfx_obj_dump(value, (dump_style_t) { .inspect = style.inspect, .no_nl = 1 });
+ gfxboot_log("\n");
+ }
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_hash_set(obj_id_t hash_id, obj_id_t key_id, obj_id_t value_id, int do_ref_cnt)
+{
+ unsigned u;
+ int match;
+
+ hash_t *hash = gfx_obj_hash_ptr(hash_id);
+ if(!hash) return 0;
+
+ data_t *key = gfx_obj_mem_ptr(key_id);
+ if(!key) return 0;
+
+ u = find_key(hash, key, &match);
+ // gfxboot_log("XXX set: key %s, u %d, match %d\n", (char *) key->ptr, (int) u, match);
+
+ if(!match) {
+ hash->size++;
+ if(hash->size > hash->max) {
+ unsigned max = hash->max + (hash->max >> 3) + 0x10;
+ hash_id = gfx_obj_realloc(hash_id, OBJ_HASH_SIZE(max));
+ if(!hash_id) return 0;
+ hash = gfx_obj_hash_ptr(hash_id);
+ if(!hash) return 0;
+ hash->max = max;
+ }
+ if(u + 1 < hash->size) {
+ // gfxboot_log("XXX set: move %d -> %d [%d]\n", (int) u, (int) u + 1, (int) (hash->size - u - 1));
+ gfx_memcpy(hash->ptr + u + 1, hash->ptr + u, (sizeof *hash->ptr) * (hash->size - u - 1));
+ hash->ptr[u].key = 0;
+ hash->ptr[u].value = 0;
+ }
+ }
+
+ obj_id_t orig_key_id = 0;
+
+ // if key is writable string, make a copy
+ obj_t *key_obj = gfx_obj_ptr(key_id);
+ if(key_obj && !key_obj->flags.ro) {
+ orig_key_id = key_id;
+ key_id = gfx_obj_mem_dup(key_id, 0);
+ }
+
+ if(do_ref_cnt) {
+ if(!orig_key_id) gfx_obj_ref_inc(key_id);
+ gfx_obj_ref_inc(value_id);
+ gfx_obj_ref_dec(hash->ptr[u].key);
+ gfx_obj_ref_dec(hash->ptr[u].value);
+ }
+
+ // this is a bit tricky: release orig_key_id regardless of do_ref_cnt
+ if(orig_key_id) gfx_obj_ref_dec(orig_key_id);
+
+ hash->ptr[u].key = key_id;
+ hash->ptr[u].value = value_id;
+
+ return hash_id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_pair_t gfx_obj_hash_get(obj_id_t hash_id, data_t *key)
+{
+ obj_id_t orig_hash_id = hash_id;
+ unsigned u, level = 0;
+ int match;
+ hash_t *hash;
+
+ do {
+ // Return hash_id for hash where search started - or hash where key was found in?
+ // With next line it's the second. Remove that line for first variant.
+ orig_hash_id = hash_id;
+
+ hash = gfx_obj_hash_ptr(hash_id);
+
+ if(!hash) return (obj_id_pair_t) {};
+
+ u = find_key(hash, key, &match);
+ // gfxboot_log("XXX get key %s, u %d, match %d\n", (char *) key->ptr, (int) u, match);
+
+ hash_id = hash->parent_id;
+ }
+ while(!match && hash_id && level++ < 100);
+
+ if(!match) return (obj_id_pair_t) {};
+
+ return (obj_id_pair_t) { .id1 = orig_hash_id, .id2 = hash->ptr[u].value };
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_obj_hash_del(obj_id_t hash_id, obj_id_t key_id, int do_ref_cnt)
+{
+ unsigned u;
+ int match;
+
+ hash_t *hash = gfx_obj_hash_ptr(hash_id);
+ if(!hash) return;
+
+ data_t *key = gfx_obj_mem_ptr(key_id);
+ if(!key) return;
+
+ u = find_key(hash, key, &match);
+ // gfxboot_log("XXX del key %s, u %d, match %d\n", (char *) key->ptr, (int) u, match);
+
+ if(match) {
+ if(do_ref_cnt) {
+ gfx_obj_ref_dec(hash->ptr[u].key);
+ gfx_obj_ref_dec(hash->ptr[u].value);
+ }
+
+ hash->size--;
+
+ if(u < hash->size) {
+ // gfxboot_log("XXX del: move %d -> %d [%d]\n", (int) u + 1, (int) u, (int) (hash->size - u));
+ gfx_memcpy(hash->ptr + u, hash->ptr + u + 1, (sizeof *hash->ptr) * (hash->size - u));
+ }
+
+ hash->ptr[hash->size].key = 0;
+ hash->ptr[hash->size].value = 0;
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_hash_gc(obj_t *ptr)
+{
+ if(!ptr) return 0;
+
+ hash_t *hash = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+ unsigned idx, more_gc = 0;
+
+ if(hash && data_size == OBJ_HASH_SIZE(hash->max)) {
+ more_gc += gfx_obj_ref_dec_delay_gc(hash->parent_id);
+ for(idx = 0; idx < hash->size; idx++) {
+ more_gc += gfx_obj_ref_dec_delay_gc(hash->ptr[idx].key);
+ more_gc += gfx_obj_ref_dec_delay_gc(hash->ptr[idx].value);
+ }
+ }
+
+ return more_gc;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_hash_contains(obj_t *ptr, obj_id_t id)
+{
+ if(!ptr || !id) return 0;
+
+ hash_t *hash = ptr->data.ptr;
+ unsigned data_size = ptr->data.size;
+ unsigned idx;
+
+ if(hash && data_size == OBJ_HASH_SIZE(hash->max)) {
+ if(hash->parent_id == id) return 1;
+ for(idx = 0; idx < hash->size; idx++) {
+ if(
+ id == hash->ptr[idx].key ||
+ id == hash->ptr[idx].value
+ ) return 1;
+ }
+ }
+
+ return 0;
+}
+
+#if 0
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// linear search
+unsigned find_key(hash_t *hash, data_t *key, int *match)
+{
+ unsigned u;
+ data_t *hash_key;
+
+ *match = 0;
+
+ for(u = 0; u < hash->size; u++) {
+ hash_key = gfx_obj_mem_ptr(hash->ptr[u].key);
+ if(!hash_key) continue;
+ int i = gfx_obj_mem_cmp(key, hash_key);
+ if(i > 0) continue;
+ if(i == 0) *match = 1;
+ break;
+ }
+
+ return u;
+}
+
+#else
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// binary search
+unsigned find_key(hash_t *hash, data_t *key, int *match)
+{
+ unsigned u_start, u_end, u;
+ data_t *hash_key;
+
+ *match = 0;
+
+ u_start = u = 0;
+ u_end = hash->size;
+
+ while(u_end > u_start) {
+ u = (u_end + u_start) / 2;
+
+ hash_key = gfx_obj_mem_ptr(hash->ptr[u].key);
+ if(!hash_key) return 0;
+
+ int i = gfx_obj_mem_cmp(key, hash_key);
+
+ if(i == 0) {
+ *match = 1;
+ break;
+ }
+
+ if(u_end == u_start + 1) {
+ if(i > 0) u++;
+ break;
+ }
+
+ if(i > 0) {
+ if(u_end == u + 1) {
+ if(i > 0) u++;
+ break;
+ }
+ u_start = u;
+ }
+ else {
+ u_end = u;
+ }
+ }
+
+ return u;
+}
+#endif
diff --git a/gfxboot_jpeg.c b/gfxboot_jpeg.c
new file mode 100644
index 0000000..b5b3665
--- /dev/null
+++ b/gfxboot_jpeg.c
@@ -0,0 +1,950 @@
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#pragma GCC diagnostic ignored "-Wshadow"
+
+#include "gfxboot.h"
+
+#define ERR_NO_SOI 1
+#define ERR_NOT_8BIT 2
+#define ERR_HEIGHT_MISMATCH 3
+#define ERR_WIDTH_MISMATCH 4
+#define ERR_BAD_WIDTH_OR_HEIGHT 5
+#define ERR_TOO_MANY_COMPPS 6
+#define ERR_ILLEGAL_HV 7
+#define ERR_QUANT_TABLE_SELECTOR 8
+#define ERR_NOT_YCBCR_221111 9
+#define ERR_UNKNOWN_CID_IN_SCAN 10
+#define ERR_NOT_SEQUENTIAL_DCT 11
+#define ERR_WRONG_MARKER 12
+#define ERR_NO_EOI 13
+#define ERR_BAD_TABLES 14
+#define ERR_DEPTH_MISMATCH 15
+
+#define ISHIFT 11
+
+#define IFIX(a) ((int)((a) * (1 << ISHIFT) + .5))
+#define IMULT(a, b) (((a) * (b)) >> ISHIFT)
+#define ITOINT(a) ((a) >> ISHIFT)
+
+#ifndef __P
+# define __P(x) x
+#endif
+
+/* special markers */
+#define M_BADHUFF -1
+#define M_EOF 0x80
+
+struct in {
+ unsigned int bits;
+ int left;
+ int marker;
+ void *data;
+};
+
+/*********************************/
+struct dec_hufftbl;
+struct enc_hufftbl;
+
+union hufftblp {
+ struct dec_hufftbl *dhuff;
+ struct enc_hufftbl *ehuff;
+};
+
+struct scan {
+ int dc; /* old dc value */
+
+ union hufftblp hudc;
+ union hufftblp huac;
+ int next; /* when to switch to next scan */
+
+ int cid; /* component id */
+ int hv; /* horiz/vert, copied from comp */
+ int tq; /* quant tbl, copied from comp */
+};
+
+/*********************************/
+
+#define DECBITS 8 /* seems to be the optimum */
+
+struct dec_hufftbl {
+ int maxcode[17];
+ int valptr[16];
+ unsigned char vals[256];
+ unsigned int llvals[1 << DECBITS];
+};
+
+struct jpeg_decdata {
+ int dcts[6 * 64 + 16];
+ int out[64 * 6];
+ int dquant[3][64];
+};
+
+static void decode_mcus __P((struct in *, int *, int, struct scan *, int *));
+static void dec_makehuff __P((struct dec_hufftbl *, int *, unsigned char *));
+
+static void setinput __P((struct in *));
+/*********************************/
+
+#undef PREC
+#define PREC int
+
+static void idctqtab __P((unsigned char *, PREC *));
+static void idct __P((int *, int *, PREC *, PREC, int));
+static void scaleidctqtab __P((PREC *, PREC));
+
+/*********************************/
+
+static void initcol __P((PREC[][64]));
+
+static void col221111(int *out, unsigned char *pic, int width, int bits);
+static unsigned char tmp_img[16*16*4]; /* 16 x 16, 32 bit color */
+
+/*********************************/
+
+#define M_SOI 0xd8
+#define M_APP0 0xe0
+#define M_DQT 0xdb
+#define M_SOF0 0xc0
+#define M_DHT 0xc4
+#define M_DRI 0xdd
+#define M_SOS 0xda
+#define M_RST0 0xd0
+#define M_EOI 0xd9
+#define M_COM 0xfe
+
+static unsigned char *datap;
+static struct jpeg_decdata decdata;
+
+static void memset(void *p, int c, int n)
+{
+ unsigned char *x = p;
+
+ while(n--) *x++ = c;
+}
+
+
+static int getbyte(void)
+{
+ return *datap++;
+}
+
+static int getword(void)
+{
+ int c1, c2;
+ c1 = *datap;
+ c2 = datap[1];
+ datap += 2;
+ return c1 << 8 | c2;
+}
+
+struct comp {
+ int cid;
+ int hv;
+ int tq;
+};
+
+#define MAXCOMP 4
+struct jpginfo {
+ int nc; /* number of components */
+ int ns; /* number of scans */
+ int dri; /* restart interval */
+};
+
+static struct jpginfo info;
+static struct comp comps[MAXCOMP];
+
+static struct scan dscans[MAXCOMP];
+
+static unsigned char quant[4][64];
+
+static struct dec_hufftbl dhuff[4];
+
+#define dec_huffdc (dhuff + 0)
+#define dec_huffac (dhuff + 2)
+
+static struct in in;
+
+static int readtables(int till)
+{
+ int m, l, i, j, lq, pq, tq;
+ int tc, th, tt;
+
+ for (;;) {
+ if (getbyte() != 0xff)
+ return -1;
+ if ((m = getbyte()) == till)
+ break;
+
+ switch (m) {
+ case 0xc2:
+ return 0;
+
+ case M_DQT:
+ lq = getword();
+ while (lq > 2) {
+ pq = getbyte();
+ tq = pq & 15;
+ if (tq > 3)
+ return -1;
+ pq >>= 4;
+ if (pq != 0)
+ return -1;
+ for (i = 0; i < 64; i++)
+ quant[tq][i] = getbyte();
+ lq -= 64 + 1;
+ }
+ break;
+
+ case M_DHT:
+ l = getword();
+ while (l > 2) {
+ int hufflen[16], k;
+ unsigned char huffvals[256];
+
+ tc = getbyte();
+ th = tc & 15;
+ tc >>= 4;
+ tt = tc * 2 + th;
+ if (tc > 1 || th > 1)
+ return -1;
+ for (i = 0; i < 16; i++)
+ hufflen[i] = getbyte();
+ l -= 1 + 16;
+ k = 0;
+ for (i = 0; i < 16; i++) {
+ for (j = 0; j < hufflen[i]; j++)
+ huffvals[k++] = getbyte();
+ l -= hufflen[i];
+ }
+ dec_makehuff(dhuff + tt, hufflen,
+ huffvals);
+ }
+ break;
+
+ case M_DRI:
+ l = getword();
+ info.dri = getword();
+ break;
+
+ default:
+ l = getword();
+ while (l-- > 2)
+ getbyte();
+ break;
+ }
+ }
+ return 0;
+}
+
+static void dec_initscans(void)
+{
+ int i;
+
+ for (i = 0; i < info.ns; i++)
+ dscans[i].dc = 0;
+}
+
+int gfx_jpeg_decode(uint8_t *buf, uint8_t *pic, int x0, int x1, int y0, int y1, int color_bits)
+{
+ int i, j, m, tac, tdc;
+ int mcusx, mcusy, mx, my;
+ int max[6];
+ int width, height;
+ int mx0, mx1, my0, my1;
+
+ datap = buf;
+ if (getbyte() != 0xff)
+ return ERR_NO_SOI;
+ if (getbyte() != M_SOI)
+ return ERR_NO_SOI;
+ if (readtables(M_SOF0))
+ return ERR_BAD_TABLES;
+ if(info.dri) return ERR_WRONG_MARKER;
+
+ getword();
+ i = getbyte();
+ if (i != 8)
+ return ERR_NOT_8BIT;
+
+ height = getword();
+ width = getword();
+
+ info.nc = getbyte();
+ if (info.nc > MAXCOMP)
+ return ERR_TOO_MANY_COMPPS;
+ for (i = 0; i < info.nc; i++) {
+ int h, v;
+ comps[i].cid = getbyte();
+ comps[i].hv = getbyte();
+ v = comps[i].hv & 15;
+ h = comps[i].hv >> 4;
+ comps[i].tq = getbyte();
+ if (h > 3 || v > 3)
+ return ERR_ILLEGAL_HV;
+ if (comps[i].tq > 3)
+ return ERR_QUANT_TABLE_SELECTOR;
+ }
+ if (readtables(M_SOS))
+ return ERR_BAD_TABLES;
+ getword();
+ info.ns = getbyte();
+ if (info.ns != 3)
+ return ERR_NOT_YCBCR_221111;
+ for (i = 0; i < 3; i++) {
+ dscans[i].cid = getbyte();
+ tdc = getbyte();
+ tac = tdc & 15;
+ tdc >>= 4;
+ if (tdc > 1 || tac > 1)
+ return ERR_QUANT_TABLE_SELECTOR;
+ for (j = 0; j < info.nc; j++)
+ if (comps[j].cid == dscans[i].cid)
+ break;
+ if (j == info.nc)
+ return ERR_UNKNOWN_CID_IN_SCAN;
+ dscans[i].hv = comps[j].hv;
+ dscans[i].tq = comps[j].tq;
+ dscans[i].hudc.dhuff = dec_huffdc + tdc;
+ dscans[i].huac.dhuff = dec_huffac + tac;
+ }
+
+ i = getbyte();
+ j = getbyte();
+ m = getbyte();
+
+ if (i != 0 || j != 63 || m != 0)
+ return ERR_NOT_SEQUENTIAL_DCT;
+
+ if (dscans[0].cid != 1 || dscans[1].cid != 2 || dscans[2].cid != 3)
+ return ERR_NOT_YCBCR_221111;
+
+ if (dscans[0].hv != 0x22 || dscans[1].hv != 0x11 || dscans[2].hv != 0x11)
+ return ERR_NOT_YCBCR_221111;
+
+ mcusx = (width + 15) >> 4;
+ mcusy = (height + 15) >> 4;
+
+ mx0 = x0 >> 4;
+ my0 = y0 >> 4;
+
+ /* inclusive! */
+ mx1 = ((x1 + 15) >> 4) - 1;
+ my1 = ((y1 + 15) >> 4) - 1;
+
+ if(my1 < mcusy) mcusy = my1 + 1;
+
+ idctqtab(quant[dscans[0].tq], decdata.dquant[0]);
+ idctqtab(quant[dscans[1].tq], decdata.dquant[1]);
+ idctqtab(quant[dscans[2].tq], decdata.dquant[2]);
+ initcol(decdata.dquant);
+ setinput(&in);
+
+ dec_initscans();
+
+ dscans[0].next = 6 - 4;
+ dscans[1].next = 6 - 4 - 1;
+ dscans[2].next = 6 - 4 - 1 - 1; /* 411 encoding */
+ for (my = 0; my < mcusy; my++) {
+ for (mx = 0; mx < mcusx; mx++) {
+ decode_mcus(&in, decdata.dcts, 6, dscans, max);
+
+ if(
+ my >= my0 && my <= my1 &&
+ mx >= mx0 && mx <= mx1
+ ) {
+ int i0, i1, j0, j1, yofs;
+
+ idct(decdata.dcts, decdata.out, decdata.dquant[0], IFIX(128.5), max[0]);
+ idct(decdata.dcts + 64, decdata.out + 64, decdata.dquant[0], IFIX(128.5), max[1]);
+ idct(decdata.dcts + 128, decdata.out + 128, decdata.dquant[0], IFIX(128.5), max[2]);
+ idct(decdata.dcts + 192, decdata.out + 192, decdata.dquant[0], IFIX(128.5), max[3]);
+ idct(decdata.dcts + 256, decdata.out + 256, decdata.dquant[1], IFIX(0.5), max[4]);
+ idct(decdata.dcts + 320, decdata.out + 320, decdata.dquant[2], IFIX(0.5), max[5]);
+
+ // color_bits * 2: actually 16 * (color_bits / 8)
+ col221111(decdata.out, tmp_img, color_bits * 2, color_bits);
+
+ j0 = my == my0 ? y0 - 16 * my : 0;
+ j1 = my == my1 ? y1 - 16 * my : 16;
+ for(j = j0; j < j1; j++) {
+ yofs = (16 * my - y0 + j) * (x1 - x0);
+ i0 = mx == mx0 ? x0 - 16 * mx : 0;
+ i1 = mx == mx1 ? x1 - 16 * mx : 16;
+
+ switch(color_bits) {
+ case 8:
+ for(i = i0; i < i1; i++) {
+ *((unsigned char *) pic + 16 * mx - x0 + i + yofs) =
+ *((unsigned char *) tmp_img + 16 * j + i);
+ }
+ break;
+
+ case 16:
+ for(i = i0; i < i1; i++) {
+ *((unsigned short *) pic + 16 * mx - x0 + i + yofs) =
+ *((unsigned short *) tmp_img + 16 * j + i);
+ }
+ break;
+
+ case 32:
+ for(i = i0; i < i1; i++) {
+ *((unsigned *) pic + 16 * mx - x0 + i + yofs) =
+ *((unsigned *) tmp_img + 16 * j + i);
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/****************************************************************/
+/************** huffman decoder ***************/
+/****************************************************************/
+
+static int fillbits __P((struct in *, int, unsigned int));
+static int dec_rec2
+__P((struct in *, struct dec_hufftbl *, int *, int, int));
+
+static void setinput(in)
+struct in *in;
+{
+ in->left = 0;
+ in->bits = 0;
+ in->marker = 0;
+}
+
+static int fillbits(in, le, bi)
+struct in *in;
+int le;
+unsigned int bi;
+{
+ int b, m;
+
+ if (in->marker) {
+ if (le <= 16)
+ in->bits = bi << 16, le += 16;
+ return le;
+ }
+ while (le <= 24) {
+ b = getbyte();
+ if (b == 0xff && (m = getbyte()) != 0) {
+ in->marker = m;
+ if (le <= 16)
+ bi = bi << 16, le += 16;
+ break;
+ }
+ bi = bi << 8 | b;
+ le += 8;
+ }
+ in->bits = bi; /* tmp... 2 return values needed */
+ return le;
+}
+
+#define LEBI_DCL int le, bi
+#define LEBI_GET(in) (le = in->left, bi = in->bits)
+#define LEBI_PUT(in) (in->left = le, in->bits = bi)
+
+#define GETBITS(in, n) ( \
+ (le < (n) ? le = fillbits(in, le, bi), bi = in->bits : 0), \
+ (le -= (n)), \
+ bi >> le & ((1 << (n)) - 1) \
+)
+
+#define UNGETBITS(in, n) ( \
+ le += (n) \
+)
+
+
+static int dec_rec2(in, hu, runp, c, i)
+struct in *in;
+struct dec_hufftbl *hu;
+int *runp;
+int c, i;
+{
+ LEBI_DCL;
+
+ LEBI_GET(in);
+ if (i) {
+ UNGETBITS(in, i & 127);
+ *runp = i >> 8 & 15;
+ i >>= 16;
+ } else {
+ for (i = DECBITS; (c = ((c << 1) | GETBITS(in, 1))) >= (hu->maxcode[i]); i++);
+ if (i >= 16) {
+ in->marker = M_BADHUFF;
+ return 0;
+ }
+ i = hu->vals[hu->valptr[i] + c - hu->maxcode[i - 1] * 2];
+ *runp = i >> 4;
+ i &= 15;
+ }
+ if (i == 0) { /* sigh, 0xf0 is 11 bit */
+ LEBI_PUT(in);
+ return 0;
+ }
+ /* receive part */
+ c = GETBITS(in, i);
+ if (c < (1 << (i - 1)))
+ c += (-1 << i) + 1;
+ LEBI_PUT(in);
+ return c;
+}
+
+#define DEC_REC(in, hu, r, i) ( \
+ r = GETBITS(in, DECBITS), \
+ i = hu->llvals[r], \
+ i & 128 ? \
+ ( \
+ UNGETBITS(in, i & 127), \
+ r = i >> 8 & 15, \
+ i >> 16 \
+ ) \
+ : \
+ ( \
+ LEBI_PUT(in), \
+ i = dec_rec2(in, hu, &r, r, i), \
+ LEBI_GET(in), \
+ i \
+ ) \
+)
+
+static void decode_mcus(in, dct, n, sc, maxp)
+struct in *in;
+int *dct;
+int n;
+struct scan *sc;
+int *maxp;
+{
+ struct dec_hufftbl *hu;
+ int i, r, t;
+ LEBI_DCL;
+
+ memset(dct, 0, n * 64 * sizeof(*dct));
+ LEBI_GET(in);
+ while (n-- > 0) {
+ hu = sc->hudc.dhuff;
+ *dct++ = (sc->dc += DEC_REC(in, hu, r, t));
+
+ hu = sc->huac.dhuff;
+ i = 63;
+ while (i > 0) {
+ t = DEC_REC(in, hu, r, t);
+ if (t == 0 && r == 0) {
+ dct += i;
+ break;
+ }
+ dct += r;
+ *dct++ = t;
+ i -= r + 1;
+ }
+ *maxp++ = 64 - i;
+ if (n == sc->next)
+ sc++;
+ }
+ LEBI_PUT(in);
+}
+
+static void dec_makehuff(hu, hufflen, huffvals)
+struct dec_hufftbl *hu;
+int *hufflen;
+unsigned char *huffvals;
+{
+ int code, k, i, j, d, x, c, v;
+ for (i = 0; i < (1 << DECBITS); i++)
+ hu->llvals[i] = 0;
+
+/*
+ * llvals layout:
+ *
+ * value v already known, run r, backup u bits:
+ * vvvvvvvvvvvvvvvv 0000 rrrr 1 uuuuuuu
+ * value unknown, size b bits, run r, backup u bits:
+ * 000000000000bbbb 0000 rrrr 0 uuuuuuu
+ * value and size unknown:
+ * 0000000000000000 0000 0000 0 0000000
+ */
+ code = 0;
+ k = 0;
+ for (i = 0; i < 16; i++, code <<= 1) { /* sizes */
+ hu->valptr[i] = k;
+ for (j = 0; j < hufflen[i]; j++) {
+ hu->vals[k] = *huffvals++;
+ if (i < DECBITS) {
+ c = code << (DECBITS - 1 - i);
+ v = hu->vals[k] & 0x0f; /* size */
+ for (d = 1 << (DECBITS - 1 - i); --d >= 0;) {
+ if (v + i < DECBITS) { /* both fit in table */
+ x = d >> (DECBITS - 1 - v -
+ i);
+ if (v && x < (1 << (v - 1)))
+ x += (-1 << v) + 1;
+ x = x << 16 | (hu-> vals[k] & 0xf0) << 4 |
+ (DECBITS - (i + 1 + v)) | 128;
+ } else
+ x = v << 16 | (hu-> vals[k] & 0xf0) << 4 |
+ (DECBITS - (i + 1));
+ hu->llvals[c | d] = x;
+ }
+ }
+ code++;
+ k++;
+ }
+ hu->maxcode[i] = code;
+ }
+ hu->maxcode[16] = 0x20000; /* always terminate decode */
+}
+
+/****************************************************************/
+/************** idct ***************/
+/****************************************************************/
+
+#define ONE ((PREC)IFIX(1.))
+#define S2 ((PREC)IFIX(0.382683432))
+#define C2 ((PREC)IFIX(0.923879532))
+#define C4 ((PREC)IFIX(0.707106781))
+
+#define S22 ((PREC)IFIX(2 * 0.382683432))
+#define C22 ((PREC)IFIX(2 * 0.923879532))
+#define IC4 ((PREC)IFIX(1 / 0.707106781))
+
+#define C3IC1 ((PREC)IFIX(0.847759065)) /* c3/c1 */
+#define C5IC1 ((PREC)IFIX(0.566454497)) /* c5/c1 */
+#define C7IC1 ((PREC)IFIX(0.198912367)) /* c7/c1 */
+
+#define XPP(a,b) (t = a + b, b = a - b, a = t)
+#define XMP(a,b) (t = a - b, b = a + b, a = t)
+#define XPM(a,b) (t = a + b, b = b - a, a = t)
+
+#define ROT(a,b,s,c) ( t = IMULT(a + b, s), \
+ a = IMULT(a, c - s) + t, \
+ b = IMULT(b, c + s) - t)
+
+#define IDCT \
+( \
+ XPP(t0, t1), \
+ XMP(t2, t3), \
+ t2 = IMULT(t2, IC4) - t3, \
+ XPP(t0, t3), \
+ XPP(t1, t2), \
+ XMP(t4, t7), \
+ XPP(t5, t6), \
+ XMP(t5, t7), \
+ t5 = IMULT(t5, IC4), \
+ ROT(t4, t6, S22, C22),\
+ t6 -= t7, \
+ t5 -= t6, \
+ t4 -= t5, \
+ XPP(t0, t7), \
+ XPP(t1, t6), \
+ XPP(t2, t5), \
+ XPP(t3, t4) \
+)
+
+static unsigned char zig2[64] = {
+ 0, 2, 3, 9, 10, 20, 21, 35,
+ 14, 16, 25, 31, 39, 46, 50, 57,
+ 5, 7, 12, 18, 23, 33, 37, 48,
+ 27, 29, 41, 44, 52, 55, 59, 62,
+ 15, 26, 30, 40, 45, 51, 56, 58,
+ 1, 4, 8, 11, 19, 22, 34, 36,
+ 28, 42, 43, 53, 54, 60, 61, 63,
+ 6, 13, 17, 24, 32, 38, 47, 49
+};
+
+void idct(in, out, quant, off, max)
+int *in;
+int *out;
+PREC *quant;
+PREC off;
+int max;
+{
+ PREC t0, t1, t2, t3, t4, t5, t6, t7, t;
+ PREC tmp[64], *tmpp;
+ int i, j;
+ unsigned char *zig2p;
+
+ t0 = off;
+ if (max == 1) {
+ t0 += in[0] * quant[0];
+ for (i = 0; i < 64; i++)
+ out[i] = ITOINT(t0);
+ return;
+ }
+ zig2p = zig2;
+ tmpp = tmp;
+ for (i = 0; i < 8; i++) {
+ j = *zig2p++;
+ t0 += in[j] * quant[j];
+ j = *zig2p++;
+ t5 = in[j] * quant[j];
+ j = *zig2p++;
+ t2 = in[j] * quant[j];
+ j = *zig2p++;
+ t7 = in[j] * quant[j];
+ j = *zig2p++;
+ t1 = in[j] * quant[j];
+ j = *zig2p++;
+ t4 = in[j] * quant[j];
+ j = *zig2p++;
+ t3 = in[j] * quant[j];
+ j = *zig2p++;
+ t6 = in[j] * quant[j];
+ IDCT;
+ tmpp[0 * 8] = t0;
+ tmpp[1 * 8] = t1;
+ tmpp[2 * 8] = t2;
+ tmpp[3 * 8] = t3;
+ tmpp[4 * 8] = t4;
+ tmpp[5 * 8] = t5;
+ tmpp[6 * 8] = t6;
+ tmpp[7 * 8] = t7;
+ tmpp++;
+ t0 = 0;
+ }
+ for (i = 0; i < 8; i++) {
+ t0 = tmp[8 * i + 0];
+ t1 = tmp[8 * i + 1];
+ t2 = tmp[8 * i + 2];
+ t3 = tmp[8 * i + 3];
+ t4 = tmp[8 * i + 4];
+ t5 = tmp[8 * i + 5];
+ t6 = tmp[8 * i + 6];
+ t7 = tmp[8 * i + 7];
+ IDCT;
+ out[8 * i + 0] = ITOINT(t0);
+ out[8 * i + 1] = ITOINT(t1);
+ out[8 * i + 2] = ITOINT(t2);
+ out[8 * i + 3] = ITOINT(t3);
+ out[8 * i + 4] = ITOINT(t4);
+ out[8 * i + 5] = ITOINT(t5);
+ out[8 * i + 6] = ITOINT(t6);
+ out[8 * i + 7] = ITOINT(t7);
+ }
+}
+
+static unsigned char zig[64] = {
+ 0, 1, 5, 6, 14, 15, 27, 28,
+ 2, 4, 7, 13, 16, 26, 29, 42,
+ 3, 8, 12, 17, 25, 30, 41, 43,
+ 9, 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54,
+ 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61,
+ 35, 36, 48, 49, 57, 58, 62, 63
+};
+
+static PREC aaidct[8] = {
+ IFIX(0.3535533906), IFIX(0.4903926402),
+ IFIX(0.4619397663), IFIX(0.4157348062),
+ IFIX(0.3535533906), IFIX(0.2777851165),
+ IFIX(0.1913417162), IFIX(0.0975451610)
+};
+
+
+static void idctqtab(qin, qout)
+unsigned char *qin;
+PREC *qout;
+{
+ int i, j;
+
+ for (i = 0; i < 8; i++)
+ for (j = 0; j < 8; j++)
+ qout[zig[i * 8 + j]] = qin[zig[i * 8 + j]] *
+ IMULT(aaidct[i], aaidct[j]);
+}
+
+static void scaleidctqtab(q, sc)
+PREC *q;
+PREC sc;
+{
+ int i;
+
+ for (i = 0; i < 64; i++)
+ q[i] = IMULT(q[i], sc);
+}
+
+/****************************************************************/
+/************** color decoder ***************/
+/****************************************************************/
+
+/*
+ * YCbCr Color transformation:
+ *
+ * y:0..255 Cb:-128..127 Cr:-128..127
+ *
+ * R = Y + 1.40200 * Cr
+ * G = Y - 0.34414 * Cb - 0.71414 * Cr
+ * B = Y + 1.77200 * Cb
+ *
+ * =>
+ * Cr *= 1.40200;
+ * Cb *= 1.77200;
+ * Cg = 0.19421 * Cb + .50937 * Cr;
+ * R = Y + Cr;
+ * G = Y - Cg;
+ * B = Y + Cb;
+ *
+ * =>
+ * Cg = (50 * Cb + 130 * Cr + 128) >> 8;
+ */
+
+static void initcol(q)
+PREC q[][64];
+{
+ scaleidctqtab(q[1], IFIX(1.77200));
+ scaleidctqtab(q[2], IFIX(1.40200));
+}
+
+/* This is optimized for the stupid sun SUNWspro compiler. */
+#define STORECLAMP(a,x) \
+( \
+ (a) = (x), \
+ (unsigned int)(x) >= 256 ? \
+ ((a) = (x) < 0 ? 0 : 255) \
+ : \
+ 0 \
+)
+
+#define CLAMP(x) ((unsigned int)(x) >= 256 ? ((x) < 0 ? 0 : 255) : (x))
+
+#define CBCRCG(xin) \
+( \
+ cb = outc[0 + xin], \
+ cr = outc[64 + xin], \
+ cg = (50 * cb + 130 * cr + 128) >> 8 \
+)
+
+#define PIC(yin, xin, p, xout) \
+( \
+ y = outy[(yin) * 8 + xin], \
+ STORECLAMP(p[(xout) * 4 + 0], y + cb), \
+ STORECLAMP(p[(xout) * 4 + 1], y - cg), \
+ STORECLAMP(p[(xout) * 4 + 2], y + cr) \
+)
+
+#define PIC221111x(xin, xin_4, xin_3) \
+( \
+ CBCRCG(xin), \
+ PIC(xin_4 + 0, xin_3 + 0, pic0, xin * 2 + 0), \
+ PIC(xin_4 + 0, xin_3 + 1, pic0, xin * 2 + 1), \
+ PIC(xin_4 + 1, xin_3 + 0, pic1, xin * 2 + 0), \
+ PIC(xin_4 + 1, xin_3 + 1, pic1, xin * 2 + 1) \
+)
+
+#define PIC_16(yin, xin, p, xout, add) \
+( \
+ y = outy[(yin) * 8 + xin], \
+ *(unsigned short *) (p + (xout) * 2) = store_16(CLAMP(y + cr), CLAMP(y - cg), CLAMP(y + cb), add) \
+)
+
+#define PIC221111_16x(xin, xin_4, xin_3) \
+( \
+ CBCRCG(xin), \
+ PIC_16(xin_4 + 0, xin_3 + 0, pic0, xin * 2 + 0, 3 * 0x55), \
+ PIC_16(xin_4 + 0, xin_3 + 1, pic0, xin * 2 + 1, 0 * 0x55), \
+ PIC_16(xin_4 + 1, xin_3 + 0, pic1, xin * 2 + 0, 1 * 0x55), \
+ PIC_16(xin_4 + 1, xin_3 + 1, pic1, xin * 2 + 1, 2 * 0x55) \
+)
+
+#define PIC_8(yin, xin, p, xout, add) \
+( \
+ y = outy[(yin) * 8 + xin], \
+ p[(xout)] = store_8(CLAMP(y + cr), CLAMP(y - cg), CLAMP(y + cb), add) \
+)
+
+#define PIC221111_8x(xin, xin_4, xin_3) \
+( \
+ CBCRCG(xin), \
+ PIC_8(xin_4 + 0, xin_3 + 0, pic0, xin * 2 + 0, 3 * 0x55), \
+ PIC_8(xin_4 + 0, xin_3 + 1, pic0, xin * 2 + 1, 0 * 0x55), \
+ PIC_8(xin_4 + 1, xin_3 + 0, pic1, xin * 2 + 0, 1 * 0x55), \
+ PIC_8(xin_4 + 1, xin_3 + 1, pic1, xin * 2 + 1, 2 * 0x55) \
+)
+
+
+static unsigned store_16(unsigned r, unsigned g, unsigned b, unsigned add)
+{
+ unsigned rgb;
+
+ rgb = (((r << 5) - r + add) >> 8) << 11;
+ rgb += (((g << 6) - g + add) >> 8) << 5;
+ rgb += ((b << 5) - b + add) >> 8;
+
+ return rgb;
+}
+
+static unsigned store_8(unsigned r, unsigned g, unsigned b, unsigned add)
+{
+ unsigned rgb;
+
+#if 0
+ r = ((r << 2) - r + add) >> 8;
+ g = ((g << 3) - g + add) >> 8;
+ b = ((b << 3) - b + add) >> 8;
+
+ rgb = (r << 6) + (g << 3) + b;
+#endif
+
+ rgb = (((r << 2) - r + add) >> 8) << 6;
+ rgb += (((g << 3) - g + add) >> 8) << 3;
+ rgb += ((b << 3) - b + add) >> 8;
+
+ return rgb;
+}
+
+static void col221111(int *out, unsigned char *pic, int width, int bits)
+{
+ int i, j, k, k_4, k_3;
+ unsigned char *pic0, *pic1;
+ int *outy, *outc;
+ int cr, cg, cb, y;
+
+ pic0 = pic;
+ pic1 = pic + width;
+ outy = out;
+ outc = out + 64 * 4;
+ for(i = 2; i > 0; i--) {
+ for(j = 4; j > 0; j--) {
+ for(k = 0; k < 8; k++) {
+ k_4 = (k >> 2) << 3;
+ k_3 = (k & 3) << 1;
+ switch(bits) {
+ case 8:
+ PIC221111_8x(k, k_4, k_3);
+ break;
+ case 16:
+ PIC221111_16x(k, k_4, k_3);
+ break;
+ case 32:
+ PIC221111x(k, k_4, k_3);
+ break;
+ }
+ }
+ outc += 8;
+ outy += 16;
+ pic0 += 2 * width;
+ pic1 += 2 * width;
+ }
+ outy += 64 * 2 - 16 * 4;
+ }
+}
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+unsigned gfx_jpeg_getsize(uint8_t *buf)
+{
+ unsigned u;
+
+ datap = buf;
+ getbyte(); getbyte();
+ if(readtables(M_SOF0)) return 0;
+ getword(); getbyte();
+
+ u = getword() << 16;
+ u += getword();
+
+ return u;
+}
diff --git a/gfxboot_lib.c b/gfxboot_lib.c
new file mode 100644
index 0000000..0490169
--- /dev/null
+++ b/gfxboot_lib.c
@@ -0,0 +1,316 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// lib functions
+//
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_show_error()
+{
+ if(gfxboot_data->vm.error.id) {
+#ifdef FULL_ERROR
+ gfxboot_log(
+ "error %d (%s), ip = %s, src = %s:%d\n",
+ gfxboot_data->vm.error.id,
+ gfx_error_msg(gfxboot_data->vm.error.id),
+ gfx_debug_get_ip(),
+ gfxboot_data->vm.error.src_file,
+ gfxboot_data->vm.error.src_line
+ );
+#else
+ gfxboot_log(
+ "error %d (%s), ip = %s\n",
+ gfxboot_data->vm.error.id,
+ gfx_error_msg(gfxboot_data->vm.error.id),
+ gfx_debug_get_ip()
+ );
+#endif
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+const char *gfx_error_msg(error_id_t id)
+{
+ static const char *error_names[] = {
+ [err_ok] = "ok",
+ [err_invalid_code] = "invalid code",
+ [err_invalid_instruction] = "invalid instruction",
+ [err_no_array_start] = "no array start",
+ [err_no_hash_start] = "no hash start",
+ [err_no_memory] = "no memory",
+ [err_invalid_hash_key] = "invalid hash key",
+ [err_stack_underflow] = "stack underflow",
+ [err_internal] = "internal",
+ [err_no_loop_context] = "no loop context",
+ [err_invalid_range] = "invalid range",
+ [err_invalid_data] = "invalid data",
+ [err_readonly] = "readonly",
+ [err_invalid_arguments] = "invalid arguments",
+ [err_div_by_zero] = "div by zero",
+ };
+
+ if(id < sizeof error_names/sizeof *error_names) {
+ return error_names[id];
+ }
+ else {
+ return "weird error";
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uint32_t gfx_read_le32(const void *p)
+{
+ const uint8_t *s = p;
+
+ return (uint32_t) s[0] + ((uint32_t) s[1] << 8) + ((uint32_t) s[2] << 16) + ((uint32_t) s[3] << 24);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *gfx_utf8_enc(unsigned uc)
+{
+ static char buf[7];
+ char *s = buf;
+
+ uc &= 0x7fffffff;
+
+ if(uc < 0x80) { // 7 bits
+ *s++ = uc;
+ }
+ else {
+ if(uc < (1 << 11)) { // 11 (5 + 6) bits
+ *s++ = 0xc0 + (uc >> 6);
+ goto utf8_encode_2;
+ }
+ else if(uc < (1 << 16)) { // 16 (4 + 6 + 6) bits
+ *s++ = 0xe0 + (uc >> 12);
+ goto utf8_encode_3;
+ }
+ else if(uc < (1 << 21)) { // 21 (3 + 6 + 6 + 6) bits
+ *s++ = 0xf0 + (uc >> 18);
+ goto utf8_encode_4;
+ }
+ else if(uc < (1 << 26)) { // 26 (2 + 6 + 6 + 6 + 6) bits
+ *s++ = 0xf8 + (uc >> 24);
+ goto utf8_encode_5;
+ }
+ else { // 31 (1 + 6 + 6 + 6 + 6 + 6) bits
+ *s++ = 0xfc + (uc >> 30);
+ }
+
+ *s++ = 0x80 + ((uc >> 24) & ((1 << 6) - 1));
+
+ utf8_encode_5:
+ *s++ = 0x80 + ((uc >> 18) & ((1 << 6) - 1));
+
+ utf8_encode_4:
+ *s++ = 0x80 + ((uc >> 12) & ((1 << 6) - 1));
+
+ utf8_encode_3:
+ *s++ = 0x80 + ((uc >> 6) & ((1 << 6) - 1));
+
+ utf8_encode_2:
+ *s++ = 0x80 + (uc & ((1 << 6) - 1));
+ }
+
+ *s = 0;
+
+ return buf;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Decode utf8 sequence.
+//
+// a) if s points to a valid utf8 sequence:
+// - returns unicode char (a non-negative number)
+// - s is updated to point past utf8 char
+//
+// b) if s does not point to a valid utf8 sequence
+// - returns negated first byte
+// - s is incremented by 1
+//
+int gfx_utf8_dec(char **s, unsigned *len)
+{
+ unsigned char *p;
+ int c;
+ unsigned u, l, l0;
+
+ if(!s || (!*s && !*len)) return 0;
+
+ p = (uint8_t *) *s;
+
+ u = *p++;
+ if(len) (*len)--;
+
+ if(u >= 0x80) {
+ if(u < 0xc0 || u >= 0xfe) {
+ *s = (char *) p;
+ return -(int) u;
+ }
+ l = 1;
+ if(u < 0xe0) {
+ c = u & 0x1f;
+ }
+ else if(u < 0xf0) {
+ c = u & 0x0f;
+ l = 2;
+ }
+ else if(u < 0xf8) {
+ c = u & 0x07;
+ l = 3;
+ }
+ else if(u < 0xfc) {
+ c = u & 0x03;
+ l = 4;
+ }
+ else if(u < 0xfe) {
+ c = u & 0x01;
+ l = 5;
+ }
+ if(len && l > *len) {
+ *s = (char *) p;
+ return -(int) u;
+ }
+ l0 = l;
+ while(l--) {
+ u = *p++;
+ if(u < 0x80 || u >= 0xc0) {
+ u = (uint8_t) **s;
+ (*s)++;
+ if(len) (*len)--;
+ return -(int) u;
+ }
+ c = (c << 6) + ((int) u & 0x3f);
+ }
+ if(len) *len -= l0;
+ }
+ else {
+ c = (int) u;
+ }
+
+ *s = (char *) p;
+
+ return c;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_read_file(char *name)
+{
+ obj_id_t id = 0;
+ void *buf = 0;
+
+ int size = gfxboot_sys_read_file(name, &buf);
+
+ if(size >= 0 && buf) {
+ id = gfx_obj_mem_new((unsigned) size);
+
+ if(size) {
+ data_t *mem = gfx_obj_mem_ptr(id);
+
+ if(mem) gfx_memcpy(mem->ptr, buf, (unsigned) size);
+
+ gfxboot_sys_free(buf);
+ }
+ }
+
+ gfxboot_log("read(%s): id = #%08x\n", name, id);
+
+ return id;
+}
+
+
+#if INCLUDE_DIV64
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+static uint64_t __udivmoddi4(uint64_t num, uint64_t den, int mod) __attribute__((noinline));
+uint64_t __udivdi3 (uint64_t a, uint64_t b);
+uint64_t __umoddi3 (uint64_t a, uint64_t b);
+int64_t __divdi3 (int64_t a, int64_t b);
+int64_t __moddi3 (int64_t a, int64_t b);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uint64_t __udivmoddi4(uint64_t num, uint64_t den, int mod)
+{
+ if(num < (1llu << 32) && den < (1llu << 32)) {
+ return mod ? ((uint32_t) num % (uint32_t) den) : ((uint32_t) num / (uint32_t) den);
+ }
+
+ uint64_t bit = 1, res = 0;
+
+ while(den < num && bit && (den & (1llu << 63)) == 0) {
+ den <<= 1;
+ bit <<= 1;
+ }
+
+ while(bit) {
+ if(num >= den) {
+ num -= den;
+ res |= bit;
+ }
+ bit >>= 1;
+ den >>= 1;
+ }
+
+ return mod ? num : res;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uint64_t __udivdi3(uint64_t a, uint64_t b)
+{
+ return __udivmoddi4(a, b, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uint64_t __umoddi3 (uint64_t a, uint64_t b)
+{
+ return __udivmoddi4(a, b, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int64_t __divdi3(int64_t a, int64_t b)
+{
+ unsigned sign = 0;
+
+ if(a < 0) {
+ a = -a;
+ sign = 1;
+ }
+
+ if(b < 0) {
+ b = -b;
+ sign ^= 1;
+ }
+
+ int64_t r = (int64_t) __udivmoddi4((uint64_t) a, (uint64_t) b, 0);
+
+ return sign ? -r : r;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int64_t __moddi3(int64_t a, int64_t b)
+{
+ int64_t r;
+
+ if(b < 0) b = -b;
+
+ if(a < 0) {
+ r = - (int64_t) __udivmoddi4((uint64_t) (-a), (uint64_t) b, 1);
+ }
+ else {
+ r = (int64_t) __udivmoddi4((uint64_t) a, (uint64_t) b, 1);
+ }
+
+ return r;
+}
+#endif
diff --git a/gfxboot_main.c b/gfxboot_main.c
new file mode 100644
index 0000000..5cc4ce4
--- /dev/null
+++ b/gfxboot_main.c
@@ -0,0 +1,507 @@
+#include
+
+static uint8_t _console_font[4947];
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfxboot_init()
+{
+ gfxboot_log("gfxboot_init\n");
+
+ gfxboot_log("data_t = %d, obj_t = %d\n", (int) sizeof (data_t), (int) sizeof (obj_t));
+
+ if(gfx_malloc_init()) return 1;
+
+ if(gfx_obj_init()) return 1;
+
+ // setup virtual fb as canvas object
+ gfxboot_data->screen.virt_id = gfx_obj_canvas_new(gfxboot_data->screen.real.width, gfxboot_data->screen.real.height);
+ if(!gfxboot_data->screen.virt_id) return 1;
+
+ gfx_vm_status_dump();
+
+ canvas_t *canvas = gfx_obj_canvas_ptr(gfxboot_data->screen.virt_id);
+
+ gfxboot_data->gstate_id = gfx_obj_gstate_new();
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) {
+ gstate->canvas_id = gfx_obj_ref_inc(gfxboot_data->screen.virt_id);
+ gstate->region = (area_t) { .width = canvas->width, .height = canvas->height };
+ gstate->pos = (area_t) {0, 0, 0, 0};
+ gstate->color = COLOR(0x00, 0xff, 0xff, 0xff);
+ gstate->bg_color = COLOR(0xff, 0x00, 0x00, 0x00);
+ }
+
+ gfxboot_data->console.gstate_id = gfx_obj_gstate_new();
+ gstate_t *console_gstate = gfx_obj_gstate_ptr(gfxboot_data->console.gstate_id);
+
+ if(console_gstate) {
+ console_gstate->canvas_id = gfx_obj_ref_inc(gfxboot_data->screen.virt_id);
+ console_gstate->region = (area_t) { .width = canvas->width, .height = canvas->height };
+ console_gstate->pos = (area_t) {0, 0, 0, 0};
+ console_gstate->color = COLOR(0x00, 0xff, 0xff, 0xff);
+ console_gstate->bg_color = COLOR(0x00, 0x24, 0x16, 0x32);
+ }
+
+ if(!gfx_setup_dict()) return 1;
+
+ // setup compiled-in console font
+ obj_id_t console_font_data_id = gfx_obj_const_mem_nofree_new(_console_font, sizeof _console_font, 0, 0);
+ console_gstate->font_id = gfx_obj_font_open(console_font_data_id);
+
+ // font structure itself holds ref to data
+ gfx_obj_ref_dec(console_font_data_id);
+
+ area_t area = gfx_font_dim(console_gstate->font_id);
+ console_gstate->pos.width = area.width;
+ console_gstate->pos.height = area.height;
+
+ int t_width = console_gstate->pos.width * 80;
+ int t_height = console_gstate->pos.height * 25;
+ if(
+ console_gstate->region.width >= t_width &&
+ console_gstate->region.height >= t_height
+ ) {
+ console_gstate->region.x = (console_gstate->region.width - t_width) / 2;
+ console_gstate->region.y = (console_gstate->region.height - t_height) / 2;
+ console_gstate->region.width = t_width;
+ console_gstate->region.height = t_height;
+ console_gstate->pos.y = t_height - console_gstate->pos.height;
+ }
+
+ // load main program
+ obj_id_t pfile_id = gfx_read_file("main.gc");
+ obj_t *pfile_ptr = gfx_obj_ptr(pfile_id);
+
+ if(pfile_ptr) {
+ pfile_ptr->flags.ro = 1;
+ }
+ else {
+ gfxboot_log("no program to run - aborting\n");
+ return 1;
+ }
+
+ if(!gfx_program_init(pfile_id)) {
+ gfxboot_log("failed to setup prgram\n");
+ return 1;
+ }
+
+ gfx_obj_ref_dec(pfile_id);
+
+#if 0
+ gfx_console_putc_xy(200, 100, L'€');
+#endif
+
+ // gfx_obj_asciiz_new("Foo Bar");
+
+ // gfxboot_data->vm.debug.trace_ip = 1;
+ // gfx_program_run();
+ gfx_show_error();
+
+ // gfx_obj_dump(OBJ_ID(0, 1), (dump_style_t) { .dump = 1 });
+
+ return 0;
+
+ // return gfxboot_data->vm.error.id ? 1 : 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfxboot_debug_command(char *str)
+{
+ if(str) gfx_debug_cmd(str);
+}
+
+
+/*
+ if ((action & 0x01)) menu_fini ();
+ if ((action & 0x02)) *auto_boot = 1;
+ if ((action & 0x04)) grub_cmdline_run (1);
+ if ((action & 0x08)) goto refresh;
+ if ((action & 0x10)) return action >> 8;
+*/
+int gfxboot_process_key(unsigned key)
+{
+ static int x = 0, y = 0;
+ int action = 0;
+
+ gfxboot_debug(2, 2, "gfxboot_process_key: key = 0x%08x\n", key);
+
+ if(gfxboot_data->vm.debug.console.show) {
+ gfx_program_debug(key);
+
+ return action;
+ }
+ else {
+ if(key == 0x04) { // '^D'
+ gfx_program_debug_on_off(1);
+ return action;
+ }
+
+ gfx_program_process_key(key);
+ }
+
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) gfx_rect(gstate, x, y, 100, 20, (40 + y) * 0x7834242296 * (x + 1));
+
+ y += 20;
+
+ if(y >= gfxboot_data->screen.real.height) {
+ y = 0;
+ x += 120;
+ x = x % gfxboot_data->screen.real.width;
+ }
+
+ switch(key) {
+ case '0'...'9':
+ gfxboot_data->menu.entry = (int) key - '0';
+ gfxboot_log("menu.entry = %d\n", gfxboot_data->menu.entry);
+ break;
+
+ case 'b':
+ action = (gfxboot_data->menu.entry << 8) + 0x11;
+ gfxboot_log("booting entry %d\n", gfxboot_data->menu.entry);
+ break;
+
+ case 'c':
+ gfxboot_log("running cmdline\n");
+ action = 0x04;
+ break;
+
+ case 'x':
+ action = (-1 << 8) + 0x10 + 1;
+ break;
+ }
+
+ return action;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfxboot_timeout()
+{
+ gfxboot_log(
+ "gfxboot_timeout: max = %d, current = %d\n",
+ gfxboot_data->menu.timeout.max,
+ gfxboot_data->menu.timeout.current
+ );
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// lat9v-16.psfu
+//
+static uint8_t _console_font[4947] = {
+ 0x72, 0xb5, 0x4a, 0x86, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7e, 0xc3, 0x99, 0x99, 0xf3, 0xe7, 0xe7, 0xff, 0xe7, 0xe7, 0x7e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x6e, 0xf8, 0xd8, 0xd8, 0xdc, 0xd8, 0xd8, 0xd8, 0xf8, 0x6e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xdb, 0xdb, 0xdf, 0xd8, 0xdb, 0x6e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x88, 0x88, 0xf8, 0x88, 0x88, 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf8, 0x80, 0xe0, 0x80, 0x80, 0x00, 0x3e, 0x20, 0x38, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x70, 0x88, 0x80, 0x88, 0x70, 0x00, 0x3c, 0x22, 0x3c, 0x24, 0x22, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x80, 0x80, 0x80, 0xf8, 0x00, 0x3e, 0x20, 0x38, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44,
+ 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa,
+ 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x00, 0x88, 0xc8, 0xa8, 0x98, 0x88, 0x00, 0x20, 0x20, 0x20, 0x20, 0x3e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x88, 0x88, 0x50, 0x50, 0x20, 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0e, 0x38, 0xe0, 0x38, 0x0e, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xe0, 0x38, 0x0e, 0x38, 0xe0, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x06, 0x0c, 0xfe, 0x18, 0x30, 0xfe, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x1e, 0x7e, 0xfe, 0x7e, 0x1e, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0xfc, 0xfe, 0xfc, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0c, 0xfe, 0x0c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0xfe, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x6c, 0xfe, 0x6c, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x36, 0x66, 0xfe, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x10, 0x7c, 0xd6, 0xd0, 0xd0, 0x7c, 0x16, 0x16, 0xd6, 0x7c, 0x10, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc2, 0xc6, 0x0c, 0x18, 0x30, 0x60, 0xc6, 0x86, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x76, 0xdc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0x06, 0x06, 0x3c, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x1e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xfe, 0xc0, 0xc0, 0xc0, 0xfc, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x38, 0x60, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xfe, 0xc6, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x06, 0x0c, 0x78, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0x0c, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xde, 0xde, 0xde, 0xdc, 0xc0, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x66, 0x66, 0x66, 0x66, 0xfc, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x66, 0xc2, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xf8, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6c, 0xf8, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xfe, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x62, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xfe, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x66, 0xc2, 0xc0, 0xc0, 0xde, 0xc6, 0xc6, 0x66, 0x3a, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x1e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xe6, 0x66, 0x66, 0x6c, 0x78, 0x78, 0x6c, 0x66, 0x66, 0xe6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc6, 0xee, 0xfe, 0xfe, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xde, 0x7c, 0x0c, 0x0e, 0x00, 0x00,
+ 0x00, 0x00, 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x66, 0x66, 0xe6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0x64, 0x38, 0x0c, 0x06, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7e, 0x7e, 0x5a, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0xfe, 0xee, 0x6c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc6, 0xc6, 0x6c, 0x7c, 0x38, 0x38, 0x7c, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xfe, 0xc6, 0x86, 0x0c, 0x18, 0x30, 0x60, 0xc2, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
+ 0x00, 0x30, 0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xe0, 0x60, 0x60, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x1c, 0x0c, 0x0c, 0x3c, 0x6c, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x38, 0x6c, 0x64, 0x60, 0xf0, 0x60, 0x60, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x7c, 0x0c, 0xcc, 0x78, 0x00,
+ 0x00, 0x00, 0xe0, 0x60, 0x60, 0x6c, 0x76, 0x66, 0x66, 0x66, 0x66, 0xe6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x06, 0x06, 0x00, 0x0e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00,
+ 0x00, 0x00, 0xe0, 0x60, 0x60, 0x66, 0x6c, 0x78, 0x78, 0x6c, 0x66, 0xe6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0xfe, 0xd6, 0xd6, 0xd6, 0xd6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x7c, 0x0c, 0x0c, 0x1e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x76, 0x66, 0x60, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0x60, 0x38, 0x0c, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x30, 0x36, 0x1c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0xfe, 0x6c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x38, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x0c, 0xf8, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xcc, 0x18, 0x30, 0x60, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0e, 0x18, 0x18, 0x18, 0x70, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x18, 0x18, 0x18, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c,
+ 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c,
+ 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0c, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c,
+ 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c,
+ 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x18, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x7c, 0xd6, 0xd0, 0xd0, 0xd0, 0xd6, 0x7c, 0x10, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x38, 0x6c, 0x60, 0x60, 0xf0, 0x60, 0x60, 0x66, 0xf6, 0x6c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x1c, 0x32, 0x60, 0x60, 0xfc, 0x60, 0xfc, 0x60, 0x60, 0x32, 0x1c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x6c, 0x38, 0x00, 0x7c, 0xc6, 0xc6, 0x60, 0x38, 0x0c, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x7c, 0xc6, 0x60, 0x38, 0x6c, 0xc6, 0xc6, 0x6c, 0x38, 0x0c, 0xc6, 0x7c, 0x00, 0x00, 0x00,
+ 0x00, 0x6c, 0x38, 0x00, 0x00, 0x7c, 0xc6, 0x60, 0x38, 0x0c, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x42, 0x99, 0xa5, 0xa1, 0xa5, 0x99, 0x42, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x6c, 0x6c, 0x3e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x6c, 0xd8, 0x6c, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x42, 0xb9, 0xa5, 0xb9, 0xa5, 0xa5, 0x42, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00,
+ 0x38, 0x6c, 0x18, 0x30, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x38, 0x6c, 0x18, 0x6c, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x6c, 0x38, 0x00, 0xfe, 0xc6, 0x8c, 0x18, 0x30, 0x60, 0xc2, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf6, 0xc0, 0xc0, 0xc0, 0x00,
+ 0x00, 0x00, 0x7f, 0xd6, 0xd6, 0x76, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x6c, 0x38, 0x00, 0xfe, 0xcc, 0x18, 0x30, 0x60, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x70, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x6c, 0x36, 0x6c, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x77, 0xcc, 0xcc, 0xcc, 0xcf, 0xcf, 0xcc, 0xcc, 0xcc, 0x77, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xdb, 0xdb, 0xdf, 0xd8, 0xdb, 0x6e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x60, 0xc6, 0xc6, 0x7c, 0x00, 0x00,
+ 0x60, 0x30, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x18, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x38, 0x6c, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x76, 0xdc, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x6c, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x38, 0x6c, 0x38, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3e, 0x78, 0xd8, 0xd8, 0xfc, 0xd8, 0xd8, 0xd8, 0xd8, 0xde, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x66, 0xc2, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2, 0x66, 0x3c, 0x0c, 0x66, 0x3c, 0x00,
+ 0x60, 0x30, 0x00, 0xfe, 0x66, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x18, 0x00, 0xfe, 0x66, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x38, 0x6c, 0x00, 0xfe, 0x66, 0x60, 0x7c, 0x60, 0x60, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x6c, 0x00, 0xfe, 0x66, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00,
+ 0x60, 0x30, 0x00, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x0c, 0x00, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x3c, 0x66, 0x00, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x66, 0x00, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xf8, 0x6c, 0x66, 0x66, 0xf6, 0x66, 0x66, 0x66, 0x6c, 0xf8, 0x00, 0x00, 0x00, 0x00,
+ 0x76, 0xdc, 0x00, 0xc6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+ 0x60, 0x30, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x18, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x38, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x76, 0xdc, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7e, 0xc6, 0xce, 0xce, 0xde, 0xf6, 0xe6, 0xe6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00,
+ 0x60, 0x30, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x18, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x38, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x0c, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xcc, 0xc6, 0xc6, 0xc6, 0xd6, 0xdc, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x60, 0x30, 0x18, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x18, 0x30, 0x60, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x38, 0x6c, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x76, 0xdc, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6c, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x38, 0x6c, 0x38, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xdb, 0x1b, 0x7f, 0xd8, 0xdb, 0x7e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc0, 0xc6, 0x7c, 0x18, 0x6c, 0x38, 0x00,
+ 0x00, 0x60, 0x30, 0x18, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0c, 0x18, 0x30, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x38, 0x6c, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6c, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x60, 0x30, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0c, 0x18, 0x30, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x18, 0x3c, 0x66, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6c, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x78, 0x30, 0x78, 0x0c, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x76, 0xdc, 0x00, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x60, 0x30, 0x18, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0c, 0x18, 0x30, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x38, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x76, 0xdc, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xce, 0xde, 0xfe, 0xf6, 0xe6, 0xfc, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x60, 0x30, 0x18, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x18, 0x30, 0x60, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x30, 0x78, 0xcc, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xcc, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0c, 0x18, 0x30, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x0c, 0xf8, 0x00,
+ 0x00, 0x00, 0xf0, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x0c, 0xf8, 0x00,
+ 0xef, 0xbf, 0xbd, 0xff, 0xe2, 0x89, 0x88, 0xff, 0xc5, 0x92, 0xff, 0xc5, 0x93, 0xff, 0xe2, 0x97,
+ 0x86, 0xff, 0xe2, 0x90, 0x89, 0xff, 0xe2, 0x90, 0x8c, 0xff, 0xe2, 0x90, 0x8d, 0xff, 0xe2, 0x90,
+ 0x8a, 0xff, 0xe2, 0x96, 0x91, 0xff, 0xe2, 0x96, 0x92, 0xff, 0xe2, 0x96, 0x93, 0xff, 0xe2, 0x96,
+ 0x88, 0xff, 0xe2, 0x96, 0x84, 0xff, 0xe2, 0x96, 0x80, 0xff, 0xe2, 0x96, 0x8c, 0xff, 0xe2, 0x96,
+ 0x90, 0xff, 0xe2, 0x90, 0xa4, 0xff, 0xe2, 0x90, 0x8b, 0xff, 0xe2, 0x89, 0xa4, 0xff, 0xe2, 0x89,
+ 0xa5, 0xff, 0xe2, 0x89, 0xa0, 0xff, 0xe2, 0x97, 0x80, 0xff, 0xe2, 0x96, 0xb6, 0xff, 0xe2, 0x86,
+ 0x91, 0xff, 0xe2, 0x86, 0x93, 0xff, 0xe2, 0x86, 0x92, 0xff, 0xe2, 0x86, 0x90, 0xff, 0xe2, 0x86,
+ 0x95, 0xff, 0xe2, 0x86, 0x94, 0xff, 0xe2, 0x86, 0xb5, 0xff, 0xcf, 0x80, 0xff, 0x20, 0xc2, 0xa0,
+ 0xe2, 0x80, 0x80, 0xe2, 0x80, 0x81, 0xe2, 0x80, 0x82, 0xe2, 0x80, 0x83, 0xe2, 0x80, 0x84, 0xe2,
+ 0x80, 0x85, 0xe2, 0x80, 0x86, 0xe2, 0x80, 0x87, 0xe2, 0x80, 0x88, 0xe2, 0x80, 0x89, 0xe2, 0x80,
+ 0x8a, 0xe2, 0x80, 0xaf, 0xff, 0x21, 0xff, 0x22, 0xff, 0x23, 0xff, 0x24, 0xff, 0x25, 0xff, 0x26,
+ 0xff, 0x27, 0xff, 0x28, 0xff, 0x29, 0xff, 0x2a, 0xff, 0x2b, 0xff, 0x2c, 0xff, 0x2d, 0xff, 0x2e,
+ 0xff, 0x2f, 0xff, 0x30, 0xff, 0x31, 0xff, 0x32, 0xff, 0x33, 0xff, 0x34, 0xff, 0x35, 0xff, 0x36,
+ 0xff, 0x37, 0xff, 0x38, 0xff, 0x39, 0xff, 0x3a, 0xff, 0x3b, 0xff, 0x3c, 0xff, 0x3d, 0xff, 0x3e,
+ 0xff, 0x3f, 0xff, 0x40, 0xff, 0x41, 0xff, 0x42, 0xff, 0x43, 0xff, 0x44, 0xff, 0x45, 0xff, 0x46,
+ 0xff, 0x47, 0xff, 0x48, 0xff, 0x49, 0xff, 0x4a, 0xff, 0x4b, 0xe2, 0x84, 0xaa, 0xff, 0x4c, 0xff,
+ 0x4d, 0xff, 0x4e, 0xff, 0x4f, 0xff, 0x50, 0xff, 0x51, 0xff, 0x52, 0xff, 0x53, 0xff, 0x54, 0xff,
+ 0x55, 0xff, 0x56, 0xff, 0x57, 0xff, 0x58, 0xff, 0x59, 0xff, 0x5a, 0xff, 0x5b, 0xff, 0x5c, 0xff,
+ 0x5d, 0xff, 0x5e, 0xff, 0x5f, 0xef, 0xa0, 0x84, 0xff, 0x60, 0xff, 0x61, 0xff, 0x62, 0xff, 0x63,
+ 0xff, 0x64, 0xff, 0x65, 0xff, 0x66, 0xff, 0x67, 0xff, 0x68, 0xff, 0x69, 0xff, 0x6a, 0xff, 0x6b,
+ 0xff, 0x6c, 0xff, 0x6d, 0xff, 0x6e, 0xff, 0x6f, 0xff, 0x70, 0xff, 0x71, 0xff, 0x72, 0xff, 0x73,
+ 0xff, 0x74, 0xff, 0x75, 0xff, 0x76, 0xff, 0x77, 0xff, 0x78, 0xff, 0x79, 0xff, 0x7a, 0xff, 0x7b,
+ 0xff, 0x7c, 0xff, 0x7d, 0xff, 0x7e, 0xff, 0xc5, 0xb8, 0xff, 0xef, 0xa0, 0x81, 0xff, 0xe2, 0x95,
+ 0xb5, 0xff, 0xe2, 0x95, 0xb6, 0xff, 0xe2, 0x94, 0x94, 0xff, 0xe2, 0x95, 0xb7, 0xff, 0xe2, 0x94,
+ 0x82, 0xff, 0xe2, 0x94, 0x8c, 0xff, 0xe2, 0x94, 0x9c, 0xff, 0xe2, 0x95, 0xb4, 0xff, 0xe2, 0x94,
+ 0x98, 0xff, 0xe2, 0x94, 0x80, 0xff, 0xe2, 0x94, 0xb4, 0xff, 0xe2, 0x94, 0x90, 0xff, 0xe2, 0x94,
+ 0xa4, 0xff, 0xe2, 0x94, 0xac, 0xff, 0xe2, 0x94, 0xbc, 0xff, 0xef, 0xa0, 0x83, 0xff, 0xe2, 0x95,
+ 0xb9, 0xff, 0xe2, 0x95, 0xba, 0xff, 0xe2, 0x94, 0x97, 0xe2, 0x95, 0x9a, 0xff, 0xe2, 0x95, 0xbb,
+ 0xff, 0xe2, 0x94, 0x83, 0xe2, 0x95, 0x91, 0xff, 0xe2, 0x94, 0x8f, 0xe2, 0x95, 0x94, 0xff, 0xe2,
+ 0x94, 0xa3, 0xe2, 0x95, 0xa0, 0xff, 0xe2, 0x95, 0xb8, 0xff, 0xe2, 0x94, 0x9b, 0xe2, 0x95, 0x9d,
+ 0xff, 0xe2, 0x94, 0x81, 0xe2, 0x95, 0x90, 0xff, 0xe2, 0x94, 0xbb, 0xe2, 0x95, 0xa9, 0xff, 0xe2,
+ 0x94, 0x93, 0xe2, 0x95, 0x97, 0xff, 0xe2, 0x95, 0xa3, 0xe2, 0x94, 0xab, 0xff, 0xe2, 0x94, 0xb3,
+ 0xe2, 0x95, 0xa6, 0xff, 0xe2, 0x95, 0x8b, 0xe2, 0x95, 0xac, 0xff, 0xe2, 0x90, 0xa3, 0xff, 0xc2,
+ 0xa1, 0xff, 0xc2, 0xa2, 0xff, 0xc2, 0xa3, 0xff, 0xe2, 0x82, 0xac, 0xff, 0xc2, 0xa5, 0xff, 0xc5,
+ 0xa0, 0xff, 0xc2, 0xa7, 0xff, 0xc5, 0xa1, 0xff, 0xc2, 0xa9, 0xff, 0xc2, 0xaa, 0xff, 0xc2, 0xab,
+ 0xff, 0xc2, 0xac, 0xff, 0xc2, 0xad, 0xff, 0xc2, 0xae, 0xff, 0xc2, 0xaf, 0xef, 0xa0, 0x80, 0xff,
+ 0xc2, 0xb0, 0xff, 0xc2, 0xb1, 0xff, 0xc2, 0xb2, 0xff, 0xc2, 0xb3, 0xff, 0xc5, 0xbd, 0xff, 0xc2,
+ 0xb5, 0xff, 0xc2, 0xb6, 0xff, 0xc2, 0xb7, 0xff, 0xc5, 0xbe, 0xff, 0xc2, 0xb9, 0xff, 0xc2, 0xba,
+ 0xff, 0xc2, 0xbb, 0xff, 0xc5, 0x92, 0xff, 0xc5, 0x93, 0xff, 0xc5, 0xb8, 0xff, 0xc2, 0xbf, 0xff,
+ 0xc3, 0x80, 0xff, 0xc3, 0x81, 0xff, 0xc3, 0x82, 0xff, 0xc3, 0x83, 0xff, 0xc3, 0x84, 0xff, 0xc3,
+ 0x85, 0xe2, 0x84, 0xab, 0xff, 0xc3, 0x86, 0xff, 0xc3, 0x87, 0xff, 0xc3, 0x88, 0xff, 0xc3, 0x89,
+ 0xff, 0xc3, 0x8a, 0xff, 0xc3, 0x8b, 0xff, 0xc3, 0x8c, 0xff, 0xc3, 0x8d, 0xff, 0xc3, 0x8e, 0xff,
+ 0xc3, 0x8f, 0xff, 0xc3, 0x90, 0xff, 0xc3, 0x91, 0xff, 0xc3, 0x92, 0xff, 0xc3, 0x93, 0xff, 0xc3,
+ 0x94, 0xff, 0xc3, 0x95, 0xff, 0xc3, 0x96, 0xff, 0xc3, 0x97, 0xff, 0xc3, 0x98, 0xff, 0xc3, 0x99,
+ 0xff, 0xc3, 0x9a, 0xff, 0xc3, 0x9b, 0xff, 0xc3, 0x9c, 0xff, 0xc3, 0x9d, 0xff, 0xc3, 0x9e, 0xff,
+ 0xc3, 0x9f, 0xff, 0xc3, 0xa0, 0xff, 0xc3, 0xa1, 0xff, 0xc3, 0xa2, 0xff, 0xc3, 0xa3, 0xff, 0xc3,
+ 0xa4, 0xff, 0xc3, 0xa5, 0xff, 0xc3, 0xa6, 0xff, 0xc3, 0xa7, 0xff, 0xc3, 0xa8, 0xff, 0xc3, 0xa9,
+ 0xff, 0xc3, 0xaa, 0xff, 0xc3, 0xab, 0xff, 0xc3, 0xac, 0xff, 0xc3, 0xad, 0xff, 0xc3, 0xae, 0xff,
+ 0xc3, 0xaf, 0xff, 0xc3, 0xb0, 0xff, 0xc3, 0xb1, 0xff, 0xc3, 0xb2, 0xff, 0xc3, 0xb3, 0xff, 0xc3,
+ 0xb4, 0xff, 0xc3, 0xb5, 0xff, 0xc3, 0xb6, 0xff, 0xc3, 0xb7, 0xff, 0xc3, 0xb8, 0xff, 0xc3, 0xb9,
+ 0xff, 0xc3, 0xba, 0xff, 0xc3, 0xbb, 0xff, 0xc3, 0xbc, 0xff, 0xc3, 0xbd, 0xff, 0xc3, 0xbe, 0xff,
+ 0xc3, 0xbf, 0xff
+};
diff --git a/gfxboot_malloc.c b/gfxboot_malloc.c
new file mode 100644
index 0000000..f1ec1d6
--- /dev/null
+++ b/gfxboot_malloc.c
@@ -0,0 +1,140 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// malloc functions
+//
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_malloc_init()
+{
+ malloc_header_t *m = gfxboot_data->vm.mem.ptr;
+
+ if(!m || gfxboot_data->vm.mem.size < 0x1000) return 1;
+
+ m->next = gfxboot_data->vm.mem.size;
+ m->id = 0;
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_malloc_dump(dump_style_t style)
+{
+ uint8_t *p, *p_start, *p_end;
+ malloc_header_t *m;
+ unsigned idx, x, g;
+
+ p_start = gfxboot_data->vm.mem.ptr;
+ p_end = p_start + gfxboot_data->vm.mem.size;
+
+ gfxboot_log("=== memory dump ===\n");
+
+ if(!p_start) {
+ gfxboot_log(" no memory\n");
+
+ return;
+ }
+
+ for(idx = 0, p = p_start; p >= p_start && p < p_end; p += m->next, idx++) {
+ if(style.max && idx >= style.max) break;
+ m = (malloc_header_t *) p;
+ x = OBJ_ID2IDX(m->id);
+ g = OBJ_ID2GEN(m->id);
+ gfxboot_log("%4u: %4u.%02x, ", idx, x, g);
+ if(gfxboot_data->vm.debug.show_pointer) {
+ gfxboot_log("%p", m + 1);
+ }
+ else {
+ gfxboot_log("0x%08x", (int) (p - p_start) + (int) sizeof (malloc_header_t));
+ }
+ gfxboot_log("[%8d]\n", (int) (m->next - sizeof (malloc_header_t)));
+ if(m->next <= sizeof (malloc_header_t)) break;
+ }
+
+ if(!style.max && p != p_end) {
+ gfxboot_log(" -- malloc chain corrupt --\n");
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void *gfx_malloc(uint32_t size, uint32_t id)
+{
+ uint8_t *p, *p_start, *p_end;
+ malloc_header_t *m;
+ void *mem = 0;
+
+ if(id == 0) id = 1; // ensure it's not 0
+
+ p_start = gfxboot_data->vm.mem.ptr;
+ p_end = p_start + gfxboot_data->vm.mem.size;
+
+ if(size > gfxboot_data->vm.mem.size) return mem;
+
+ // size 0: return a valid pointer that we won't try to free
+ if(size == 0) return p_end;
+
+ size += sizeof (malloc_header_t); // include header size
+ size = (size + 3) & ~3U; // align a bit
+
+ for(p = p_start; p >= p_start && p < p_end; p += m->next) {
+ m = (malloc_header_t *) p;
+ if(m->id == 0 && m->next >= size) {
+ m->id = id;
+ mem = p + sizeof (malloc_header_t);
+ gfx_memset(mem, 0, size - sizeof (malloc_header_t));
+ if(m->next > size + sizeof (malloc_header_t)) {
+ uint32_t n = m->next - size;
+ m->next = size;
+ m = (malloc_header_t *) (p + size);
+ m->id = 0;
+ m->next = n;
+ }
+ break;
+ }
+ }
+
+ return mem;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_free(void *ptr)
+{
+ uint8_t *p, *p_start, *p_end, *p_last, *mem = ptr;
+ malloc_header_t *m, *m_last, *m_next;
+
+ p_start = gfxboot_data->vm.mem.ptr;
+ p_end = p_start + gfxboot_data->vm.mem.size;
+
+ if(!mem || mem < p_start || mem >= p_end) return;
+
+ // p < mem instead of 'p < p_end' for an early exit
+ for(p = p_last = p_start; p >= p_start && p < mem; p_last = p, p += m->next) {
+ m = (malloc_header_t *) p;
+
+ if(mem == p + sizeof (malloc_header_t)) {
+ if(m->id == 0) return; // already free
+
+ m->id = 0; // mark as free
+
+ if(p + m->next < p_end) { // not last block
+ m_next = (malloc_header_t *) (p + m->next);
+ if(m_next->id == 0) { // join next + current block
+ m->next += m_next->next;
+ }
+ }
+
+ if(p_last != p) { // not first block
+ m_last = (malloc_header_t *) p_last;
+ if(m_last->id == 0) { // join last + current block
+ m_last->next += m->next;
+ }
+ }
+ }
+ }
+}
+
+
diff --git a/gfxboot_mem.c b/gfxboot_mem.c
new file mode 100644
index 0000000..093e469
--- /dev/null
+++ b/gfxboot_mem.c
@@ -0,0 +1,302 @@
+#include
+#include
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// mem
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_mem_new(uint32_t size)
+{
+ return gfx_obj_alloc(OTYPE_MEM, size);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+data_t *gfx_obj_mem_ptr(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_MEM) return 0;
+
+ return &ptr->data;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+data_t *gfx_obj_mem_subtype_ptr(obj_id_t id, uint8_t subtype)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_MEM || ptr->sub_type != subtype) return 0;
+
+ return &ptr->data;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_const_mem_nofree_new(const uint8_t *str, unsigned len, uint8_t sub_type, obj_id_t ref_id)
+{
+ obj_id_t id = 0;
+
+ if(str) {
+ // avoid recursive references, always point to original object
+ if(ref_id) {
+ obj_t *ptr;
+ while((ptr = gfx_obj_ptr(ref_id))) {
+ if(ptr->base_type != OTYPE_MEM || !ptr->data.ref_id) break;
+ ref_id = ptr->data.ref_id;
+ }
+ }
+
+ id = gfx_obj_new(OTYPE_MEM);
+ obj_t *optr = gfx_obj_ptr(id);
+ if(optr) {
+ optr->data.ptr = (uint8_t *) str;
+ optr->data.size = len;
+ optr->data.ref_id = ref_id;
+ optr->flags.ro = 1;
+ optr->flags.nofree = 1;
+ optr->sub_type = sub_type;
+
+ gfx_obj_ref_inc(ref_id);
+ }
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_asciiz_new(const char *str)
+{
+ return gfx_obj_const_mem_nofree_new((uint8_t *) str, gfx_strlen(str), t_string, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_mem_dup(obj_id_t id, unsigned extra_bytes)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_MEM) return 0;
+
+ obj_id_t new_id = gfx_obj_new(OTYPE_MEM);
+ obj_t *new_ptr = gfx_obj_ptr(new_id);
+
+ if(new_ptr) {
+ data_t *data = OBJ_DATA_FROM_PTR(ptr);
+ if((new_ptr->data.ptr = gfx_malloc(data->size + extra_bytes, new_id))) {
+ new_ptr->data.size = data->size + extra_bytes;
+ new_ptr->sub_type = ptr->sub_type;
+ gfx_memcpy(new_ptr->data.ptr, data->ptr, data->size);
+ }
+ else {
+ // delete, keep generation counter
+ *new_ptr = (obj_t) { gen:new_ptr->gen };
+ new_id = 0;
+ }
+ }
+
+ return new_id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// returns value (0..255) or -1 (invalid)
+int gfx_obj_mem_get(obj_id_t mem_id, int pos)
+{
+ data_t *mem = gfx_obj_mem_ptr(mem_id);
+
+ if(!mem) return -1;
+
+ if(pos < 0) pos = (int) mem->size + pos;
+
+ if(pos < 0 || pos >= (int) mem->size) return -1;
+
+ return ((uint8_t *) mem->ptr)[pos];
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_obj_mem_del(obj_id_t mem_id, int pos)
+{
+ data_t *mem = gfx_obj_mem_ptr(mem_id);
+
+ if(!mem) return;
+
+ if(pos < 0) pos = (int) mem->size + pos;
+
+ if(pos < 0 || pos >= (int) mem->size) return;
+
+ obj_t *ptr = gfx_obj_ptr(mem_id);
+ if(ptr->flags.ro) return;
+
+ mem->size--;
+
+ if(mem->size > (unsigned) pos) {
+ gfx_memcpy(mem->ptr + pos, mem->ptr + pos + 1, mem->size - (unsigned) pos);
+ }
+
+ ((uint8_t *) mem->ptr)[mem->size] = 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_mem_set(obj_id_t mem_id, uint8_t val, int pos)
+{
+ obj_t *ptr = gfx_obj_ptr(mem_id);
+
+ if(!ptr || ptr->base_type != OTYPE_MEM || ptr->flags.ro) return 0;
+
+ data_t *mem = OBJ_DATA_FROM_PTR(ptr);
+
+ if(pos < 0) pos = (int) mem->size + pos;
+
+ if(pos < 0) return 0;
+
+ if(pos >= (int) mem->size) {
+ if(!gfx_obj_realloc(mem_id, (unsigned) pos + 1)) return 0;
+ }
+
+ ((uint8_t *) mem->ptr)[pos] = val;
+
+ return mem_id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_mem_iterate(obj_t *ptr, unsigned *idx, obj_id_t *id1, obj_id_t *id2)
+{
+ uint8_t *p = ptr->data.ptr;
+ unsigned len = ptr->data.size;
+
+ if(!p) {
+ GFX_ERROR(err_internal);
+ return 0;
+ }
+
+ if(*idx >= len) {
+ return 0;
+ }
+
+ *id1 = gfx_obj_num_new(p[*idx], t_int);
+ (*idx)++;
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_mem_dump(obj_t *ptr, dump_style_t style)
+{
+ if(!ptr) return 1;
+
+ uint8_t *p = ptr->data.ptr;
+ unsigned cnt, len = ptr->data.size;
+ char s[17];
+ int s_idx = 0;
+ unsigned sub_type = ptr->sub_type;
+ obj_id_t ref_id = ptr->data.ref_id;
+
+ if(!p) return 1;
+
+ if(!style.dump) {
+ if(!style.ref) {
+ if(style.inspect) {
+ if(ref_id) {
+ gfxboot_log("%s, ", gfx_obj_id2str(ref_id));
+ data_t *ref_data = gfx_obj_mem_ptr(ref_id);
+ if(ref_data && ptr->data.ptr >= ref_data->ptr) {
+ gfxboot_log("ofs 0x%x, ", (unsigned) (ptr->data.ptr - ref_data->ptr));
+ }
+ }
+ gfxboot_log("size %u", len);
+ // if(sub_type) gfxboot_log(", subtype %u", sub_type);
+ }
+
+ if(sub_type == t_string || sub_type == t_word || sub_type == t_ref) {
+ if(style.inspect) gfxboot_log(", ");
+ gfxboot_log("\"");
+ for(cnt = 0; cnt < len; cnt++) {
+ gfxboot_log("%c", p[cnt] < 0x20 ? ' ' : p[cnt]);
+ }
+ gfxboot_log("\"");
+ }
+ else {
+ return 0;
+ }
+
+ return 1;
+ }
+ }
+ else {
+ // if(len > 64) len = 64; // log at most this much
+ gfxboot_log(" ");
+ s_idx = 0;
+ for(cnt = 0; cnt < len; cnt++) {
+ gfxboot_log(" %02x", p[cnt]);
+ s[s_idx++] = (p[cnt] >= 0x20 && p[cnt] < 0x7f) ? (char) p[cnt] : '.';
+ if((cnt & 15) == 15) {
+ s[s_idx] = 0;
+ gfxboot_log(" %s", s);
+ s_idx = 0;
+ }
+ if((cnt & 15) == 15 && cnt + 1 < len) {
+ gfxboot_log("\n ");
+ }
+ }
+ if(s_idx) {
+ s[s_idx] = 0;
+ int spaces = 16 - s_idx;
+ while(spaces--) gfxboot_log(" ");
+ gfxboot_log(" %s", s);
+ }
+ gfxboot_log("\n");
+ }
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_mem_cmp(data_t *mem1, data_t *mem2)
+{
+ int result = 0;
+
+ unsigned len = mem1->size < mem2->size ? mem1->size : mem2->size;
+
+ if(len) {
+ if((result = gfx_memcmp(mem1->ptr, mem2->ptr, len))) {
+ result = result > 0 ? 1 : -1;
+ }
+ }
+
+ if(!result && mem1->size != mem2->size) {
+ result = mem1->size > mem2->size ? 1 : -1;
+ }
+
+ return result;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_mem_gc(obj_t *ptr)
+{
+ if(!ptr) return 0;
+
+ data_t *data = OBJ_DATA_FROM_PTR(ptr);
+
+ return gfx_obj_ref_dec_delay_gc(data->ref_id);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_mem_contains(obj_t *ptr, obj_id_t id)
+{
+ if(!ptr || !id) return 0;
+
+ data_t *data = OBJ_DATA_FROM_PTR(ptr);
+
+ return data->ref_id == id;
+}
diff --git a/gfxboot_num.c b/gfxboot_num.c
new file mode 100644
index 0000000..6af69b6
--- /dev/null
+++ b/gfxboot_num.c
@@ -0,0 +1,67 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// num
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_num_new(int64_t num, uint8_t sub_type)
+{
+ obj_id_t id = gfx_obj_new(OTYPE_NUM);
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(ptr) {
+ ptr->data.value = num;
+ ptr->sub_type = sub_type;
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int64_t *gfx_obj_num_ptr(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_NUM) return 0;
+
+ return &ptr->data.value;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int64_t *gfx_obj_num_subtype_ptr(obj_id_t id, uint8_t subtype)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_NUM || ptr->sub_type != subtype) return 0;
+
+ return &ptr->data.value;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_num_dump(obj_t *ptr, dump_style_t style)
+{
+ if(!ptr) return 1;
+
+ int64_t num = ptr->data.value;
+
+ if(style.ref) return 1;
+
+ switch(style.inspect) {
+ case 0:
+ gfxboot_log("%lld", (long long) num);
+ break;
+ case 1:
+ gfxboot_log("%lld (0x%llx)", (long long) num, (long long) num);
+ break;
+ }
+
+ return 1;
+}
+
+
diff --git a/gfxboot_obj.c b/gfxboot_obj.c
new file mode 100644
index 0000000..cf666d4
--- /dev/null
+++ b/gfxboot_obj.c
@@ -0,0 +1,570 @@
+#include
+#define WITH_TYPE_NAMES 1
+#include
+
+typedef struct {
+ unsigned data_is_ptr:1;
+ dump_function_t dump_function;
+ gc_function_t gc_function;
+ contains_function_t contains_function;
+ iterate_function_t iterate_function;
+} obj_descr_t;
+
+static unsigned gfx_obj_data_is_ptr(unsigned type);
+static dump_function_t gfx_obj_dump_function(unsigned type);
+static gc_function_t gfx_obj_gc_function(unsigned type);
+static iterate_function_t gfx_obj_iterate_function(unsigned type);
+static int gfx_obj_none_dump(obj_t *ptr, dump_style_t style);
+static int gfx_obj_invalid_dump(obj_t *ptr, dump_style_t style);
+static unsigned gfx_obj_none_gc(obj_t *ptr);
+static int gfx_obj_none_contains(obj_t *ptr, obj_id_t id);
+static unsigned gfx_obj_none_iterate(obj_t *ptr, unsigned *idx, obj_id_t *id1, obj_id_t *id2);
+
+obj_descr_t obj_descr[] = {
+ [OTYPE_NONE] = {
+ 0,
+ gfx_obj_none_dump,
+ 0,
+ 0,
+ 0
+ },
+ [OTYPE_MEM] = {
+ 1,
+ gfx_obj_mem_dump,
+ gfx_obj_mem_gc,
+ gfx_obj_mem_contains,
+ gfx_obj_mem_iterate
+ },
+ [OTYPE_OLIST] = {
+ 1,
+ gfx_obj_olist_dump,
+ 0,
+ 0,
+ 0
+ },
+ [OTYPE_FONT] = {
+ 1,
+ gfx_obj_font_dump,
+ gfx_obj_font_gc,
+ gfx_obj_font_contains,
+ 0
+ },
+ [OTYPE_CANVAS] = {
+ 1,
+ gfx_obj_canvas_dump,
+ 0,
+ 0,
+ 0
+ },
+ [OTYPE_ARRAY] = {
+ 1,
+ gfx_obj_array_dump,
+ gfx_obj_array_gc,
+ gfx_obj_array_contains,
+ gfx_obj_array_iterate
+ },
+ [OTYPE_HASH] = {
+ 1,
+ gfx_obj_hash_dump,
+ gfx_obj_hash_gc,
+ gfx_obj_hash_contains,
+ gfx_obj_hash_iterate
+ },
+ [OTYPE_CONTEXT] = {
+ 1,
+ gfx_obj_context_dump,
+ gfx_obj_context_gc,
+ gfx_obj_context_contains,
+ 0
+ },
+ [OTYPE_NUM] = {
+ 0,
+ gfx_obj_num_dump,
+ 0,
+ 0,
+ 0
+ },
+ [OTYPE_GSTATE] = {
+ 1,
+ gfx_obj_gstate_dump,
+ gfx_obj_gstate_gc,
+ gfx_obj_gstate_contains,
+ 0
+ },
+ [OTYPE_INVALID] = {
+ 0,
+ gfx_obj_invalid_dump,
+ 0,
+ 0,
+ 0
+ },
+};
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// object functions
+//
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_init()
+{
+ unsigned size = 0x2;
+
+ gfxboot_log("gfx_obj_init(%u)\n", size);
+
+ gfxboot_data->vm.olist.ptr = gfx_malloc(OBJ_OLIST_SIZE(size), OBJ_ID(0, 1));
+
+ olist_t *ol = gfxboot_data->vm.olist.ptr;
+ ol->max = size;
+
+ // create object for object list
+ obj_t *ptr = gfx_obj_ptr(gfxboot_data->vm.olist.id = gfx_obj_new(OTYPE_OLIST));
+
+ if(ptr) {
+ ptr->ref_cnt = -1u; // can't be deleted
+ ptr->data.ptr = gfxboot_data->vm.olist.ptr;
+ ptr->data.size = OBJ_OLIST_SIZE(size);
+ }
+
+ return ptr ? 0 : 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *gfx_obj_id2str(obj_id_t id)
+{
+ // corresponds to OTYPE_* defines
+ static const char *names[] = { "nil", "mem", "olist", "font", "canv", "array", "hash", "ctx", "num", "gstate" };
+ static char buf[64], buf2[32];
+ const char *s, *sub_type = "", *ro = "";
+ unsigned idx = OBJ_ID2IDX(id);
+ unsigned gen = OBJ_ID2GEN(id);
+
+ obj_t *ptr = gfx_obj_ptr_nocheck(id);
+ if(ptr && id) {
+ if(ptr->flags.ro) ro = ".ro";
+ if(ptr->sub_type) {
+ if(ptr->sub_type < sizeof type_name / sizeof *type_name) {
+ sub_type = type_name[ptr->sub_type];
+ }
+ else {
+ sub_type = "?";
+ }
+ }
+ s = ptr->base_type < sizeof names / sizeof *names ? names[ptr->base_type] : "???";
+ if(ptr->ref_cnt != -1u) {
+ gfxboot_snprintf(buf2, sizeof buf2, ".%d", ptr->ref_cnt);
+ }
+ else {
+ gfx_memcpy(buf2, ".*", sizeof ".*");
+ }
+ }
+ else {
+ s = id ? "?" : "nil";
+ *buf2 = 0;
+ }
+
+ gfxboot_snprintf(buf, sizeof buf, "#%u.%u%s.%s%s%s%s", idx, gen, buf2, s, *sub_type ? "." : "", sub_type, ro);
+
+ return buf;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_obj_dump(obj_id_t id, dump_style_t style)
+{
+ unsigned type;
+ obj_t *ptr;
+
+ ptr = gfx_obj_ptr(id);
+
+ if(!ptr) {
+ if(style.dump) {
+ gfxboot_log("= object dump (id %s): not found\n", gfx_obj_id2str(id));
+ }
+ else {
+ if(style.inspect) {
+ gfxboot_log("%s <%s>", gfx_obj_id2str(id), id ? "undef" : "nil");
+ }
+ else {
+ gfxboot_log("%s", id ? "undef" : "nil");
+ }
+ if(!style.no_nl) gfxboot_log("\n");
+ }
+
+ return;
+ }
+
+ if(style.dump && !style.no_head) {
+ gfxboot_log("== object dump (id %s) ==\n", gfx_obj_id2str(id));
+ }
+
+ type = ptr->base_type;
+
+ if(type) {
+ char *id_str = gfx_obj_id2str(id);
+ if(style.dump) {
+ gfxboot_log(" %s <", id_str);
+ gfx_obj_dump_function(type)(ptr, (dump_style_t) { .inspect = 1, .max = style.max });
+ if(gfxboot_data->vm.debug.show_pointer && ptr->flags.data_is_ptr) {
+ gfxboot_log(", data %p[%u]", ptr->data.ptr, ptr->data.size);
+ }
+ gfxboot_log(">\n");
+ gfx_obj_dump_function(type)(ptr, (dump_style_t) { .inspect = 1, .ref = 1, .dump = 1, .max = style.max });
+ }
+ else {
+ if(style.ref) {
+ gfx_obj_dump_function(type)(ptr, (dump_style_t) { .inspect = style.inspect, .ref = 1, .max = style.max });
+ }
+ else {
+ if(style.inspect) {
+ gfxboot_log("%s <", id_str);
+ gfx_obj_dump_function(type)(ptr, (dump_style_t) { .inspect = 1, .max = style.max });
+ gfxboot_log(">");
+ }
+ else {
+ if(!gfx_obj_dump_function(type)(ptr, (dump_style_t) { .inspect = 0, .max = style.max })) {
+ gfxboot_log("%s", id_str);
+ }
+ }
+ if(!style.no_nl) gfxboot_log("\n");
+ }
+ }
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Note: id1 and id2 have their reference count already increased!
+//
+unsigned gfx_obj_iterate(obj_id_t id, unsigned *idx, obj_id_t *id1, obj_id_t *id2)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr) {
+ *idx = 0;
+ return 0;
+ }
+
+ return gfx_obj_iterate_function(ptr->base_type)(ptr, idx, id1, id2);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_new(unsigned type)
+{
+ uint32_t u, u2;
+ olist_t *ol = gfxboot_data->vm.olist.ptr;
+ unsigned size = ol->max;
+ obj_t *ptr = ol->ptr;
+
+ type &= OBJ_TYPE_MASK;
+
+ if(type == OTYPE_NONE) return 0;
+
+ // ol->next = 0;
+
+ for(u = 0, u2 = ol->next; u < size; u++, u2++) {
+ if(u2 >= ol->max) u2 -= ol->max;
+ if(ptr[u2].base_type == OTYPE_NONE) {
+ ptr[u2].gen++;
+ if(!ptr[u2].gen) ptr[u2].gen++; // avoid generation count 0
+ ptr[u2].base_type = type;
+ ptr[u2].ref_cnt = 1;
+ ptr[u2].flags.data_is_ptr = gfx_obj_data_is_ptr(type);
+
+ ol->next = u2 + 1;
+ if(ol->next >= ol->max) ol->next -= ol->max;
+
+ return OBJ_ID(u2, ptr[u2].gen);
+ }
+ }
+
+ // object list too small, realloc enlarged one
+ size += (size >> 3) + 0x100;
+ if(gfx_obj_realloc(gfxboot_data->vm.olist.id, OBJ_OLIST_SIZE(size))) {
+ olist_t *ol2 = gfx_obj_olist_ptr(gfxboot_data->vm.olist.id);
+ if(ol2) {
+ ol2->max = size;
+ return gfx_obj_new(type);
+ }
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_alloc(unsigned type, uint32_t size)
+{
+ obj_id_t id = gfx_obj_new(type);
+ obj_t *optr = gfx_obj_ptr(id);
+
+ if(optr) {
+ optr->data.size = size;
+ optr->data.ptr = gfx_malloc(size, id);
+
+ if(!optr->data.ptr) {
+ // cleanup entry but keep generation counter
+ *optr = (obj_t) { gen:optr->gen };
+ id = 0;
+ }
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_realloc(obj_id_t id, uint32_t size)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+ void *ptr_old, *ptr_new;
+ uint32_t size_old;
+
+ if(ptr && ptr->flags.data_is_ptr) {
+ ptr_new = gfx_malloc(size, id);
+
+ if(ptr_new) {
+ size_old = ptr->data.size;
+ ptr_old = ptr->data.ptr;
+
+ ptr->data.ptr = ptr_new;
+ ptr->data.size = size;
+
+ if(size_old > size) size_old = size;
+ if(size_old && ptr_old) {
+ gfx_memcpy(ptr_new, ptr_old, size_old);
+ }
+
+ // store link to new object list
+ if(id == gfxboot_data->vm.olist.id) {
+ gfxboot_data->vm.olist.ptr = ptr_new;
+ }
+
+ gfx_free(ptr_old);
+ }
+ else {
+ id = 0;
+ }
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_ref_inc(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(ptr) {
+ if(gfxboot_data->vm.debug.trace.gc) gfxboot_log("GC: ++%s\n", gfx_obj_id2str(id));
+
+ if(ptr->ref_cnt != -1u) ptr->ref_cnt++;
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_obj_ref_dec(obj_id_t id)
+{
+ if(gfx_obj_ref_dec_delay_gc(id)) gfx_obj_run_gc();
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Decrement ref counter of id and add to gc list if ref count becomes 0.
+//
+// return:
+// 1: entry to gc list added
+// 0: nothing changed
+//
+unsigned gfx_obj_ref_dec_delay_gc(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr) return 0;
+
+ if(gfxboot_data->vm.debug.trace.gc) gfxboot_log("GC: --%s\n", gfx_obj_id2str(id));
+
+ if(ptr->ref_cnt && ptr->ref_cnt != -1u) {
+ ptr->ref_cnt--;
+ }
+
+ if(ptr->ref_cnt == 0) {
+ if(!gfxboot_data->vm.gc_list) {
+ gfxboot_data->vm.gc_list = gfx_obj_array_new(0);
+ }
+ if(gfxboot_data->vm.gc_list) {
+ gfx_obj_array_push(gfxboot_data->vm.gc_list, id, 0);
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_obj_run_gc()
+{
+ unsigned idx, type;
+ obj_id_t id;
+ obj_t *optr;
+ void *data_ptr;
+ array_t *a = gfx_obj_array_ptr(gfxboot_data->vm.gc_list);
+
+ if(!a) return;
+
+ for(idx = 0; idx < a->size; idx++) {
+ id = a->ptr[idx];
+ if(!id) continue;
+ optr = gfx_obj_ptr(id);
+ if(!optr) continue;
+ type = optr->base_type;
+ data_ptr = optr->data.ptr;
+
+ unsigned more_gc = gfx_obj_gc_function(type)(optr);
+
+ // ptr to gc_list might be outdated
+ if(more_gc) {
+ a = gfx_obj_array_ptr(gfxboot_data->vm.gc_list);
+ }
+
+ if(optr->flags.data_is_ptr) {
+ if(!optr->flags.nofree) {
+ gfx_free(data_ptr);
+ }
+ }
+
+ // keep generation counter
+ *optr = (obj_t) { gen:optr->gen };
+ }
+
+ // clear gc list
+ a->size = 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_t *gfx_obj_ptr_nocheck(obj_id_t id)
+{
+ uint32_t idx;
+ olist_t *ol;
+
+ idx = OBJ_ID2IDX(id);
+
+ ol = gfxboot_data->vm.olist.ptr;
+
+ if(idx >= ol->max) return 0;
+
+ return ol->ptr + idx;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_t *gfx_obj_ptr(obj_id_t id)
+{
+ obj_t *ptr;
+
+ if(!id) return 0;
+
+ ptr = gfx_obj_ptr_nocheck(id);
+
+ if(!ptr) return 0;
+
+ if(ptr->base_type == OTYPE_NONE || ptr->gen != OBJ_ID2GEN(id)) {
+ // nonexistent or outdated
+ return 0;
+ }
+
+ return ptr;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_data_is_ptr(unsigned type)
+{
+ if(type >= sizeof obj_descr/ sizeof *obj_descr) type = OTYPE_INVALID;
+
+ return obj_descr[type].data_is_ptr;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+dump_function_t gfx_obj_dump_function(unsigned type)
+{
+ if(type >= sizeof obj_descr/ sizeof *obj_descr) type = OTYPE_INVALID;
+
+ return obj_descr[type].dump_function;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+gc_function_t gfx_obj_gc_function(unsigned type)
+{
+ if(type >= sizeof obj_descr/ sizeof *obj_descr) type = OTYPE_INVALID;
+
+ return obj_descr[type].gc_function ?: gfx_obj_none_gc;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+contains_function_t gfx_obj_contains_function(unsigned type)
+{
+ if(type >= sizeof obj_descr/ sizeof *obj_descr) type = OTYPE_INVALID;
+
+ return obj_descr[type].contains_function ?: gfx_obj_none_contains;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+iterate_function_t gfx_obj_iterate_function(unsigned type)
+{
+ if(type >= sizeof obj_descr/ sizeof *obj_descr) type = OTYPE_INVALID;
+
+ return obj_descr[type].iterate_function ?: gfx_obj_none_iterate;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_none_dump(obj_t *ptr, dump_style_t style)
+{
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_invalid_dump(obj_t *ptr, dump_style_t style)
+{
+ gfxboot_log(" \n");
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_none_gc(obj_t *ptr)
+{
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_none_contains(obj_t *ptr, obj_id_t id)
+{
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+unsigned gfx_obj_none_iterate(obj_t *ptr, unsigned *idx, obj_id_t *id1, obj_id_t *id2)
+{
+ return 0;
+}
diff --git a/gfxboot_olist.c b/gfxboot_olist.c
new file mode 100644
index 0000000..4065829
--- /dev/null
+++ b/gfxboot_olist.c
@@ -0,0 +1,76 @@
+#include
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// obj list
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+obj_id_t gfx_obj_olist_new(unsigned max)
+{
+ obj_id_t id = gfx_obj_alloc(OTYPE_OLIST, OBJ_OLIST_SIZE(max));
+ olist_t *ol = gfx_obj_olist_ptr(id);
+
+ if(ol) {
+ ol->max = max;
+ }
+
+ return id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+olist_t *gfx_obj_olist_ptr(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_OLIST) return 0;
+
+ return (olist_t *) ptr->data.ptr;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_obj_olist_dump(obj_t *ptr, dump_style_t style)
+{
+ unsigned u, used;
+
+ if(!ptr) return 1;
+
+ olist_t *ol = ptr->data.ptr;
+
+ if(ptr->data.size != OBJ_OLIST_SIZE(ol->max)) {
+ gfxboot_log(" \n");
+
+ return 1;
+ }
+
+ for(u = used = 0; u < ol->max; u++) {
+ if(ol->ptr[u].base_type != OTYPE_NONE) used++;
+ }
+
+ if(!style.ref) {
+ if(style.inspect) {
+ gfxboot_log("size %u, next %u, max %u", used, ol->next, ol->max);
+
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ if(style.dump) gfxboot_log(" ");
+
+ for(u = 0; u < ol->max; u++) {
+ if(ol->ptr[u].base_type != OTYPE_NONE) {
+ obj_id_t id = OBJ_ID(u, ol->ptr[u].gen);
+ dump_style_t s = { .inspect = style.inspect, .no_head = 1 };
+ s.dump = style.dump && u;
+ gfx_obj_dump(id, s);
+ }
+ }
+
+ return 1;
+}
diff --git a/gfxboot_prim.c b/gfxboot_prim.c
new file mode 100644
index 0000000..f84a3c3
--- /dev/null
+++ b/gfxboot_prim.c
@@ -0,0 +1,4399 @@
+#include
+
+#define WITH_PRIM_NAMES 1
+#define WITH_PRIM_HEADERS 1
+#include
+
+static obj_id_t prim_array_start_id = 0;
+static obj_id_t prim_hash_start_id = 0;
+
+typedef enum {
+ op_sub, op_mul, op_div, op_mod, op_and, op_or, op_xor, op_min, op_max, op_shl, op_shr,
+ op_neg, op_not, op_abs
+} op_t;
+
+typedef enum {
+ op_eq, op_ne, op_gt, op_ge, op_lt, op_le, op_cmp
+} cmp_op_t;
+
+typedef struct {
+ obj_id_t id;
+ obj_t *ptr;
+} arg_t;
+
+static arg_t *gfx_arg_1(uint8_t type);
+static arg_t *gfx_arg_n(unsigned argc, uint8_t arg_types[]);
+static int is_true(obj_id_t id);
+static error_id_t do_op(op_t op, int64_t *result, int64_t val1, int64_t val2, unsigned sub_type);
+static void binary_op_on_stack(op_t op);
+static void unary_op_on_stack(op_t op);
+static void binary_cmp_on_stack(cmp_op_t op);
+static void gfx_prim_def_at(unsigned where);
+
+#define IS_NIL 0x80
+#define IS_RW 0x40
+#define TYPE_MASK 0x3f
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+arg_t *gfx_arg_1(uint8_t type)
+{
+ static arg_t argv[1];
+ unsigned is_nil = type & IS_NIL;
+ unsigned is_rw = type & IS_RW;
+ type &= TYPE_MASK;
+
+ GFX_ERROR(err_ok);
+
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+
+ return 0;
+ }
+
+ obj_id_t id = argv[0].id = pstack->ptr[pstack->size - 1];
+ obj_t *ptr = argv[0].ptr = gfx_obj_ptr(id);
+
+ if(
+ (ptr && (type == ptr->base_type || type == OTYPE_ANY)) ||
+ (id == 0 && (type == OTYPE_NONE || is_nil))
+ ) {
+ if(is_rw && ptr && ptr->flags.ro) {
+ GFX_ERROR(err_readonly);
+
+ return 0;
+ }
+
+ return argv;
+ }
+
+ GFX_ERROR(err_invalid_arguments);
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+arg_t *gfx_arg_n(unsigned argc, uint8_t arg_types[])
+{
+ unsigned i;
+ static arg_t argv[8];
+
+#if 0
+ if(argc >= sizeof argv / sizeof *argv) {
+ GFX_ERROR(err_internal);
+
+ return 0;
+ }
+#endif
+
+ GFX_ERROR(err_ok);
+
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < argc) {
+ GFX_ERROR(err_stack_underflow);
+
+ return 0;
+ }
+
+ unsigned stack_size = pstack->size;
+ obj_id_t *stack = pstack->ptr + stack_size - argc;
+
+ for(i = 0; i < argc; i++) {
+ obj_id_t id = argv[i].id = stack[i];
+ obj_t *ptr = argv[i].ptr = gfx_obj_ptr(id);
+
+ uint8_t type = arg_types[i];
+ unsigned is_nil = type & IS_NIL;
+ unsigned is_rw = type & IS_RW;
+ type &= TYPE_MASK;
+
+ if(
+ (ptr && (type == ptr->base_type || type == OTYPE_ANY)) ||
+ (id == 0 && (type == OTYPE_NONE || is_nil))
+ ) {
+ if(is_rw && ptr && ptr->flags.ro) {
+ GFX_ERROR(err_readonly);
+
+ return 0;
+ }
+ continue;
+ }
+
+ GFX_ERROR(err_invalid_arguments);
+
+ return 0;
+ }
+
+ return argv;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// return: 0 = failed, 1 = ok
+//
+int gfx_setup_dict()
+{
+ unsigned u;
+ obj_id_t prim_id;
+
+ gfx_obj_ref_dec(gfxboot_data->vm.program.dict);
+ gfxboot_data->vm.program.dict = gfx_obj_hash_new(0);
+ if(!gfxboot_data->vm.program.dict) return 0;
+
+ for(u = 0; u < sizeof prim_names / sizeof *prim_names ; u++) {
+ gfx_obj_hash_set(
+ gfxboot_data->vm.program.dict,
+ gfx_obj_const_mem_nofree_new((const uint8_t *) prim_names[u], gfx_strlen(prim_names[u]), t_ref, 0),
+ prim_id = gfx_obj_num_new(u, t_prim),
+ 0
+ );
+
+ switch(u) {
+ case prim_idx_array_start:
+ prim_array_start_id = prim_id;
+ break;
+ case prim_idx_hash_start:
+ prim_hash_start_id = prim_id;
+ break;
+ }
+ }
+
+ if(!prim_array_start_id || !prim_hash_start_id) return 0;
+
+ return 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+error_id_t gfx_run_prim(unsigned prim)
+{
+ if(prim > sizeof gfx_prim_list / sizeof *gfx_prim_list) {
+ GFX_ERROR(err_invalid_code);
+ return gfxboot_data->vm.error.id;
+ }
+
+ gfx_prim_list[prim]();
+
+ return gfxboot_data->vm.error.id;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// start code block
+//
+// group: code,array,hash
+//
+// ( -- code_1 )
+//
+// Put a reference to the following code block on the stack. The code block
+// starts after the opening `{` and extends to (and including) the matching
+// closing `}`.
+//
+// This special word is handled while converting the source code into binary code with `gfxboot-compile`.
+// For this reason, this is the only word that cannot be redefined.
+//
+// example:
+//
+// { "Hello!" show }
+//
+void gfx_prim_code_start()
+{
+ // will never be called
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// finish code block
+//
+// group: code,array,hash
+//
+// ( -- )
+//
+// This marks the end of a code block. When the code is executed, the
+// interpreter leaves the current execution context and returns to the
+// parent context.
+//
+// example:
+//
+// /hello { "Hello!" show } def
+// hello # print "Hello!"
+//
+void gfx_prim_code_end()
+{
+ context_t *context = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+
+ if(!context) {
+ GFX_ERROR(err_invalid_instruction);
+ return;
+ }
+
+ obj_id_t parent_id = context->parent_id;
+
+ switch(context->type) {
+ case t_ctx_block:
+ case t_ctx_func:
+ OBJ_ID_ASSIGN(gfxboot_data->vm.program.context, parent_id);
+ break;
+
+ case t_ctx_loop:
+ context->ip = 0;
+ break;
+
+ case t_ctx_repeat:
+ if(--context->index) {
+ context->ip = 0;
+ }
+ else {
+ OBJ_ID_ASSIGN(gfxboot_data->vm.program.context, parent_id);
+ }
+ break;
+
+ case t_ctx_for:
+ context->index += context->inc;
+ if(
+ (context->inc > 0 && context->index <= context->max) ||
+ (context->inc < 0 && context->index >= context->max)
+ ) {
+ context->ip = 0;
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(context->index, t_int), 0);
+ }
+ else {
+ OBJ_ID_ASSIGN(gfxboot_data->vm.program.context, parent_id);
+ }
+ break;
+
+ case t_ctx_forall:
+ ;
+ obj_id_t val1, val2;
+ unsigned idx = context->index;
+ unsigned items = gfx_obj_iterate(context->iterate_id, &idx, &val1, &val2);
+ context->index = idx;
+
+ if(items) {
+ context->ip = 0;
+ // note: reference counting for val1 and val2 has been done inside gfx_obj_iterate()
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, val1, 0);
+ if(items > 1) gfx_obj_array_push(gfxboot_data->vm.program.pstack, val2, 0);
+ }
+ else {
+ OBJ_ID_ASSIGN(gfxboot_data->vm.program.context, parent_id);
+ }
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_instruction);
+ break;
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// start array definition
+//
+// group: array,code,hash
+//
+// ( -- mark_1 )
+//
+// mark_1: array start marker
+//
+// Put array start marker on stack. Array definition is completed with ].
+//
+// example:
+//
+// [ 1 2 3 ] # array with 3 elements
+//
+void gfx_prim_array_start()
+{
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, prim_array_start_id, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// finish array definition
+//
+// group: array,code,hash
+//
+// ( mark_1 any_1 ... any_n -- array_1 )
+//
+// mark_1: array start marker
+// any_1 ... any_n: some elements
+// array_1: new array
+//
+// Search for mark_1 on the stack and put everything between mark_1 and TOS
+// into an array.
+//
+// example:
+//
+// [ 10 20 "some" "text" ] # array with 4 elements
+//
+void gfx_prim_array_end()
+{
+ array_t *a = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!a) return;
+
+ unsigned idx = a->size;
+ unsigned start_idx = -1u;
+
+ while(idx-- > 0) {
+ if(a->ptr[idx] == prim_array_start_id) {
+ start_idx = idx;
+ break;
+ }
+ }
+
+ if(start_idx == -1u) {
+ GFX_ERROR(err_no_array_start);
+ return;
+ }
+
+ obj_id_t array_id = gfx_obj_array_new(a->size - start_idx - 1);
+
+ if(!array_id) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ // no ref counting neded as the elements are just moved
+ for(idx = start_idx + 1; idx < a->size; idx++) {
+ gfx_obj_array_push(array_id, a->ptr[idx], 0);
+ }
+
+ a->size = start_idx + 1;
+ a->ptr[start_idx] = array_id;
+
+ gfx_obj_ref_dec(prim_array_start_id);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// start hash definition
+//
+// group: hash,array,code
+//
+// ( -- mark_1 )
+//
+// mark_1: hash start marker
+//
+// Put hash start marker on stack. Hash definition is completed with ).
+//
+// example:
+//
+// ( "foo" 10 "bar" 20 ) # hash with 2 keys "foo" and "bar"
+//
+void gfx_prim_hash_start()
+{
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, prim_hash_start_id, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// finish hash definition
+//
+// group: hash,array,code
+//
+// ( mark_1 any_1 ... any_n -- hash_1 )
+//
+// mark_1: array start marker
+// any_1 ... any_n: some key - value pairs
+// hash_1: new hash
+//
+// Search for mark_1 on the stack and put everything between mark_1 and TOS
+// into a hash. The elements are interpreted alternatingly as key and value.
+// If there's an odd number of elements on the stack, the last value is nil.
+//
+// example:
+//
+// ( "foo" 10 "bar" 20 ) # hash with 2 keys "foo" and "bar"
+//
+void gfx_prim_hash_end()
+{
+ array_t *a = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!a) return;
+
+ unsigned idx = a->size;
+ unsigned start_idx = -1u;
+
+ while(idx-- > 0) {
+ if(a->ptr[idx] == prim_hash_start_id) {
+ start_idx = idx;
+ break;
+ }
+ }
+
+ if(start_idx == -1u) {
+ GFX_ERROR(err_no_hash_start);
+ return;
+ }
+
+ obj_id_t hash_id = gfx_obj_hash_new((a->size - start_idx) / 2);
+
+ if(!hash_id) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ // no ref counting neded as the elements are just moved
+ for(idx = start_idx + 1; idx < a->size; idx += 2) {
+ obj_id_t key = a->ptr[idx];
+ obj_id_t val = idx + 1 < a->size ? a->ptr[idx + 1] : 0;
+ if(gfx_obj_mem_ptr(key)) {
+ gfx_obj_hash_set(hash_id, key, val, 0);
+ }
+ else {
+ GFX_ERROR(err_invalid_hash_key);
+ gfx_obj_ref_dec(hash_id);
+ return;
+ }
+ }
+
+ a->size = start_idx + 1;
+ a->ptr[start_idx] = hash_id;
+
+ gfx_obj_ref_dec(prim_hash_start_id);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// where: 0 (existing), 1 (local), 2 (global)
+//
+void gfx_prim_def_at(unsigned where)
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+
+ if(!ptr1 || ptr1->sub_type != t_ref) {
+ GFX_ERROR(err_invalid_instruction);
+ return;
+ }
+
+ obj_id_t dict_id = 0;
+
+ if(where == 0) {
+ obj_id_pair_t pair = gfx_lookup_dict(OBJ_DATA_FROM_PTR(ptr1));
+ if(pair.id1) {
+ dict_id = pair.id1;
+ }
+ else {
+ where = 1;
+ }
+ }
+
+ if(where) {
+ context_t *context = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+ if(!context) {
+ GFX_ERROR(err_internal);
+ return;
+ }
+
+ if(where == 2) {
+ while(context->parent_id) {
+ context = gfx_obj_context_ptr(context->parent_id);
+ if(!context) {
+ GFX_ERROR(err_internal);
+ return;
+ }
+ }
+ }
+
+ if(!context->dict_id) context->dict_id = gfx_obj_hash_new(0);
+
+ dict_id = context->dict_id;
+
+ if(!dict_id) {
+ GFX_ERROR(err_internal);
+ return;
+ }
+ }
+
+ gfx_obj_hash_set(dict_id, id1, id2, 1);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// define new word
+//
+// group: def
+//
+// ( word_1 any_1 -- )
+//
+// If word_1 does not exist, define word_1 in the current context.
+//
+// If word_1 does already exist, redefine word_1 in the context in which it is defined.
+//
+// example:
+// /x 100 def # define x as 100
+// /neg { -1 mul } def # define a function that negates its argument
+//
+void gfx_prim_def()
+{
+ gfx_prim_def_at(0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// define new local word
+//
+// group: def
+//
+// ( word_1 any_1 -- )
+//
+// Define word_1 in the current local context.
+//
+// example:
+// /foo 200 ldef # define local word foo as 200
+//
+void gfx_prim_ldef()
+{
+ gfx_prim_def_at(1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// define new global word
+//
+// group: def
+//
+// ( word_1 any_1 -- )
+//
+// Define word_1 in the global context.
+//
+// example:
+// /foo 300 gdef # define global word foo as 300
+//
+void gfx_prim_gdef()
+{
+ gfx_prim_def_at(2);
+}
+
+
+#if 0
+/* not really needed... */
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_prim_class()
+{
+ arg_t *argv;
+
+ argv = gfx_arg_n(3, (uint8_t [3]) { OTYPE_MEM, OTYPE_HASH, OTYPE_HASH | IS_RW });
+
+ if(argv) {
+ if(argv[0].ptr->sub_type != t_ref) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ context_t *context = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+ if(!context) {
+ GFX_ERROR(err_internal);
+ return;
+ }
+
+ while(context->parent_id) {
+ context = gfx_obj_context_ptr(context->parent_id);
+ if(!context) {
+ GFX_ERROR(err_internal);
+ return;
+ }
+ }
+
+ if(!context->dict_id) context->dict_id = gfx_obj_hash_new(0);
+
+ obj_id_t dict_id = context->dict_id;
+
+ gfx_obj_hash_set(dict_id, argv[0].id, argv[2].id, 1);
+
+ hash_t *hash = OBJ_HASH_FROM_PTR(argv[2].ptr);
+ obj_id_t old_id = hash->parent_id;
+ hash->parent_id = gfx_obj_ref_inc(argv[1].id);
+ gfx_obj_ref_dec(old_id);
+
+ gfx_obj_array_pop_n(3, gfxboot_data->vm.program.pstack, 1);
+ }
+}
+#endif
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// conditional execution
+//
+// group: if
+//
+// (bool_1 code_1 -- )
+// (int_1 code_1 -- )
+// (nil code_1 -- )
+// (any_1 code_1 -- )
+//
+// code_1: code block to run if condition evaluates to 'true'
+//
+// The condition is false for: boolean false, integer 0, or nil. In all other cases it is true.
+//
+// example:
+//
+// true { "ok" show } if # "ok"
+// 50 { "ok" show } if # "ok"
+// nil { "ok" show } if # shows nothing
+// "" { "ok" show } if # "ok"
+//
+void gfx_prim_if()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ if(!gfx_is_code(id2)) {
+ GFX_ERROR(err_invalid_code);
+ return;
+ }
+
+ if(is_true(id1)) {
+ obj_id_t context_id = gfx_obj_context_new(t_ctx_block);
+ context_t *context = gfx_obj_context_ptr(context_id);
+
+ if(!context) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ context->code_id = gfx_obj_ref_inc(id2);
+
+ context->parent_id = gfxboot_data->vm.program.context;
+ gfxboot_data->vm.program.context = context_id;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// conditional execution
+//
+// group: if
+//
+// (bool_1 code_1 code_2 -- )
+// (int_1 code_1 code_2 -- )
+// (nil code_1 code_2 -- )
+// (any_1 code_1 code_2 -- )
+//
+// code_1: code block to run if condition evaluates to 'true'
+// code_2: code block to run if condition evaluates to 'false'
+//
+// The condition is false for: boolean false, integer 0, or nil. In all other cases it is true.
+//
+// example:
+//
+// false { "ok" } { "bad" } ifelse show # "bad"
+// 20 { "ok" } { "bad" } ifelse show # "ok"
+// nil { "ok" } { "bad" } ifelse sho # "bad"
+// "" { "ok" } { "bad" } ifelse show # "ok"
+//
+void gfx_prim_ifelse()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 3) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 3];
+ obj_id_t id2 = pstack->ptr[pstack->size - 2];
+ obj_id_t id3 = pstack->ptr[pstack->size - 1];
+
+ if(!gfx_is_code(id2) || !gfx_is_code(id3)) {
+ GFX_ERROR(err_invalid_code);
+ return;
+ }
+
+ obj_id_t context_id = gfx_obj_context_new(t_ctx_block);
+ context_t *context = gfx_obj_context_ptr(context_id);
+
+ if(!context) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ context->code_id = gfx_obj_ref_inc(is_true(id1) ? id2 : id3);
+
+ context->parent_id = gfxboot_data->vm.program.context;
+ gfxboot_data->vm.program.context = context_id;
+
+ for(int i = 0; i < 3; i++) gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// endless loop
+//
+// group: if,loop
+//
+// ( code_1 -- )
+//
+// Repeat code_1 forever until you exit the loop explicitly.
+//
+// example:
+//
+// { "Help!" show } loop
+//
+void gfx_prim_loop()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 1];
+
+ if(!gfx_is_code(id1)) {
+ GFX_ERROR(err_invalid_code);
+ return;
+ }
+
+ obj_id_t context_id = gfx_obj_context_new(t_ctx_loop);
+ context_t *context = gfx_obj_context_ptr(context_id);
+
+ if(!context) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ context->code_id = gfx_obj_ref_inc(id1);
+
+ context->parent_id = gfxboot_data->vm.program.context;
+ gfxboot_data->vm.program.context = context_id;
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// repeat code block
+//
+// group: if,loop
+//
+// ( int_1 code_1 -- )
+//
+// Repeat code_1 int_1 times. If int_1 is less or equal to 0, code_1 is not run.
+//
+// example:
+//
+// 3 { "Help!" show } repeat # "Help!Help!Help!"
+//
+void gfx_prim_repeat()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ int64_t *count = gfx_obj_num_subtype_ptr(id1, t_int);
+
+ if(!count || !gfx_is_code(id2)) {
+ GFX_ERROR(err_invalid_code);
+ return;
+ }
+
+ if(*count > 0) {
+ obj_id_t context_id = gfx_obj_context_new(t_ctx_repeat);
+ context_t *context = gfx_obj_context_ptr(context_id);
+
+ if(!context) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ context->code_id = gfx_obj_ref_inc(id2);
+ context->index = *count;
+
+ context->parent_id = gfxboot_data->vm.program.context;
+ gfxboot_data->vm.program.context = context_id;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// run code block repeatedly, with counter
+//
+// group: if,loop
+//
+// ( int_1 int_2 int_3 code_1 -- )
+// int_1: start value
+// int_2: increment value
+// int_3: maximum value (inclusive)
+//
+// Run code_1 repeatedly and put the current counter value on the stack in every iteration.
+//
+// The counter starts with int_1 and is incremented by int_2 until it
+// reaches int_3. The code block is executed with the start value and then
+// as long as the counter is less than or equal to the maximum value.
+//
+// The increment may be negative. In that case the loop is executed as long as the counter
+// is greater than or equal to the maximum value.
+//
+// If the increment is 0, the loop is not executed.
+//
+// example:
+//
+// 0 1 4 { } for # 0 1 2 3 4
+// 0 -2 -5 { } for # 0 -2 -4
+//
+void gfx_prim_for()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 4) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 4];
+ obj_id_t id2 = pstack->ptr[pstack->size - 3];
+ obj_id_t id3 = pstack->ptr[pstack->size - 2];
+ obj_id_t id4 = pstack->ptr[pstack->size - 1];
+
+ int64_t *start = gfx_obj_num_subtype_ptr(id1, t_int);
+ int64_t *inc = gfx_obj_num_subtype_ptr(id2, t_int);
+ int64_t *max = gfx_obj_num_subtype_ptr(id3, t_int);
+
+ if(!start || !inc || !max || !gfx_is_code(id4)) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ int pop_count = 4;
+
+ if(
+ (*inc > 0 && *start <= *max) ||
+ (*inc < 0 && *start >= *max)
+ ) {
+ obj_id_t context_id = gfx_obj_context_new(t_ctx_for);
+ context_t *context = gfx_obj_context_ptr(context_id);
+
+ if(!context) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ context->code_id = gfx_obj_ref_inc(id4);
+ context->index = *start;
+ context->inc = *inc;
+ context->max = *max;
+
+ context->parent_id = gfxboot_data->vm.program.context;
+ gfxboot_data->vm.program.context = context_id;
+
+ pop_count--;
+ }
+
+ for(int i = 0; i < pop_count; i++) gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// loop over all elements
+//
+// group: if,loop
+//
+// ( array_1 code_1 -- )
+// ( hash_1 code_1 -- )
+// ( string_1 code_1 -- )
+//
+// Run code_1 for each element of array_1, hash_1, or string_1.
+//
+// For array_1 and string_1, each element is put on the stack and code_1 is run.
+//
+// For hash_1, each key and value pair are put on the stack and code_1 is run.
+// The hash keys are iterated in alphanumerical order.
+//
+// Note that string_1 is interpreted as a sequence of bytes, not UTF8-encoded characters.
+//
+// example:
+//
+// [ 10 20 30 ] { } forall # 10 20 30
+// ( "foo" 10 "bar" 20 ) { } forall # "bar" 20 "foo" 10
+// "ABC" { } forall # 65 66 67
+//
+void gfx_prim_forall()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+
+ if(
+ !gfx_is_code(id2) ||
+ !ptr1 ||
+ (ptr1->base_type != OTYPE_ARRAY && ptr1->base_type != OTYPE_HASH && ptr1->base_type != OTYPE_MEM)
+ ) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ obj_id_t val1, val2;
+ unsigned idx = 0;
+ unsigned items = gfx_obj_iterate(id1, &idx, &val1, &val2);
+
+ if(items) {
+ obj_id_t context_id = gfx_obj_context_new(t_ctx_forall);
+ context_t *context = gfx_obj_context_ptr(context_id);
+
+ if(!context) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ context->code_id = gfx_obj_ref_inc(id2);
+ context->index = idx;
+ context->iterate_id = gfx_obj_ref_inc(id1);
+
+ context->parent_id = gfxboot_data->vm.program.context;
+ gfxboot_data->vm.program.context = context_id;
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ // note: reference counting for val1 and val2 has been done inside gfx_obj_iterate()
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, val1, 0);
+ if(items > 1) gfx_obj_array_push(gfxboot_data->vm.program.pstack, val2, 0);
+ }
+ else {
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// leave loop/repeat/for/forall loop
+//
+// group: if,loop
+//
+// ( -- )
+//
+// Exit from current loop.
+//
+// example:
+//
+// 0 1 10 { dup 4 eq { exit } if } for # 0 1 2 3 4
+//
+void gfx_prim_exit()
+{
+ context_t *context = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+
+ if(!context) {
+ GFX_ERROR(err_internal);
+ return;
+ }
+
+ for(; context; context = gfx_obj_context_ptr(context->parent_id)) {
+ if(context->type == t_ctx_block) continue;
+
+ if(context->type == t_ctx_func) {
+ GFX_ERROR(err_no_loop_context);
+ return;
+ }
+
+ OBJ_ID_ASSIGN(gfxboot_data->vm.program.context, context->parent_id);
+
+ return;
+ }
+
+ GFX_ERROR(err_no_loop_context);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// leave current function
+//
+// group: if,loop
+//
+// Exit from currently running function.
+//
+// example:
+//
+// /foo { dup nil eq { return } if show } def
+// "abc" foo # shows "abc"
+// nil foo # does nothing
+//
+void gfx_prim_return()
+{
+ context_t *context = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+
+ if(!context) {
+ GFX_ERROR(err_internal);
+ return;
+ }
+
+ for(; context; context = gfx_obj_context_ptr(context->parent_id)) {
+ if(context->type == t_ctx_func) break;
+ }
+
+ if(!context) gfxboot_data->vm.program.stop = 1;
+
+ OBJ_ID_ASSIGN(gfxboot_data->vm.program.context, context ? context->parent_id : 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// create or duplicate string
+//
+// group: mem
+//
+// ( int_1 -- string_1 )
+// int_1: length
+// string_1: new string with length int_1
+// ( string_2 -- string_3 )
+// string_2: string to duplicate
+// string_3: copy of string_2
+//
+// There are two variants: given a number, a string of that length is
+// created and initialized with zeros; given a string, a copy of that string is created.
+//
+// int_1 may be 0 to create a zero-length string.
+//
+// Note: duplication works for all string-like objects. For example for word references and even code blocks.
+//
+// example:
+//
+// 2 string # creates an empty string of length 2: "\x00\x00"
+// "abc" string # creates a copy of "abc"
+//
+// # even this works:
+// /abc mem # a copy of /abc
+// { 10 20 } mem # a copy of the code block { 10 20 }
+//
+void gfx_prim_string()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+ if(!ptr1) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ obj_id_t val_id = 0;
+
+ switch(ptr1->base_type) {
+ case OTYPE_NUM:
+ {
+ int64_t value = OBJ_VALUE_FROM_PTR(ptr1);
+
+ if(value < 0 || value >= (1ll << 32)) {
+ GFX_ERROR(err_invalid_range);
+ return;
+ }
+
+ val_id = gfx_obj_mem_new(value);
+
+ if(!val_id) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+ }
+ break;
+
+ case OTYPE_MEM:
+ val_id = gfx_obj_mem_dup(id1, 0);
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ // we did the ref counting already above
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, val_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get array, hash, or string element
+//
+// group: get
+//
+// ( array_1 int_1 -- )
+// array_1: array to modify
+// int_1: element index
+//
+// ( hash_1 string_1 -- )
+// hash_1: hash to modify
+// string_1: key
+//
+// ( string_2 int_2 -- )
+// string_2: string to modify
+// int_2: element index
+//
+// Read the respective element of array_1, hash_1, or string_2.
+//
+// example:
+//
+// [ 10 20 30 ] 2 get # 30
+// ( "foo" 10 "bar" 20 ) "foo" get # 10
+// "ABC" 1 get # 66
+//
+void gfx_prim_get()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+ if(!ptr1) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ obj_id_t val = 0;
+
+ switch(ptr1->base_type) {
+ case OTYPE_ARRAY:
+ {
+ int64_t *idx = gfx_obj_num_ptr(id2);
+ if(!idx) {
+ GFX_ERROR(err_invalid_range);
+ return;
+ }
+ val = gfx_obj_array_get(id1, *idx);
+ gfx_obj_ref_inc(val);
+ }
+ break;
+
+ case OTYPE_HASH:
+ {
+ data_t *key = gfx_obj_mem_ptr(id2);
+ if(!key) {
+ GFX_ERROR(err_invalid_hash_key);
+ return;
+ }
+ val = gfx_obj_hash_get(id1, key).id2;
+ gfx_obj_ref_inc(val);
+ }
+ break;
+
+ case OTYPE_MEM:
+ {
+ int64_t *idx = gfx_obj_num_ptr(id2);
+ if(!idx) {
+ GFX_ERROR(err_invalid_range);
+ return;
+ }
+ int i = gfx_obj_mem_get(id1, *idx);
+ if(i != -1) {
+ val = gfx_obj_num_new(i, t_int);
+ }
+ }
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ // we did the ref counting already above
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, val, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_prim_get_x(data_t *key)
+{
+ arg_t *argv;
+
+ argv = gfx_arg_1(OTYPE_HASH);
+
+ if(argv) {
+ obj_id_pair_t pair = gfx_obj_hash_get(argv[0].id, key);
+ if(!pair.id1) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+ gfx_exec_id(argv[0].id, pair.id2, 1);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_prim_put_x(obj_id_t key)
+{
+ arg_t *argv;
+
+ argv = gfx_arg_n(2, (uint8_t [2]) { OTYPE_HASH | IS_RW, OTYPE_ANY | IS_NIL });
+
+ if(argv) {
+ if(!gfx_obj_hash_set(argv[0].id, key, argv[1].id, 1)) {
+ GFX_ERROR(err_invalid_hash_key);
+ return;
+ }
+
+ gfx_obj_array_pop_n(2, gfxboot_data->vm.program.pstack, 1);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set array, hash, or string element
+//
+// group: get
+//
+// ( array_1 int_1 any_1 -- )
+// array_1: array to modify
+// int_1: element index
+// any_1: new value
+//
+// ( hash_1 string_1 any_2 -- )
+// hash_1: hash to modify
+// string_1: key
+// any_2: new value
+//
+// ( string_2 int_2 int_3 -- )
+// string_2: string to modify
+// int_2: element index
+// int_3: new value
+//
+// Set the respective element of array_1, hash_1, or string_2.
+//
+// Note that string constants are read-only and cannot be modified.
+//
+// example:
+//
+// /x [ 10 20 30 ] def
+// x 2 40 put # x is now [ 10 20 40 ]
+//
+// /y ( "foo" 10 "bar" 20 ) def
+// y "bar" 40 put # y is now ( "foo" 10 "bar" 40 )
+//
+// /z "ABC" mem def # mem is needed to create a writable copy
+// z 1 68 put # z is now "ADC"
+//
+void gfx_prim_put()
+{
+ arg_t *argv;
+
+ argv = gfx_arg_n(3, (uint8_t [3]) { OTYPE_ARRAY | IS_RW, OTYPE_NUM, OTYPE_ANY | IS_NIL });
+
+ if(argv) {
+ int pos = OBJ_VALUE_FROM_PTR(argv[1].ptr);
+
+ if(!gfx_obj_array_set(argv[0].id, argv[2].id, pos, 1)) {
+ GFX_ERROR(err_invalid_range);
+ return;
+ }
+
+ gfx_obj_array_pop_n(3, gfxboot_data->vm.program.pstack, 1);
+ return;
+ }
+
+ if(gfxboot_data->vm.error.id == err_readonly) return;
+
+ argv = gfx_arg_n(3, (uint8_t [3]) { OTYPE_HASH | IS_RW, OTYPE_MEM, OTYPE_ANY | IS_NIL });
+
+ if(argv) {
+ if(!gfx_obj_hash_set(argv[0].id, argv[1].id, argv[2].id, 1)) {
+ GFX_ERROR(err_invalid_hash_key);
+ return;
+ }
+
+ gfx_obj_array_pop_n(3, gfxboot_data->vm.program.pstack, 1);
+ return;
+ }
+
+ if(gfxboot_data->vm.error.id == err_readonly) return;
+
+ argv = gfx_arg_n(3, (uint8_t [3]) { OTYPE_MEM | IS_RW, OTYPE_NUM, OTYPE_NUM });
+
+ if(argv) {
+ uint8_t val = OBJ_VALUE_FROM_PTR(argv[2].ptr);
+ int pos = OBJ_VALUE_FROM_PTR(argv[1].ptr);
+
+ if(!gfx_obj_mem_set(argv[0].id, val, pos)) {
+ GFX_ERROR(err_invalid_range);
+ return;
+ }
+
+ gfx_obj_array_pop_n(3, gfxboot_data->vm.program.pstack, 1);
+ return;
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// delete an array, hash, or string element
+//
+// group: get
+//
+// ( array_1 int_1 -- )
+// array_1: array to modify
+// int_1: element index
+//
+// ( hash_1 string_1 -- )
+// hash_1: hash to modify
+// string_1: key
+//
+// ( string_2 int_2 -- )
+// string_2: string to modify
+// int_2: element index
+//
+// Delete the respective element of array_1, hash_1, or string_2. The length
+// of array_1 andstring_2 will be reduced by 1.
+//
+// Note that string constants are read-only and cannot be modified.
+//
+// example:
+//
+// /x [ 10 20 30 ] def
+// x 1 delete # x is now [ 10 30 ]
+//
+// /y ( "foo" 10 "bar" 20 ) def
+// y "foo" delete # y is now ( "bar" 20 )
+//
+// /z "ABC" mem def # mem is needed to create a writable copy
+// z 1 delete # z is now "AC"
+//
+void gfx_prim_delete()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+ if(!ptr1) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ if(ptr1->flags.ro) {
+ GFX_ERROR(err_readonly);
+ return;
+ }
+
+ switch(ptr1->base_type) {
+ case OTYPE_ARRAY:
+ {
+ int64_t *idx = gfx_obj_num_ptr(id2);
+ if(!idx) {
+ GFX_ERROR(err_invalid_range);
+ return;
+ }
+ gfx_obj_array_del(id1, *idx, 1);
+ }
+ break;
+
+ case OTYPE_HASH:
+ {
+ data_t *key = gfx_obj_mem_ptr(id2);
+ if(!key) {
+ GFX_ERROR(err_invalid_hash_key);
+ return;
+ }
+ gfx_obj_hash_del(id1, id2, 1);
+ }
+ break;
+
+ case OTYPE_MEM:
+ {
+ int64_t *idx = gfx_obj_num_ptr(id2);
+ if(!idx) {
+ GFX_ERROR(err_invalid_range);
+ return;
+ }
+ gfx_obj_mem_del(id1, *idx);
+ }
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get size of array, hash, or string
+//
+// group: get
+//
+// ( array_1 -- int_1 )
+// int_1: number of elements in array_1
+//
+// ( hash_1 -- int_2 )
+// int_2: number of key - value pairs in hash_1
+//
+// ( string_1 -- int_3 )
+// int_3: number of bytes in string_1
+//
+// Put the length of array_1, hash_1, or string_1 on the stack.
+//
+// example:
+//
+// [ 10 20 30 ] length # 3
+// ( "foo" 10 "bar" 20 ) length # 2
+// "ABC" length # 3
+//
+void gfx_prim_length()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+ if(!ptr1) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ int64_t val = 0;
+
+ switch(ptr1->base_type) {
+ case OTYPE_ARRAY:
+ val = OBJ_ARRAY_FROM_PTR(ptr1)->size;
+ break;
+
+ case OTYPE_HASH:
+ val = OBJ_HASH_FROM_PTR(ptr1)->size;
+ break;
+
+ case OTYPE_MEM:
+ val = OBJ_DATA_FROM_PTR(ptr1)->size;
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ obj_id_t val_id = gfx_obj_num_new(val, t_int);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ // we did the ref counting already above
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, val_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// duplicate TOS
+//
+// group: stack
+//
+// ( any_1 -- any_1 any_1 )
+//
+// Duplicate the top-of-stack element.
+//
+// example:
+//
+// 10 dup # 10 10
+//
+void gfx_prim_dup()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 1];
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, id1, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// remove TOS
+//
+// group: stack
+//
+// ( any_1 -- )
+//
+// Remove the top-of-stack element.
+//
+// example:
+//
+// 10 20 pop # 10
+//
+void gfx_prim_pop()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// swap upper two stack elements
+//
+// group: stack
+//
+// ( any_1 any_2 -- any_2 any_1 )
+//
+// Swap the two topmost stack elements.
+//
+// example:
+//
+// 10 20 exch # 20 10
+//
+void gfx_prim_exch()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ gfx_obj_array_set(gfxboot_data->vm.program.pstack, id2, (int) pstack->size - 2, 0);
+ gfx_obj_array_set(gfxboot_data->vm.program.pstack, id1, (int) pstack->size - 1, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// rotate upper three stack elements
+//
+// group: stack
+//
+// ( any_1 any_2 any_3 -- any_2 any_3 any_1 )
+//
+// Rotate any_1 to the top-of-stack.
+//
+// example:
+//
+// 10 20 30 rot # 20 30 10
+//
+void gfx_prim_rot()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 3) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 3];
+ obj_id_t id2 = pstack->ptr[pstack->size - 2];
+ obj_id_t id3 = pstack->ptr[pstack->size - 1];
+
+ gfx_obj_array_set(gfxboot_data->vm.program.pstack, id2, (int) pstack->size - 3, 0);
+ gfx_obj_array_set(gfxboot_data->vm.program.pstack, id3, (int) pstack->size - 2, 0);
+ gfx_obj_array_set(gfxboot_data->vm.program.pstack, id1, (int) pstack->size - 1, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// rotate stack elements
+//
+// group: stack
+//
+// ( any_1 ... any_n int_1 int_2 -- any_x ... any_y )
+// int_1: number of stack elements to rotate (equal to index n)
+// int_2: rotation amount
+//
+// Rotate the n elements any_1 ... any_n. The new positions are calculated as follows:
+//
+// x = (1 - int_2) mod int_1
+//
+// y = (n - int_2) mod int_1
+//
+// This can be seen as rotating int_1 elements up by int_2 resp. down by -int_2.
+//
+// example:
+//
+// 10 20 30 40 50 5 2 roll # 40 50 10 20 30
+//
+// /rot { 3 -1 roll } def # definition of 'rot'
+//
+void gfx_prim_roll()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ int64_t *xlen = gfx_obj_num_ptr(id1);
+ int64_t *xofs = gfx_obj_num_ptr(id2);
+
+ if(!xlen || *xlen < 0 || !xofs) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ int len = *xlen;
+ int ofs = *xofs;
+
+ if((unsigned) len + 2 > pstack->size) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ if(!len) return;
+
+ ofs %= len;
+ if(ofs < 0) ofs += len;
+ if(!ofs) return;
+
+ ofs = len - ofs;
+
+ // FIXME: it's a bit inefficient this way
+ while(ofs--) {
+ obj_id_t tmp_id = pstack->ptr[(int) pstack->size - len];
+ for(int i = 0; i < len - 1; i++) {
+ pstack->ptr[(int) pstack->size - len + i] = pstack->ptr[(int) pstack->size - len + i + 1];
+ }
+ pstack->ptr[pstack->size - 1] = tmp_id;
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// copy TOS-1 to TOS
+//
+// group: stack
+//
+// ( any_1 any_2 -- any_1 any_2 any_1 )
+//
+// Put a copy of the second-from-top element on the top-of-stack.
+//
+// example:
+//
+// 10 20 over # 10 20 10
+//
+void gfx_prim_over()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, id1, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// copy stack element
+//
+// group: stack
+//
+// ( any_n ... any_0 int_1 -- any_n ... any_0 any_n )
+// int_1: element position on stack (n is equal to int_1)
+//
+// Copy the int_1-th-from-top element on the top-of-stack.
+//
+// example:
+//
+// 10 20 30 40 3 index # 10 20 30 40 10
+//
+// /dup { 0 index } def # definition of 'dup'
+//
+// /over { 1 index } def # definition of 'over'
+//
+void gfx_prim_index()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 1];
+ int64_t *idx = gfx_obj_num_ptr(id1);
+
+ if(!idx || *idx < 0) {
+ GFX_ERROR(err_invalid_range);
+ return;
+ }
+
+ if(*idx + 2 > pstack->size) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id2 = pstack->ptr[pstack->size - 2 - *idx];
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, id2, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_exec_id(obj_id_t dict_id, obj_id_t id, int on_stack)
+{
+ int64_t *val;
+
+ if((val = gfx_obj_num_subtype_ptr(id, t_prim))) {
+ if(on_stack) gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_run_prim(*val);
+ }
+ else if(gfx_obj_mem_subtype_ptr(id, t_code)) {
+ obj_id_t context_id = gfx_obj_context_new(t_ctx_func);
+ context_t *context = gfx_obj_context_ptr(context_id);
+ if(!context) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+ context->code_id = gfx_obj_ref_inc(id);
+
+ context->parent_id = gfxboot_data->vm.program.context;
+ gfxboot_data->vm.program.context = context_id;
+
+ if(dict_id) {
+ context->dict_id = gfx_obj_hash_new(0);
+ hash_t *hash = gfx_obj_hash_ptr(context->dict_id);
+ if(!hash) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+ hash->parent_id = gfx_obj_ref_inc(dict_id);
+ }
+
+ if(on_stack) gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ }
+ else {
+ gfx_obj_ref_inc(id);
+ if(on_stack) gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, id, 0);
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// execute object
+//
+// group: loop
+//
+// ( ref_1 -- )
+// ref_1: word reference
+//
+// ( code_1 -- )
+// code_1: code block
+//
+// Executes the given code block or looks up and executes the word reference.
+//
+// example:
+//
+// { 10 20 } exec # 10 20
+//
+// /foo "abc" def
+// foo # "abc"
+// /foo exec # "abc"
+//
+void gfx_prim_exec()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id = pstack->ptr[pstack->size - 1];
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr) return;
+
+ if(
+ ptr->base_type == OTYPE_MEM &&
+ (ptr->sub_type == t_word || ptr->sub_type == t_ref)
+ ) {
+ id = gfx_lookup_dict(OBJ_DATA_FROM_PTR(ptr)).id2;
+ }
+
+ gfx_exec_id(0, id, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// addition
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 + int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 xor bool_2
+//
+// ( array_1 array_2 -- array_3 )
+// array_3: array_2 appended to array_1
+//
+// ( hash_1 hash_2 -- hash_3 )
+// hash_3: joined hash_1 and hash_2
+//
+// ( string_1 string_2 -- string_3 )
+// string_3: string_2 appended to string_1
+//
+// Add two numbers, or concatenate two arrays, or join two hashes, or concatenate two strings.
+//
+// For boolean 1 bit arithmetic this is equivalent to 'xor'.
+//
+// example:
+//
+// 10 20 add # 30
+// true true add # false
+// [ 10 20 ] [ 30 40 ] add # [ 10 20 30 40 ]
+// ( "foo" 10 ) ( "bar" 20 ) add # ( "bar" 20 "foo" 10 )
+// "abc" "def" add # "abcdef"
+//
+void gfx_prim_add()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+ obj_t *ptr2 = gfx_obj_ptr(id2);
+
+ // FIXME: maybe allow 'mem_ref + int'?
+
+ if(!ptr1 || !ptr2 || ptr1->base_type != ptr2->base_type) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ obj_id_t result_id = 0;
+
+ switch(ptr1->base_type) {
+ case OTYPE_NUM:
+ {
+ int64_t result = ptr1->data.value + ptr2->data.value;
+ if(ptr1->sub_type == t_bool) result &= 1;
+ result_id = gfx_obj_num_new(result, ptr1->sub_type);
+ }
+ break;
+
+ case OTYPE_MEM:
+ {
+ result_id = gfx_obj_mem_new(ptr1->data.size + ptr2->data.size);
+ obj_t *result_ptr = gfx_obj_ptr(result_id);
+ if(!result_ptr) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+ result_ptr->sub_type = ptr1->sub_type;
+ gfx_memcpy(result_ptr->data.ptr, ptr1->data.ptr, ptr1->data.size);
+ gfx_memcpy(result_ptr->data.ptr + ptr1->data.size, ptr2->data.ptr, ptr2->data.size);
+ }
+ break;
+
+ case OTYPE_ARRAY:
+ {
+ array_t *array1 = gfx_obj_array_ptr(id1);
+ array_t *array2 = gfx_obj_array_ptr(id2);
+ if(array1 && array2) {
+ result_id = gfx_obj_array_new(array1->size + array2->size + 0x10);
+ }
+ if(!result_id) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+ obj_id_t val;
+ unsigned idx = 0;
+ while(gfx_obj_iterate(id1, &idx, &val, 0)) {
+ // note: reference counting for val has been done inside gfx_obj_iterate()
+ gfx_obj_array_push(result_id, val, 0);
+ }
+ idx = 0;
+ while(gfx_obj_iterate(id2, &idx, &val, 0)) {
+ // note: reference counting for val has been done inside gfx_obj_iterate()
+ gfx_obj_array_push(result_id, val, 0);
+ }
+ }
+ break;
+
+ case OTYPE_HASH:
+ {
+ hash_t *hash1 = gfx_obj_hash_ptr(id1);
+ hash_t *hash2 = gfx_obj_hash_ptr(id2);
+ if(hash1 && hash2) {
+ result_id = gfx_obj_hash_new(hash1->size + hash2->size + 0x10);
+ }
+ if(!result_id) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+ obj_id_t key, val;
+ unsigned idx = 0;
+ while(gfx_obj_iterate(id1, &idx, &key, &val)) {
+ // note: reference counting for key & val has been done inside gfx_obj_iterate()
+ gfx_obj_hash_set(result_id, key, val, 0);
+ }
+ idx = 0;
+ while(gfx_obj_iterate(id2, &idx, &key, &val)) {
+ // note: reference counting for key & val has been done inside gfx_obj_iterate()
+ gfx_obj_hash_set(result_id, key, val, 0);
+ }
+ }
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, result_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// subtraction
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 - int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 xor bool_2
+//
+// Subtract int_2 from int_1.
+//
+// For boolean 1 bit arithmetic this is equivalent to 'xor'.
+//
+// example:
+//
+// 100 30 sub # 70
+// false true sub # true
+//
+void gfx_prim_sub()
+{
+ binary_op_on_stack(op_sub);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// multiplication
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 * int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 and bool_2
+//
+// Multiply int_1 by int_2.
+//
+// For boolean 1 bit arithmetic this is equivalent to 'and'.
+//
+// example:
+//
+// 20 30 mul # 600
+// true false mul # false
+//
+void gfx_prim_mul()
+{
+ binary_op_on_stack(op_mul);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// division
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 / int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 / bool_2
+//
+// Divide int_1 by int_2.
+//
+// You can do a 1 bit division with boolean values. Note that this will run
+// into a division by zero exception if bool_2 is false.
+//
+// example:
+//
+// 200 30 div # 6
+// true true div # true
+//
+void gfx_prim_div()
+{
+ binary_op_on_stack(op_div);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// remainder
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 % int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 / bool_2
+//
+// int_3 is the remainder dividing int_1 by int_2.
+//
+// You can get the remainder from a 1 bit division with boolean values. Note
+// that this will run into a division by zero exception if bool_2 is false.
+//
+// example:
+//
+// 200 30 mod # 20
+// true true mod # false
+//
+void gfx_prim_mod()
+{
+ binary_op_on_stack(op_mod);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// negation
+//
+// group: calc
+//
+// ( int_1 -- int_2 )
+// int_2: -int_1
+//
+// ( bool_1 -- bool_2 )
+// bool_2: -bool_1
+//
+// Negate int_1 (change sign).
+//
+// For boolean 1 bit arithmetic the value is unchanged (this is not a 'not' operation).
+//
+// example:
+//
+// 20 neg # -20
+// true neg # true
+//
+void gfx_prim_neg()
+{
+ unary_op_on_stack(op_neg);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// absolute value
+//
+// group: calc
+//
+// ( int_1 -- int_2 )
+// int_2: |int_1|
+//
+// ( bool_1 -- bool_2 )
+// bool_2: bool_1
+//
+// Absolute value of int_1 (change sign if int_1 is negative).
+//
+// example:
+//
+// For boolean 1 bit arithmetic the value is unchanged.
+//
+// -20 abs # 20
+// true abs # true
+//
+void gfx_prim_abs()
+{
+ unary_op_on_stack(op_abs);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// minimum
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: minimum(int_1, int_2)
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 and bool_2
+//
+// int_3 is the smaller value of int_1 and int_2.
+//
+// For boolean 1 bit arithmetic this is equivalent to 'and'
+//
+// example:
+//
+// 10 20 min # 10
+// true false min # false
+//
+void gfx_prim_min()
+{
+ binary_op_on_stack(op_min);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// maximum
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: maximum(int_1, int_2)
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 or bool_2
+//
+// int_3 is the larger value of int_1 and int_2.
+//
+// For boolean 1 bit arithmetic this is equivalent to 'or'
+//
+// example:
+//
+// 10 20 max # 20
+// true false max # true
+//
+void gfx_prim_max()
+{
+ binary_op_on_stack(op_max);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// and
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 and int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 and bool_2
+//
+// example:
+//
+// 15 4 and # 4
+// true false and # false
+//
+void gfx_prim_and()
+{
+ binary_op_on_stack(op_and);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// or
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 or int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 or bool_2
+//
+// example:
+//
+// 15 4 or # 15
+// true false or # true
+//
+void gfx_prim_or()
+{
+ binary_op_on_stack(op_or);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// exclusive or
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 xor int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 xor bool_2
+//
+// example:
+//
+// 15 4 xor # 11
+// true false or # true
+//
+void gfx_prim_xor()
+{
+ binary_op_on_stack(op_xor);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// not
+//
+// group: calc
+//
+// ( int_1 -- int_2 )
+// int_2: -int_1 - 1
+//
+// ( bool_1 -- bool_2 )
+// bool_2: !bool_1
+//
+// example:
+//
+// 20 not # -21
+// true not # false
+//
+void gfx_prim_not()
+{
+ unary_op_on_stack(op_not);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// shift left
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 << int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 and !bool_2
+//
+// example:
+//
+// 1 4 shl # 16
+// true false shl # true
+//
+void gfx_prim_shl()
+{
+ binary_op_on_stack(op_shl);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// shift right
+//
+// group: calc
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 >> int_2
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 and !bool_2
+//
+// example:
+//
+// 16 4 shr # 1
+// true false shr # true
+//
+void gfx_prim_shr()
+{
+ binary_op_on_stack(op_shr);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// equal
+//
+// group: cmp
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 == bool_2
+//
+// ( int_1 int_2 -- bool_4 )
+// bool_4: int_1 == int_2
+//
+// ( string_1 string_2 -- bool_5 )
+// bool_5: string_1 == string_2
+//
+// ( any_1 any_2 -- bool_6 )
+// bool_6: any_1 == any_2
+//
+// For pairs of booleans, integers, and strings the values are compared. For all
+// other combinations the internal object id is compared.
+//
+// example:
+//
+// 10 20 eq # false
+// true false eq # false
+// "abc" "abc" eq # true
+// [ 10 20 ] [ 10 20 ] eq # false
+// 0 false eq # false
+// 0 nil eq # false
+// "abc" [ 10 ] eq # false
+//
+// /foo [ 10 20 ] def
+// /bar foo def
+// foo bar eq # true
+//
+void gfx_prim_eq()
+{
+ binary_cmp_on_stack(op_eq);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// not equal
+//
+// group: cmp
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 != bool_2
+//
+// ( int_1 int_2 -- bool_4 )
+// bool_4: int_1 != int_2
+//
+// ( string_1 string_2 -- bool_5 )
+// bool_5: string_1 != string_2
+//
+// ( any_1 any_2 -- bool_6 )
+// bool_6: any_1 != any_2
+//
+// For pairs of booleans, integers, and strings the values are compared. For all
+// other combinations the internal object id is compared.
+//
+// example:
+//
+// 10 20 ne # true
+// true false ne # true
+// "abc" "abc" ne # false
+// [ 10 20 ] [ 10 20 ] ne # true
+// 0 false ne # true
+// 0 nil ne # true
+// "abc" [ 10 ] ne # true
+//
+// /foo [ 10 20 ] def
+// /bar foo def
+// foo bar ne # false
+//
+void gfx_prim_ne()
+{
+ binary_cmp_on_stack(op_ne);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// greater than
+//
+// group: cmp
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 > bool_2
+//
+// ( int_1 int_2 -- bool_4 )
+// bool_4: int_1 > int_2
+//
+// ( string_1 string_2 -- bool_5 )
+// bool_5: string_1 > string_2
+//
+// ( any_1 any_2 -- bool_6 )
+// bool_6: any_1 > any_2
+//
+// For pairs of booleans, integers, and strings the values are compared. For all
+// other combinations the internal object id is compared.
+//
+// example:
+//
+// 10 20 gt # false
+// true false gt # true
+// "abd" "abc" gt # true
+// [ 10 20 ] [ 10 20 ] gt # varies
+// 0 false gt # varies
+// 0 nil gt # varies
+// "abc" [ 10 ] gt # varies
+//
+void gfx_prim_gt()
+{
+ binary_cmp_on_stack(op_gt);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// greater or equal
+//
+// group: cmp
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 >= bool_2
+//
+// ( int_1 int_2 -- bool_4 )
+// bool_4: int_1 >= int_2
+//
+// ( string_1 string_2 -- bool_5 )
+// bool_5: string_1 >= string_2
+//
+// ( any_1 any_2 -- bool_6 )
+// bool_6: any_1 >= any_2
+//
+// For pairs of booleans, integers, and strings the values are compared. For all
+// other combinations the internal object id is compared.
+//
+// example:
+//
+// 10 20 ge # false
+// true false ge # true
+// "abd" "abc" ge # true
+// [ 10 20 ] [ 10 20 ] ge # varies
+// 0 false ge # varies
+// 0 nil ge # varies
+// "abc" [ 10 ] ge # varies
+//
+void gfx_prim_ge()
+{
+ binary_cmp_on_stack(op_ge);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// less than
+//
+// group: cmp
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 < bool_2
+//
+// ( int_1 int_2 -- bool_4 )
+// bool_4: int_1 < int_2
+//
+// ( string_1 string_2 -- bool_5 )
+// bool_5: string_1 < string_2
+//
+// ( any_1 any_2 -- bool_6 )
+// bool_6: any_1 < any_2
+//
+// For pairs of booleans, integers, and strings the values are compared. For all
+// other combinations the internal object id is compared.
+//
+// example:
+//
+// 10 20 lt # true
+// true false lt # false
+// "abd" "abc" lt # false
+// [ 10 20 ] [ 10 20 ] lt # varies
+// 0 false lt # varies
+// 0 nil lt # varies
+// "abc" [ 10 ] lt # varies
+//
+void gfx_prim_lt()
+{
+ binary_cmp_on_stack(op_lt);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// less or equal
+//
+// group: cmp
+//
+// ( bool_1 bool_2 -- bool_3 )
+// bool_3: bool_1 <= bool_2
+//
+// ( int_1 int_2 -- bool_4 )
+// bool_4: int_1 <= int_2
+//
+// ( string_1 string_2 -- bool_5 )
+// bool_5: string_1 <= string_2
+//
+// ( any_1 any_2 -- bool_6 )
+// bool_6: any_1 <= any_2
+//
+// For pairs of booleans, integers, and strings the values are compared. For all
+// other combinations the internal object id is compared.
+//
+// example:
+//
+// 10 20 le # true
+// true false le # false
+// "abd" "abc" le # false
+// [ 10 20 ] [ 10 20 ] le # varies
+// 0 false le # varies
+// 0 nil le # varies
+// "abc" [ 10 ] le # varies
+//
+void gfx_prim_le()
+{
+ binary_cmp_on_stack(op_le);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// compare
+//
+// group: cmp
+//
+// ( int_1 int_2 -- int_3 )
+// int_3: int_1 <=> int_2
+//
+// ( bool_1 bool_2 -- int_4 )
+// int_4: bool_1 <=> bool_2
+//
+// ( string_1 string_2 -- int_5 )
+// int_5: string_1 <=> string_2
+//
+// ( any_1 any_2 -- int_6 )
+// int_6: any_1 <=> any_2
+//
+// For pairs of booleans, integers, and strings the values are compared. For
+// all other combinations the internal object id is compared.
+//
+// The result is -1, 1, 0 if the first argument is less than, greater than,
+// or equal to the second argument, respectively.
+//
+// example:
+//
+// 10 20 cmp # -1
+// true false cmp # 1
+// "abc" "abc" cmp # 0
+// [ 10 20 ] [ 10 20 ] cmp # varies
+// 0 false cmp # varies
+// 0 nil cmp # varies
+// "abc" [ 10 ] cmp # varies
+//
+// /foo [ 10 20 ] def
+// /bar foo def
+// foo bar cmp # 0
+//
+void gfx_prim_cmp()
+{
+ binary_cmp_on_stack(op_cmp);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int is_true(obj_id_t id)
+{
+ obj_t *ptr = gfx_obj_ptr(id);
+ int val = 0;
+
+ if(ptr) {
+ if(ptr->base_type == OTYPE_NUM) {
+ val = ptr->data.value ? 1 : 0;
+ }
+ else {
+ val = 1;
+ }
+ }
+
+ return val;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+error_id_t do_op(op_t op, int64_t *result, int64_t val1, int64_t val2, unsigned sub_type)
+{
+ error_id_t err = 0;
+
+ switch(op) {
+ case op_sub:
+ *result = val1 - val2;
+ break;
+
+ case op_mul:
+ if(sub_type == t_bool) {
+ *result = (val1 & val2) & 1;
+ }
+ else {
+ *result = val1 * val2;
+ }
+ break;
+
+ case op_div:
+ if(sub_type == t_bool) {
+ int i1 = val1 & 1;
+ int i2 = val2 & 1;
+ if(i2 == 0) {
+ err = err_div_by_zero;
+ }
+ else {
+ *result = i1;
+ }
+ }
+ else {
+ if(
+ val2 == 0 ||
+ ((uint64_t) val1 == 0x8000000000000000ll && val2 == -1)
+ ) {
+ err = err_div_by_zero;
+ }
+ else {
+ *result = val1 / val2;
+ }
+ }
+ break;
+
+ case op_mod:
+ if(sub_type == t_bool) {
+ int i1 = val1 & 1;
+ int i2 = val2 & 1;
+ if(i2 == 0) {
+ err = err_div_by_zero;
+ }
+ else {
+ *result = i1;
+ }
+ }
+ else {
+ if(
+ val2 == 0 ||
+ ((uint64_t) val1 == 0x8000000000000000ll && val2 == -1)
+ ) {
+ err = err_div_by_zero;
+ }
+ else {
+ *result = val1 % val2;
+ }
+ }
+ break;
+
+ case op_and:
+ *result = val1 & val2;
+ break;
+
+ case op_or:
+ *result = val1 | val2;
+ break;
+
+ case op_xor:
+ *result = val1 ^ val2;
+ break;
+
+ case op_min:
+ *result = val1 < val2 ? val1 : val2;
+ break;
+
+ case op_max:
+ *result = val1 > val2 ? val1 : val2;
+ break;
+
+ case op_shl:
+ *result = val1 << val2;
+ break;
+
+ case op_shr:
+ *result = val1 >> val2;
+ break;
+
+ case op_neg:
+ *result = -val1;
+ break;
+
+ case op_not:
+ *result = ~val1;
+ break;
+
+ case op_abs:
+ *result = val1 > 0 ? val1 : -val1;
+ break;
+
+ default:
+ *result = 0;
+ break;
+ }
+
+ if(sub_type == t_bool) *result &= 1;
+
+ return err;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void binary_op_on_stack(op_t op)
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+ obj_t *ptr2 = gfx_obj_ptr(id2);
+
+ if(!ptr1 || !ptr2 || ptr1->base_type != ptr2->base_type || ptr1->base_type != OTYPE_NUM) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ int64_t result;
+
+ error_id_t err = do_op(op, &result, ptr1->data.value, ptr2->data.value, ptr1->sub_type);
+
+ if(err) {
+ GFX_ERROR(err);
+ return;
+ }
+
+ obj_id_t result_id = gfx_obj_num_new(result, ptr1->sub_type);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, result_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void unary_op_on_stack(op_t op)
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id = pstack->ptr[pstack->size - 1];
+ obj_t *ptr = gfx_obj_ptr(id);
+
+ if(!ptr || ptr->base_type != OTYPE_NUM) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ int64_t result;
+
+ error_id_t err = do_op(op, &result, ptr->data.value, 0, ptr->sub_type);
+
+ if(err) {
+ GFX_ERROR(err);
+ return;
+ }
+
+ obj_id_t result_id = gfx_obj_num_new(result, ptr->sub_type);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, result_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void binary_cmp_on_stack(cmp_op_t op)
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+ obj_t *ptr2 = gfx_obj_ptr(id2);
+
+ int result = 0;
+
+ if(id1 != 0 || id2 != 0) {
+ if(!ptr1 || !ptr2) {
+ result = id1 > id2 ? 1 : -1;
+ }
+ else if(ptr1->base_type != ptr2->base_type) {
+ result = ptr1->base_type > ptr2->base_type ? 1 : -1;
+ }
+ else {
+ switch(ptr1->base_type) {
+ case OTYPE_NUM:
+ if(ptr1->sub_type != ptr2->sub_type) {
+ result = ptr1->sub_type > ptr2->sub_type ? 1 : -1;
+ }
+ else {
+ if(ptr1->data.value != ptr2->data.value) {
+ result = ptr1->data.value > ptr2->data.value ? 1 : -1;
+ }
+ }
+ break;
+
+ case OTYPE_MEM:
+ if(ptr1->sub_type != ptr2->sub_type) {
+ result = ptr1->sub_type > ptr2->sub_type ? 1 : -1;
+ }
+ else {
+ result = gfx_obj_mem_cmp(&ptr1->data, &ptr2->data);
+ }
+ break;
+
+ default:
+ if(id1 != id2) {
+ result = id1 > id2 ? 1 : -1;
+ }
+ break;
+ }
+ }
+ }
+
+ uint8_t subtype = t_bool;
+
+ switch(op) {
+ case op_eq:
+ result = result == 0 ? 1 : 0;
+ break;
+
+ case op_ne:
+ result = result != 0 ? 1 : 0;
+ break;
+
+ case op_gt:
+ result = result > 0 ? 1 : 0;
+ break;
+
+ case op_ge:
+ result = result >= 0 ? 1 : 0;
+ break;
+
+ case op_lt:
+ result = result < 0 ? 1 : 0;
+ break;
+
+ case op_le:
+ result = result <= 0 ? 1 : 0;
+ break;
+
+ case op_cmp:
+ subtype = t_int;
+ break;
+ }
+
+ obj_id_t result_id = gfx_obj_num_new(result, subtype);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, result_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get parent of context, font, or hash
+//
+// group: hash
+//
+// ( context_1 -- context_2 )
+// context_2: parent of context_1 or nil
+//
+// ( font_1 -- font_2 )
+// font_2: parent of font_1 or nil
+//
+// ( hash_1 -- hash_2 )
+// hash_2: parent of hash_1 or nil
+//
+// If a word lookup fails in a context, the lookup continues in the parent
+// context.
+//
+// If a glyph lookup fails in a font, the lookup continues in the parent
+// font.
+//
+// If a key cannot be found in a hash, the lookup continues in the parent
+// hash.
+//
+// example:
+//
+// /x ( "foo" 10 "bar" 20 ) def
+// /y ( "zap" 30 ) def
+// x getparent # nil
+// x y setparent
+// x getparent # ( "zap" 30 )
+//
+void gfx_prim_getparent()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+ if(!ptr1) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ obj_id_t parent_id = 0;
+
+ switch(ptr1->base_type) {
+ case OTYPE_HASH:
+ ;
+ hash_t *hash = OBJ_HASH_FROM_PTR(ptr1);
+ parent_id = hash->parent_id;
+ break;
+
+ case OTYPE_FONT:
+ ;
+ font_t *font = OBJ_FONT_FROM_PTR(ptr1);
+ parent_id = font->parent_id;
+ break;
+
+ case OTYPE_CONTEXT:
+ ;
+ context_t *ctx = OBJ_CONTEXT_FROM_PTR(ptr1);
+ parent_id = ctx->parent_id;
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, parent_id, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set parent of context, font, or hash
+//
+// group: hash
+//
+// ( context_1 context_2 -- )
+// ( context_1 nil -- )
+// context_2: new parent of context_1
+//
+// ( font_1 font_2 -- )
+// ( font_1 nil -- )
+// font_2: new parent of font_1
+//
+// ( hash_1 hash_2 -- )
+// ( hash_1 nil -- )
+// hash_2: new parent of hash_1
+//
+// If nil is used as second argument, any existing parent link is removed.
+//
+// If a word lookup fails in a context, the lookup continues in the parent
+// context.
+//
+// If a glyph lookup fails in a font, the lookup continues in the parent
+// font.
+//
+// If a key cannot be found in a hash, the lookup continues in the parent
+// hash.
+//
+// example:
+//
+// /x ( "foo" 10 "bar" 20 ) def
+// /y ( "zap" 30 ) def
+// x "zap" get # nil
+// x y setparent
+// x "zap" get # 30
+// x nil setparent
+// x "zap" get # nil
+//
+void gfx_prim_setparent()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 2) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 2];
+ obj_id_t id2 = pstack->ptr[pstack->size - 1];
+
+ obj_t *ptr1 = gfx_obj_ptr(id1);
+
+ if(!ptr1) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ if(ptr1->flags.ro) {
+ GFX_ERROR(err_readonly);
+ return;
+ }
+
+ obj_id_t old_parent_id = 0;
+
+ switch(ptr1->base_type) {
+ case OTYPE_HASH:
+ if(id2 && !gfx_obj_hash_ptr(id2)) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+ hash_t *hash = OBJ_HASH_FROM_PTR(ptr1);
+ old_parent_id = hash->parent_id;
+ hash->parent_id = gfx_obj_ref_inc(id2);
+ break;
+
+ case OTYPE_FONT:
+ if(id2 && !gfx_obj_font_ptr(id2)) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+ font_t *font = OBJ_FONT_FROM_PTR(ptr1);
+ old_parent_id = font->parent_id;
+ font->parent_id = gfx_obj_ref_inc(id2);
+ break;
+
+ case OTYPE_CONTEXT:
+ if(id2 && !gfx_obj_context_ptr(id2)) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+ context_t *ctx = OBJ_CONTEXT_FROM_PTR(ptr1);
+ old_parent_id = ctx->parent_id;
+ ctx->parent_id = gfx_obj_ref_inc(id2);
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ gfx_obj_ref_dec(old_parent_id);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get active dictionary
+//
+// group: hash
+//
+// ( -- hash_1 )
+// ( -- nil )
+// hash_1: dictionary
+//
+// Return the currently active dictionary or nil, if the current context
+// does not (yet) have a dictionary.
+//
+// A dictionary will only be created on demand - that is, the first time a
+// word is defined in the current context.
+//
+// When a program is started the global context is created containing a
+// dictionary with all primitive words.
+//
+// example:
+//
+// /foo { getdict } def
+// foo # nil
+//
+// /bar { /x 10 ldef getdict } def
+// bar # ( /x 10 )
+//
+void gfx_prim_getdict()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ context_t *context = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+ if(!context) {
+ GFX_ERROR(err_internal);
+ return;
+ }
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, context->dict_id, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set active dictionary
+//
+// group: hash
+//
+// ( hash_1 -- )
+// ( nil -- )
+// hash_1: new active dictionary
+//
+// Set the currently active dictionary. With nil, the dictionary is removed
+// from the current context.
+//
+// example:
+//
+// /foo { /x 10 ldef x } def
+// foo # 10
+//
+// /bar { ( /x 10 ) setdict x } def
+// bar # 10
+//
+void gfx_prim_setdict()
+{
+ array_t *pstack = gfx_obj_array_ptr(gfxboot_data->vm.program.pstack);
+
+ if(!pstack || pstack->size < 1) {
+ GFX_ERROR(err_stack_underflow);
+ return;
+ }
+
+ obj_id_t id1 = pstack->ptr[pstack->size - 1];
+
+ if(id1 && !gfx_obj_hash_ptr(id1)) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ context_t *context = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+ if(!context) {
+ GFX_ERROR(err_internal);
+ return;
+ }
+
+ OBJ_ID_ASSIGN(context->dict_id, id1);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// make object read-only
+//
+// group: mem
+//
+// ( any_1 -- any_1 )
+//
+// Make any object read-only. A read-only object cannot be modified.
+//
+// Note that string constants are read-only by default.
+//
+// example:
+//
+// [ 10 20 30 ] freeze # [ 10 20 30 ]
+// 0 delete # raises 'readonly' exception
+//
+void gfx_prim_freeze()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_ANY | IS_NIL);
+
+ if(!argv) return;
+
+ if(argv[0].ptr) argv[0].ptr->flags.ro = 1;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get drawing color
+//
+// group: gfx
+//
+// ( -- int_1 )
+// int_1: color
+//
+// Return current drawing color.
+//
+// A color is a RGB value with red in bits 16-23, green in bits 8-15 and
+// blue in bits 0-7. This is independent of what the graphics card is actually using.
+//
+// example:
+//
+// getcolor # 0xffffff (white)
+//
+void gfx_prim_getcolor()
+{
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(gstate ? gstate->color : 0, t_int), 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set drawing color
+//
+// group: gfx
+//
+// ( int_1 -- )
+// int_1: color
+//
+// Set current drawing color.
+//
+// A color is a RGB value with red in bits 16-23, green in bits 8-15 and
+// blue in bits 0-7. This is independent of what the graphics card is actually using.
+//
+// example:
+//
+// 0xff0000 setcolor # red
+//
+void gfx_prim_setcolor()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_NUM);
+
+ if(!argv) return;
+
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) gstate->color = OBJ_VALUE_FROM_PTR(argv[0].ptr);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get background color
+//
+// group: gfx
+//
+// ( -- int_1 )
+// int_1: color
+//
+// Return current background color.
+//
+// A color is a RGB value with red in bits 16-23, green in bits 8-15 and
+// blue in bits 0-7. This is independent of what the graphics card is actually using.
+//
+// example:
+//
+// getcolor # 0 (black)
+//
+void gfx_prim_getbgcolor()
+{
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(gstate ? gstate->bg_color : 0, t_int), 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set background color
+//
+// group: gfx
+//
+// ( int_1 -- )
+// int_1: color
+//
+// Set current background color.
+//
+// A color is a RGB value with red in bits 16-23, green in bits 8-15 and
+// blue in bits 0-7. This is independent of what the graphics card is actually using.
+//
+// example:
+//
+// 0xff00 setcolor # green
+//
+void gfx_prim_setbgcolor()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_NUM);
+
+ if(!argv) return;
+
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) gstate->bg_color = OBJ_VALUE_FROM_PTR(argv[0].ptr);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get drawing position
+//
+// group: gfx
+//
+// ( -- int_1 int_2 )
+// int_1: x
+// int_2: y
+//
+// Return current drawing position. The position is relative to the drawing region in the graphics state.
+//
+// example:
+//
+// getpos # 0 0
+//
+void gfx_prim_getpos()
+{
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(gstate ? gstate->pos.x : 0, t_int), 0);
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(gstate ? gstate->pos.y : 0, t_int), 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set drawing position
+//
+// group: gfx
+//
+// ( int_1 int_2 -- )
+// int_1: x
+// int_2: y
+//
+// Set drawing position. The position is relative to the drawing region in the graphics state.
+//
+// example:
+//
+// 20 30 setpos
+//
+void gfx_prim_setpos()
+{
+ arg_t *argv = gfx_arg_n(2, (uint8_t [2]) { OTYPE_NUM, OTYPE_NUM });
+
+ if(!argv) return;
+
+ int64_t val1 = OBJ_VALUE_FROM_PTR(argv[0].ptr);
+ int64_t val2 = OBJ_VALUE_FROM_PTR(argv[1].ptr);
+
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) {
+ gstate->pos.x = val1;
+ gstate->pos.y = val2;
+ }
+
+ gfx_obj_array_pop_n(2, gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get font
+//
+// group: gfx
+//
+// ( gstate_1 -- font_1 )
+// ( gstate_1 -- nil )
+// gstate_1: graphics state
+// font_1: font
+//
+// Get font from graphics state.
+//
+// example:
+//
+// getgstate getfont # current font
+//
+void gfx_prim_getfont()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_GSTATE);
+
+ if(!argv) return;
+
+ gstate_t *gstate = OBJ_GSTATE_FROM_PTR(argv[0].ptr);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gstate->font_id, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set font
+//
+// group: gfx
+//
+// ( gstate_1 font_1 -- )
+// ( gstate_1 nil -- )
+// gstate_1: graphics state
+// font_1: font
+//
+// Set font in graphics state. If nil is passed, any font is removed from the graphics state.
+//
+// example:
+//
+// /foo_font "foo.fnt" readfile newfont def
+// getgstate foo_font setfont # use "foo.fnt"
+//
+void gfx_prim_setfont()
+{
+ arg_t *argv = gfx_arg_n(2, (uint8_t [2]) { OTYPE_GSTATE, OTYPE_FONT | IS_NIL });
+
+ if(!argv) return;
+
+ gstate_t *gstate = OBJ_GSTATE_FROM_PTR(argv[0].ptr);
+
+ OBJ_ID_ASSIGN(gstate->font_id, argv[1].id);
+
+ area_t area = gfx_font_dim(gstate->font_id);
+
+ gstate->pos.width = area.width;
+ gstate->pos.height = area.height;
+
+ gfx_obj_array_pop_n(2, gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// create font object
+//
+// group: gfx
+//
+// ( string_1 -- font_1 )
+// ( string_1 -- nil )
+// string_1: font data
+// font_1: font object
+//
+// Parse font data in string_1 and create font object. If string_1 does not
+// contain valid font data, return nil.
+//
+// example:
+//
+// /foo_font "foo.fnt" readfile newfont def # create font from file "foo.fnt"
+//
+void gfx_prim_newfont()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_MEM);
+
+ if(!argv) return;
+
+ obj_id_t font_id = gfx_obj_font_open(argv[0].id);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, font_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get drawing region
+//
+// group: gfx
+//
+// ( gstate_1 -- int_1 int_2 int_3 int_4 )
+// gstate_1: graphics state
+// int_1: x
+// int_2: y
+// int_3: width
+// int_4: height
+//
+// Get drawing region associated with graphics state. Any drawing operation
+// will be relative to this region. Graphics output will be clipped at the
+// region boundaries.
+//
+// example:
+//
+// getgstate getregion # 0 0 800 600
+//
+void gfx_prim_getregion()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_GSTATE);
+
+ if(!argv) return;
+
+ gstate_t *gstate = OBJ_GSTATE_FROM_PTR(argv[0].ptr);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(gstate ? gstate->region.x : 0, t_int), 0);
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(gstate ? gstate->region.y : 0, t_int), 0);
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(gstate ? gstate->region.width : 0, t_int), 0);
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(gstate ? gstate->region.height : 0, t_int), 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set drawing region
+//
+// group: gfx
+//
+// ( gstate_1 int_1 int_2 int_3 int_4 -- )
+// gstate_1: graphics state
+// int_1: x
+// int_2: y
+// int_3: width
+// int_4: height
+//
+// Set drawing region associated with graphics state. Any drawing operation
+// will be relative to this region. Graphics output will be clipped at the
+// region boundaries.
+//
+// example:
+//
+// getgstate 10 10 200 100 setregion
+//
+void gfx_prim_setregion()
+{
+ arg_t *argv = gfx_arg_n(5, (uint8_t [5]) { OTYPE_GSTATE, OTYPE_NUM, OTYPE_NUM, OTYPE_NUM, OTYPE_NUM });
+
+ if(!argv) return;
+
+ gstate_t *gstate = OBJ_GSTATE_FROM_PTR(argv[0].ptr);
+
+ int64_t val1 = OBJ_VALUE_FROM_PTR(argv[1].ptr);
+ int64_t val2 = OBJ_VALUE_FROM_PTR(argv[2].ptr);
+ int64_t val3 = OBJ_VALUE_FROM_PTR(argv[3].ptr);
+ int64_t val4 = OBJ_VALUE_FROM_PTR(argv[4].ptr);
+
+ area_t area = { .x = val1, .y = val2, .width = val3, .height = val4 };
+ canvas_t *canvas = gfx_obj_canvas_ptr(gstate->canvas_id);
+ if(canvas) {
+ area_t clip = { .width = canvas->width, .height = canvas->height };
+ gfx_clip(&area, &clip);
+ }
+ gstate->region = area;
+
+ gfx_obj_array_pop_n(5, gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// create canvas
+//
+// group: gfx
+//
+// ( int_1 int_2 -- canvas_1 )
+// int_1: width
+// int_2: height
+//
+// Create a new empty canvas of the specified size.
+//
+// example:
+//
+// 800 600 canvas
+//
+void gfx_prim_canvas()
+{
+ arg_t *argv = gfx_arg_n(2, (uint8_t [2]) { OTYPE_NUM, OTYPE_NUM });
+
+ if(!argv) return;
+
+ int64_t val1 = OBJ_VALUE_FROM_PTR(argv[0].ptr);
+ int64_t val2 = OBJ_VALUE_FROM_PTR(argv[1].ptr);
+
+ gfx_obj_array_pop_n(2, gfxboot_data->vm.program.pstack, 1);
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_canvas_new(val1, val2), 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get canvas
+//
+// group: gfx
+//
+// ( gstate_1 -- canvas_1 )
+// ( gstate_1 -- nil )
+// gstate_1: graphics state
+// canvas_1: canvas object
+//
+// Get canvas object from graphics state. A canvas is a memory area with
+// associated width and height. All drawing operations are done on canvas
+// objects. If no canvas is associated with the graphics state, return nil.
+//
+// example:
+//
+// getgstate getcanvas dim # 800 600
+//
+void gfx_prim_getcanvas()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_GSTATE);
+
+ if(!argv) return;
+
+ gstate_t *gstate = OBJ_GSTATE_FROM_PTR(argv[0].ptr);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gstate->canvas_id, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set canvas
+//
+// group: gfx
+//
+// ( gstate_1 canvas_1 -- )
+// ( gstate_1 nil -- )
+// gstate_1: graphics state
+// canvas_1: canvas object
+//
+// Set canvas of graphics state. A canvas is a memory area with
+// associated width and height. All drawing operations are done on canvas
+// objects. If nil is passed, the canvas is removed from the graphics state.
+//
+// The drawing region of gstate_1 is adjusted to match the canvas size. The
+// drawing position is reset to x = 0, y = 0.
+//
+// example:
+//
+// getgstate 800 600 canvas setcanvas
+//
+void gfx_prim_setcanvas()
+{
+ arg_t *argv = gfx_arg_n(2, (uint8_t [2]) { OTYPE_GSTATE, OTYPE_CANVAS | IS_NIL });
+
+ if(!argv) return;
+
+ gstate_t *gstate = OBJ_GSTATE_FROM_PTR(argv[0].ptr);
+
+ OBJ_ID_ASSIGN(gstate->canvas_id, argv[1].id);
+
+ if(argv[1].id) {
+ canvas_t *canvas = OBJ_CANVAS_FROM_PTR(argv[1].ptr);
+
+ gstate->region = (area_t) {0, 0, canvas->width, canvas->height};
+ gstate->pos.x = gstate->pos.y = 0;
+ }
+
+ gfx_obj_array_pop_n(2, gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get graphics state
+//
+// group: gfx
+//
+// ( -- gstate_1 )
+// ( -- nil )
+//
+// Get current graphics state. If none has been set, return nil.
+//
+// The graphics state consists of a canvas to draw into, a region describing
+// a rectangular drawing and clipping area in that canvas, a drawing
+// position (relative to the drawing region), drawing color, background
+// color (for text), and a text font.
+//
+// example:
+//
+// /saved_state getgstate def # save current graphics state
+//
+void gfx_prim_getgstate()
+{
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfxboot_data->gstate_id, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set graphics state
+//
+// group: gfx
+//
+// ( gstate_1 -- )
+// ( nil -- )
+//
+// Set current graphics state. If nil is passed, the current state is removed.
+//
+// The graphics state consists of a canvas to draw into, a region describing
+// a rectangular drawing and clipping area in that canvas, a drawing
+// position (relative to the drawing region), drawing color, background
+// color (for text), and a text font.
+//
+// example:
+//
+// /saved_state getgstate def # save current graphics state
+// ...
+// saved_state setgstate # restore saved graphics state
+//
+void gfx_prim_setgstate()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_GSTATE | IS_NIL);
+
+ if(!argv) return;
+
+ OBJ_ID_ASSIGN(gfxboot_data->gstate_id, argv[0].id);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// create graphics state
+//
+// group: gfx
+//
+// ( -- gstate_1 )
+//
+// Create a new empty graphics state gate_1.
+//
+// example:
+//
+// gstate
+//
+void gfx_prim_gstate()
+{
+ obj_id_t gstate_id = gfx_obj_gstate_new();
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gstate_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get graphics state of the debug console
+//
+// group: gfx
+//
+// ( -- gstate_1 )
+// ( -- nil )
+//
+// Get graphics state of the debug console. If none has been set, return nil.
+//
+// example:
+//
+// /saved_state getconsolegstate def # save current debug console state
+//
+void gfx_prim_getconsolegstate()
+{
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfxboot_data->console.gstate_id, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set graphics state of the debug console
+//
+// group: gfx
+//
+// ( gstate_1 -- )
+// ( nil -- )
+//
+// Set graphics state of the debug console. If nil is passed, the current state is removed.
+//
+// example:
+//
+// /saved_state getconsolegstate def
+// ...
+// saved_state setconsolegstate # restore saved debug console state
+//
+void gfx_prim_setconsolegstate()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_GSTATE | IS_NIL);
+
+ if(!argv) return;
+
+ OBJ_ID_ASSIGN(gfxboot_data->console.gstate_id, argv[0].id);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// show
+//
+// group: gfx
+//
+// ( string_1 -- )
+//
+// Print string_1 at current cursor position in canvas associated with
+// current graphics state.
+//
+// The cursor position is advanced to point at the end of the printed text.
+// Newline ('\x0a') and carriage return ('\x0d') characters are interpreted
+// and the cursor position is adjusted relative to the starting position.
+//
+// example:
+//
+// "Hello!" show # print "Hello!"
+//
+void gfx_prim_show()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_MEM);
+
+ if(!argv) return;
+
+ data_t *data = OBJ_DATA_FROM_PTR(argv[0].ptr);
+
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) {
+ gfx_puts(gstate, data->ptr, data->size);
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// get graphics object dimension
+//
+// group: gfx
+//
+// ( canvas_1 -- int_1 int_2 )
+// ( font_1 -- int_1 int_2 )
+// ( gstate_1 -- int_1 int_2 )
+// int_1: width
+// int_2: height
+//
+// Get dimension of graphics object. For a canvas it is its size, for a
+// graphics state it is the size of the associated region, for a fixed size
+// font it is its glyph size, for proportional font the width is 0 and the
+// height is the font height.
+//
+// example:
+//
+// getconsolegstate getcanvas dim # 800 600
+// getconsolegstate dim # 640 480
+// getconsolegstate getfont dim # 8 16
+//
+void gfx_prim_dim()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_ANY);
+
+ if(!argv) return;
+
+ area_t area;
+
+ switch(argv[0].ptr->base_type) {
+ case OTYPE_FONT:
+ area = gfx_font_dim(argv[0].id);
+ break;
+
+ case OTYPE_CANVAS:
+ ;
+ canvas_t *canvas = OBJ_CANVAS_FROM_PTR(argv[0].ptr);
+ area.width = canvas->width;
+ area.height = canvas->height;
+ break;
+
+ case OTYPE_GSTATE:
+ ;
+ gstate_t *gstate = OBJ_GSTATE_FROM_PTR(argv[0].ptr);
+ area.width = gstate->region.width;
+ area.height = gstate->region.height;
+ break;
+
+ default:
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(area.width, t_int), 0);
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, gfx_obj_num_new(area.height, t_int), 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// run code
+//
+// group: loop
+//
+// ( string_1 -- )
+// string_1: binary code
+//
+// Load binary code and run it.
+//
+// Note: unlike 'exec' this does not open a new context but replaces the
+// currently running code with the new one.
+//
+// example:
+//
+// "new_program" readfile run
+//
+void gfx_prim_run()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_MEM);
+
+ if(!argv) return;
+
+ if(!gfx_is_code(argv[0].id)) {
+ GFX_ERROR(err_invalid_code);
+
+ return;
+ }
+
+ context_t *context = gfx_obj_context_ptr(gfxboot_data->vm.program.context);
+
+ if(!context) {
+ GFX_ERROR(err_internal);
+
+ return;
+ }
+
+ OBJ_ID_ASSIGN(context->code_id, argv[0].id);
+
+ context->ip = context->current_ip = 0;
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// read file
+//
+// group: mem
+//
+// ( string_1 -- string_2 )
+// ( string_1 -- nil )
+// string_1: file name
+// string_2: file content
+//
+// Read entire file and return its content. If the file could not be read, return nil.
+//
+// example:
+//
+// "foo" readfile
+//
+void gfx_prim_readfile()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_MEM);
+
+ if(!argv) return;
+
+ // interface expects 0-terminated string
+ // so we create a 1 byte larger copy
+ obj_id_t tmp_id = gfx_obj_mem_dup(argv[0].id, 1);
+ data_t *data = gfx_obj_mem_ptr(tmp_id);
+
+ if(!data) {
+ GFX_ERROR(err_invalid_arguments);
+ return;
+ }
+
+ obj_id_t file_id = gfx_read_file(data->ptr);
+
+ gfx_obj_ref_dec(tmp_id);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, file_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// unpack image
+//
+// group: gfx
+//
+// ( string_1 -- canvas_1 )
+// ( string_1 -- nil )
+// string_1: image file data
+//
+// Unpacks image and returns a canvas object with the image or nil if the
+// data dos not contain image data.
+//
+// example:
+//
+// "foo.jpg" readfile unpackimage
+//
+void gfx_prim_unpackimage()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_MEM);
+
+ if(!argv) return;
+
+ obj_id_t image_id = gfx_image_open(argv[0].id);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, image_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// copy rectangular region
+//
+// group: gfx
+//
+// ( gstate_1 gstate_2 -- )
+//
+// Copy from the drawing region of gstate_2 to the drawing region of gstate_1, at the drawing pos of gstate_1.
+//
+// example:
+//
+// /cat_pic gstate def
+// cat_pic "cat.jpg" readfile unpackimage setcanvas
+// 0 0 setpos getgstate cat_pic blt # show cat picture
+//
+void gfx_prim_blt()
+{
+ arg_t *argv = gfx_arg_n(2, (uint8_t [2]) { OTYPE_GSTATE, OTYPE_GSTATE });
+
+ if(!argv) return;
+
+ gstate_t *gstate1 = OBJ_GSTATE_FROM_PTR(argv[0].ptr);
+ gstate_t *gstate2 = OBJ_GSTATE_FROM_PTR(argv[1].ptr);
+
+ area_t area2 = gstate2->region;
+ area_t area1 = {
+ .x = gstate1->region.x + gstate1->pos.x,
+ .y = gstate1->region.y + gstate1->pos.y,
+ .width = area2.width,
+ .height = area2.height
+ };
+
+#if 0
+ gfxboot_serial(4, "blt area1 %dx%d_%dx%d, area2 %dx%d_%dx%d\n",
+ area1.x, area1.y, area1.width, area1.height,
+ area2.x, area2.y, area2.width, area2.height
+ );
+#endif
+
+ area_t diff = gfx_clip(&area1, &gstate1->region);
+
+ ADD_AREA(area2, diff);
+
+#if 0
+ gfxboot_serial(4, "blt clipped area1 %dx%d_%dx%d\n",
+ area1.x, area1.y, area1.width, area1.height
+ );
+#endif
+
+ gfx_blt(1, gstate1->canvas_id, area1, gstate2->canvas_id, area2);
+
+ gfx_obj_array_pop_n(2, gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// start debug console
+//
+// group: def
+//
+// ( -- )
+//
+// Stop code execution and start debug console.
+//
+// You can leave (and re-enter) the debug console with `^D` but note that this
+// doesn't resume program execution. Use the `run` (or `r`) console command for this.
+//
+// example:
+//
+// /foo { debug 10 20 } def
+// foo # activate debug console when 'foo' is run
+//
+void gfx_prim_debug()
+{
+ gfxboot_data->vm.program.stop = 1;
+ gfx_program_debug_on_off(1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// read pixel
+//
+// group: gfx
+//
+// ( -- int_1 )
+// ( -- nil )
+// int_1: color
+//
+// Read pixel at drawing position from canvas in current graphics state. If
+// the position is outside the drawing region, return nil.
+//
+// example:
+//
+// getpixel
+//
+void gfx_prim_getpixel()
+{
+ obj_id_t val = 0;
+
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) {
+ canvas_t *canvas = gfx_obj_canvas_ptr(gstate ? gstate->canvas_id : 0);
+
+ if(canvas) {
+ color_t color;
+ if(gfx_getpixel(gstate, canvas, gstate->pos.x, gstate->pos.y, &color)) {
+ val = gfx_obj_num_new(color, t_int);
+ }
+ }
+ }
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, val, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// set pixel
+//
+// group: gfx
+//
+// ( -- )
+//
+// Set pixel with current color at drawing position in canvas in current
+// graphics state. If the position is outside the drawing region, nothing is
+// drawn.
+//
+// example:
+//
+// setpixel
+//
+void gfx_prim_putpixel()
+{
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) {
+ canvas_t *canvas = gfx_obj_canvas_ptr(gstate->canvas_id);
+
+ if(canvas) {
+ gfx_putpixel(gstate, canvas, gstate->pos.x, gstate->pos.y, gstate->color);
+ }
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// draw line
+//
+//group: gfx
+//
+// ( int_1 int_2 -- )
+// int_1: x
+// int_2: y
+//
+// Draw line from current position to the specified x and y coordinates
+// using the current color. The drawing position is updated to the end
+// position. Line segments outside the drawing region are not drawn.
+//
+// example:
+//
+// 100 200 lineto
+//
+void gfx_prim_lineto()
+{
+ arg_t *argv = gfx_arg_n(2, (uint8_t [2]) { OTYPE_NUM, OTYPE_NUM });
+
+ if(!argv) return;
+
+ int64_t val1 = OBJ_VALUE_FROM_PTR(argv[0].ptr);
+ int64_t val2 = OBJ_VALUE_FROM_PTR(argv[1].ptr);
+
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) {
+ canvas_t *canvas = gfx_obj_canvas_ptr(gstate->canvas_id);
+
+ if(canvas) {
+ gfx_line(gstate, canvas, gstate->pos.x, gstate->pos.y, val1, val2, gstate->color);
+ }
+
+ gstate->pos.x = val1;
+ gstate->pos.y = val2;
+ }
+
+ gfx_obj_array_pop_n(2, gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// draw filled rectangle
+//
+// group: gfx
+//
+// ( int_1 int_2 -- )
+// int_1: width
+// int_2: height
+//
+// Draw filled rectangle (using current color) at current position. The
+// rectangle is clipped at the current drawing region.
+//
+// example:
+//
+// 200 100 fillrect
+//
+void gfx_prim_fillrect()
+{
+ arg_t *argv = gfx_arg_n(2, (uint8_t [2]) { OTYPE_NUM, OTYPE_NUM });
+
+ if(!argv) return;
+
+ int64_t val1 = OBJ_VALUE_FROM_PTR(argv[0].ptr);
+ int64_t val2 = OBJ_VALUE_FROM_PTR(argv[1].ptr);
+
+ gstate_t *gstate = gfx_obj_gstate_ptr(gfxboot_data->gstate_id);
+
+ if(gstate) {
+ gfx_rect(gstate, gstate->pos.x, gstate->pos.y, val1, val2, gstate->color);
+ }
+
+ gfx_obj_array_pop_n(2, gfxboot_data->vm.program.pstack, 1);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// decode Unicode string
+//
+// group: mem
+//
+// (string_1 -- array_1 )
+// string_1: UTF8-encoded string
+// array_1: array with decoded chars
+//
+// The array contains one element for each UTF8-encoded char. If string_1
+// contains non-UTF8-chars they are represented as the negated 8-bit value.
+//
+// example:
+//
+// "ABC" utf8decode # [ 65 66 67 ]
+// "Ä €" utf8decode # [ 196 32 8364 ]
+// "A\xf0B" utf8decode # [ 65 -240 66 ]
+//
+void gfx_prim_utf8decode()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_MEM);
+
+ if(!argv) return;
+
+ data_t *mem = OBJ_DATA_FROM_PTR(argv[0].ptr);
+
+ char *data = (char *) mem->ptr;
+ unsigned data_len = mem->size;
+
+ unsigned uni_len = 0;
+ while(data_len) {
+ uni_len++;
+ gfx_utf8_dec(&data, &data_len);
+ }
+
+ obj_id_t array_id = gfx_obj_array_new(uni_len);
+ if(!array_id) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ data = (char *) mem->ptr;
+ data_len = mem->size;
+
+ int i = 0;
+ while(data_len) {
+ int c = gfx_utf8_dec(&data, &data_len);
+ gfx_obj_array_set(array_id, gfx_obj_num_new(c, t_int), i++, 0);
+ }
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, array_id, 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// encode Unicode string
+//
+// group: mem
+//
+// (array_1 -- string_1 )
+// array_1: array with decoded chars
+// string_1: UTF8-encoded string
+//
+// The array contains one element for each UTF8-encoded char. If string_1
+// should contain non-UTF8-chars they are represented as the negated 8-bit
+// value in array_1.
+//
+// example:
+//
+// [ 65 66 67 ] utf8encode # "ABC"
+// [ 196 32 8364 ] utf8encode # "Ä €"
+// [ 65 -240 66 ] utf8encode # "A\xf0B"
+//
+void gfx_prim_utf8encode()
+{
+ arg_t *argv = gfx_arg_1(OTYPE_ARRAY);
+
+ if(!argv) return;
+
+ obj_id_t array_id = argv[0].id;
+ unsigned array_size = OBJ_ARRAY_FROM_PTR(argv[0].ptr)->size;
+
+ obj_id_t mem_id = gfx_obj_mem_new(array_size*6);
+ if(!mem_id) {
+ GFX_ERROR(err_no_memory);
+ return;
+ }
+
+ obj_t *ptr = gfx_obj_ptr(mem_id);
+ ptr->sub_type = t_string;
+ data_t *mem = OBJ_DATA_FROM_PTR(ptr);
+
+ uint8_t *data = (uint8_t *) mem->ptr;
+
+ for(int i = 0; i < (int) array_size; i++) {
+ int64_t *val = gfx_obj_num_ptr(gfx_obj_array_get(array_id, i));
+
+ if(!val) {
+ gfx_obj_ref_dec(mem_id);
+ GFX_ERROR(err_invalid_data);
+ return;
+ }
+
+ if(*val <= 0) {
+ *data++ = -*val;
+ }
+ else {
+ uint8_t *str = gfx_utf8_enc(*val);
+ while(*str) *data++ = *str++;
+ }
+ }
+
+ gfx_obj_realloc(mem_id, data - (uint8_t *) mem->ptr);
+
+ gfx_obj_array_pop(gfxboot_data->vm.program.pstack, 1);
+
+ gfx_obj_array_push(gfxboot_data->vm.program.pstack, mem_id, 0);
+}
diff --git a/git2log b/git2log
new file mode 100755
index 0000000..2032a05
--- /dev/null
+++ b/git2log
@@ -0,0 +1,1041 @@
+#! /usr/bin/perl
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+#
+# This script is maintained at https://github.com/openSUSE/linuxrc-devtools
+#
+# If you're in another project, this is just a copy.
+# You may update it to the latest version from time to time...
+#
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+use strict;
+
+use Getopt::Long;
+
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+$Data::Dumper::Terse = 1;
+$Data::Dumper::Indent = 1;
+
+sub usage;
+sub changelog_outdated;
+sub get_github_project;
+sub get_version;
+sub get_tags;
+sub get_log;
+sub is_formatted_tag;
+sub get_branch;
+sub choose_tags;
+sub add_head_tag;
+sub tags_to_str;
+sub format_log;
+sub format_all_logs;
+sub fix_dates;
+sub add_line_breaks;
+sub add_bugzilla_to_weblate;
+sub format_date_obs;
+sub format_date_iso;
+sub raw_date_to_s;
+
+usage 0 if !@ARGV;
+
+my @changelog_deps = qw ( .git/HEAD .git/refs/heads .git/refs/tags );
+
+my $branch;
+my $current_version;
+my @tags;
+my @all_tags;
+my $config;
+
+my $opt_log;
+my $opt_version;
+my $opt_branch;
+my $opt_update;
+my $opt_file;
+my $opt_start;
+my $opt_max;
+my $opt_width = 66;
+my $opt_width_fuzz = 8;
+my $opt_sep_width = 68;
+my $opt_format = 'internal'; # obs, internal
+my $opt_merge_msg_before = 1; # log auto generated pr merge message before the commit messages (vs. after)
+my $opt_join_author = 1; # join consecutive commit messages as long as they are by the same author
+my $opt_keep_date = 1; # don't join consecutive commit messages if they have different time stamps
+my $opt_default_email = 'opensuse-packaging@opensuse.org'; # default email to use in changelog
+my $opt_weblate = 'bsc#1149754'; # bugzilla ref to use for Weblate commits
+
+GetOptions(
+ 'help' => sub { usage 0 },
+ 'version' => \$opt_version,
+ 'branch' => \$opt_branch,
+ 'update' => \$opt_update,
+ 'start=s' => \$opt_start,
+ 'format=s' => \$opt_format,
+ 'max=i' => \$opt_max,
+ 'width=i' => \$opt_width,
+ 'fuzz=i' => \$opt_width_fuzz,
+ 'merge-msg=s' => sub { $opt_merge_msg_before = ($_[1] eq 'after' ? 0 : 1) },
+ 'join-author!' => \$opt_join_author,
+ 'keep-date!' => \$opt_keep_date,
+ 'log|changelog' => \$opt_log,
+ 'default-email=s' => \$opt_default_email,
+ 'weblate=s' => \$opt_weblate,
+) || usage 1;
+
+# ensure we are used correctly
+usage 1 if @ARGV > 1 || !($opt_log || $opt_version || $opt_branch);
+$opt_file = @ARGV ? shift : '-';
+
+die "no git repo\n" unless -d ".git";
+
+# if update option has been give write changelog only if git refs are newer
+exit 0 if $opt_update && $opt_file ne '-' && -f($opt_file) && !changelog_outdated($opt_file);
+
+$opt_max = 2 if $opt_version || $opt_branch;
+
+# gather some data
+get_github_project;
+get_branch;
+get_log;
+fix_dates;
+get_tags;
+choose_tags;
+add_head_tag;
+get_version;
+
+# just print current branch
+if($opt_branch) {
+ open my $f, ">$opt_file";
+ print $f $config->{branch} ? $config->{branch} : "master", "\n";
+ close $f;
+
+ exit 0;
+}
+
+# just print current version
+if($opt_version) {
+ my $old_version;
+
+ if($opt_file ne '-' && open(my $f, $opt_file)) {
+ chomp($old_version = <$f>);
+ close $f;
+ }
+
+ if($config->{version} ne $old_version) {
+ open my $f, ">$opt_file";
+ print $f "$config->{version}\n";
+ close $f;
+ }
+
+ exit 0;
+}
+
+# set start tag
+if($opt_start) {
+ my $x = is_formatted_tag $opt_start;
+ die "$opt_start: not a valid start tag\n" if !$x;
+ $x->{branch} = $config->{branch} if !$x->{branch};
+ $config->{start} = $x;
+}
+
+format_all_logs;
+
+open my $f, ">$opt_file";
+
+print $f $_->{formatted} for @{$config->{log}};
+
+close $f;
+
+exit 0;
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# usage(exit_code)
+#
+# Print help message and exit.
+# - exit_code: exit code
+#
+# Function does not return.
+#
+sub usage
+{
+ my $err = shift;
+
+ print <<" usage";
+Usage: git2log [OPTIONS] [FILE]
+Create changelog and project version from git repo.
+ --changelog Write changelog to FILE.
+ --version Write version number to FILE.
+ --branch Write current branch to FILE.
+ --start START_TAG Start with tag START_TAG.
+ --max N Write at most N log entries.
+ --update Write changelog or version only if FILE is outdated.
+ --format FORMAT Write log using FORMAT. Supported FORMATs are 'internal' (default) and 'obs'.
+ --width WIDTH Reformat log entries to be max WIDTH chars wide.
+ --fuzz FUZZ Allow log lines to be up to FUZZ chars longer as WIDTH to avoid
+ line breaks leaving tiny bits on the last line.
+ --merge-msg WHERE Log message about merges before or after the actual merge commit messages.
+ Valid values for WHERE are 'after' and 'before' (default).
+ --join-author Join consecutive commits as long as they are by the same author. (default)
+ --no-join-author Keep consecutive commits by the same author separate.
+ --keep-date Join consecutive commits only if they have the same date. (default)
+ --no-keep-date Join consecutive commits even if dates differ.
+ --default-email Use this email in changelog entries if no other suitable email could be
+ determined (default: opensuse-packaging\@opensuse.org).
+ --weblate STRING Add this STRING to every auto-generated Weblate commit.
+ --help Print this help text.
+ usage
+
+ exit $err;
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# res = changelog_outdated(file)
+#
+# Return status of changelog file.
+# - file: changelog file name
+# - res: status
+# 1: file is newer than the last git repo change and should be updated
+# 0: file is still recent enough
+#
+# Relies on global var @changelog_deps.
+#
+sub changelog_outdated
+{
+ my $file = $_[0];
+
+ my $changelog_time = (stat $file)[9];
+
+ return 1 if !defined $changelog_time;
+
+ for (@changelog_deps) {
+ return 1 if (stat)[9] > $changelog_time;
+ }
+
+ return 0;
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# get_github_project()
+#
+# Set $config->{github_project} to the github project name.
+#
+sub get_github_project
+{
+ if(`git config remote.origin.url` =~ m#github.com[:/]+(\S+/\S+)#) {
+ $config->{github_project} = $1;
+ $config->{github_project} =~ s/\.git$//;
+ }
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# get_version()
+#
+# Set $config->{branch} and $config->{version} to the current branch and
+# version info.
+#
+# This might be taken directly from HEAD if HEAD is tagged or otherwise be
+# exprapolated from the most recent tag (cf. add_head_tag()).
+#
+sub get_version
+{
+ $config->{version} = "0.0";
+
+ my $tag = $config->{log}[0]{tags}[0];
+
+ if($tag->{version}) {
+ $config->{version} = $tag->{version};
+ $config->{branch} = $tag->{branch};
+ }
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# get_tags()
+#
+# Parse $config->{raw_log}, extract tag names, and split into per-tag
+# sections.
+#
+# Only tags recognized by is_formatted_tag() are considered.
+#
+# Tags inside merge commits are ignored.
+#
+# The parsed logs is stored in $config->{log}, an array of log sections.
+# Each section is a hash with these keys:
+# - 'tags': array of tags for this section
+# - 'commit': git commit id associated with these tags
+# - 'lines': git log lines
+#
+sub get_tags
+{
+ my $log_entry;
+
+ # the end of the merge commit if in a merge
+ my $merge;
+
+ for (@{$config->{raw_log}}) {
+ if(/^commit (\S+)( \((.*)\))?/) {
+ my $commit = $1;
+ my $tag_list = $3;
+ my $xtag;
+
+ # we have reached the end of the merge commit
+ undef $merge if $merge && $commit =~ /^$merge/;
+
+ # ignore tag info inside a merge commit
+ $tag_list = "" if $merge;
+
+ for my $t (split /, /, $tag_list) {
+ if($t =~ /tag: (\S+)/) {
+ my $tag = $1;
+ my $x = is_formatted_tag $tag;
+ push @$xtag, $x if $x;
+ }
+ }
+
+ if($xtag) {
+ if($log_entry) {
+ push @{$config->{log}}, $log_entry;
+ last if $opt_max && @{$config->{log}} >= $opt_max;
+ }
+ $log_entry = { commit => $commit, tags => $xtag };
+ }
+ else {
+ $log_entry = { commit => $commit } if !$log_entry;
+ }
+ }
+ elsif(!$merge && /^Merge: (\S+)/) {
+ # remember end of merge
+ $merge = $1;
+ }
+
+ push @{$log_entry->{lines}}, $_ if $log_entry;
+ }
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# get_log()
+#
+# Read git log and store lines as array in $config->{raw_log} (trailing
+# newlines removed).
+#
+sub get_log
+{
+ chomp(@{$config->{raw_log}} = `git log --pretty=medium --date=raw --topo-order --decorate`);
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# hash_ref = is_formatted_tag(tag_name)
+#
+# Parse tag and return hash ref with branch and version number parts or
+# undef if it doesn't match.
+# - tag_name: tag as string
+# - hash_ref: hash ref with internal tag representation (with keys 'branch' and 'version').
+#
+# This expects tags of the form "VERSION" or "BRANCH-VERSION" where VERSION
+# consists of decimal numbers separated by dots '.' and BRANCH can be any
+# string.
+# (Note: it doesn't really have to be the name of an existing branch.)
+#
+# Tags not conforming to this convention are ignored.
+#
+sub is_formatted_tag
+{
+ if($_[0] =~ /^((.+)-)?((\d+\.)*\d+)$/) {
+ return { branch => $2, version => $3 }
+ }
+
+ return undef;
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# get_branch()
+#
+# Get currently active git branch and store in $config->{branch}.
+#
+# 'master' branch is represented by empty 'branch' key.
+#
+sub get_branch
+{
+ chomp(my $branch = `git rev-parse --abbrev-ref HEAD`);
+
+ $branch = "" if $branch eq 'master';
+
+ $config->{branch} = $branch;
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# res = tag_sort(a, b)
+#
+# Compare 2 tags.
+# - a, b: refs to tag hash
+# - res: -1, 0, 1
+#
+# This is used when we have to decide between alternative tags.
+# (Prefer 'lesser' variant.)
+#
+sub tag_sort
+{
+ my ($x, $y);
+
+ $x = length $a->{version};
+ $y = length $b->{version};
+
+ # longer version number first
+ return $y <=> $x if $y <=> $x;
+
+ return $a->{branch} cmp $b->{branch};
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# str = tag_to_str(tag_ref)
+#
+# Convert tag into string.
+# - tag_ref: ref to hash with 'branch' and 'version' keys
+# - str: string (e.g. "foo-1.44")
+#
+# 'master' branch is represented by missing/empty 'branch' key.
+#
+sub tag_to_str
+{
+ my $tag = $_[0];
+ my $str;
+
+ $str = "$tag->{branch}-" if $tag->{branch} ne "";
+ $str .= $tag->{version};
+
+ return $str;
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# str = tags_to_str(tag_array_ref)
+#
+# Convert array of tags into string.
+# - tag_array_ref: ref to array of tags
+# - str: string (e.g. "(tag1, tag2)"
+#
+# This function is used only internally for debugging.
+#
+sub tags_to_str
+{
+ my $tags = $_[0];
+ my $str;
+
+ for my $t (@$tags) {
+ $str .= ", " if $str;
+ $str .= tag_to_str $t;
+ }
+
+ return "($str)";
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# choose_tags()
+#
+# Scan commit messages and extract tag & branch information.
+#
+# This stores the tag/branch info in $config->{log}[]{tags}.
+#
+sub choose_tags
+{
+ my $branch = $config->{branch};
+
+ for my $x (@{$config->{log}}) {
+ # printf "# %s\n", tags_to_str($x->{tags});
+
+ # no tag info? -> ignore
+ next if !$x->{tags};
+
+ # single tag? -> remember branch info
+ if(@{$x->{tags}} == 1) {
+ $branch = $x->{tags}[0]{branch};
+ next;
+ }
+
+ # several tags? -> choose one
+
+ # any with current branch name?
+ my @t = grep { $_->{branch} eq $branch } @{$x->{tags}};
+
+ # no? -> choose among all
+ @t = @{$x->{tags}} if @t == 0;
+
+ # prefer longest version number, then alphanumerically smallest branch name
+ @t = sort tag_sort @t;
+
+ $branch = $t[0]{branch};
+
+ # Here's some magic:
+ #
+ # If a commit is tagged "FOO-X.Y" *and* "FOO-X.Y.0" assume the user
+ # wants to start a new sub-numbering scheme.
+ #
+ # Use "X.Y" as version but remember to go to "X.Y.1" when we need to
+ # increment the version.
+ #
+ my $version = $t[0]{version};
+ if($version =~ s/\.0$//) {
+ if(grep { $_->{branch} eq $branch && $_->{version} eq $version } @t) {
+ $t[0]{new_start} = 1;
+ $t[0]{version} = $version;
+ }
+ }
+
+ $x->{tags} = [ $t[0] ];
+
+ # printf "X %s\n", tags_to_str($x->{tags});
+ }
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# add_head_tag()
+#
+# Suggest tag for HEAD if there isn't one.
+#
+# Basically, use branch + version from most recent tag and increment version.
+#
+# If the 'new_start' attribute to tag is set, start a new sub-numbering scheme.
+#
+sub add_head_tag
+{
+ return if @{$config->{log}} < 2;
+
+ # HEAD tagged already?
+ return if $config->{log}[0]{tags};
+
+ # the first tagged commit if HEAD isn't tagged
+ my $tag = { %{$config->{log}[1]{tags}[0]} };
+
+ # append '.0' to version
+ $tag->{version} .= '.0' if $tag->{new_start};
+
+ # increment version
+ $tag->{version} =~ s/(\d+)$/$1 + 1/e;
+
+ $config->{log}[0]{tags}[0] = $tag;
+
+ # remember that the tag was generated
+ $config->{log}[0]{was_untagged} = 1;
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# fix_dates()
+#
+# Adjust time stamps in entire git log.
+#
+# The time stamps of the git commits are not necessarily ordered by date.
+# But the generated changelog is required to have a strictly monotonic time.
+#
+# We do this by going through the log in reverse and rewriting any dates we
+# find whenever the date decreases.
+#
+# A minimum time difference of 1 second beween entries is maintained.
+#
+# Not very subtle but it works.
+#
+sub fix_dates
+{
+ my $last_date;
+
+ for (reverse @{$config->{raw_log}}) {
+ # e.g. "Date: 1443184889 +0200"
+ if(/^(Date:\s+)(\S+)(\s+\S+)/) {
+ if(defined $last_date && $2 < $last_date) {
+ $_ = "$1$last_date$3\n";
+ }
+ else {
+ $last_date = $2;
+ }
+
+ # ensure a minimal time gap of 1 second
+ $last_date += 1;
+ }
+ }
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# format_all_logs()
+#
+# Format the entire git log.
+#
+# This is done for every code version individually (the log has already been
+# split accordingly).
+#
+# If $config->{start} is set, use this as starting point. Else format the
+# entire git log.
+#
+sub format_all_logs
+{
+ # check if start tag actually exists - if not, print nothing
+ if($config->{start}) {
+ my $tag_found;
+ for (@{$config->{log}}) {
+ $tag_found = 1, last if grep { tag_to_str($config->{start}) eq tag_to_str($_) } @{$_->{tags}};
+ }
+ return if !$tag_found;
+ }
+
+ for (@{$config->{log}}) {
+ if($config->{start}) {
+ # stop if we meet the start tag
+ last if grep { tag_to_str($config->{start}) eq tag_to_str($_) } @{$_->{tags}};
+ }
+ format_log $_;
+ }
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# format_log(log)
+#
+# Format log messages.
+# - log: is an array ref with individual commits
+#
+# All commits belong to a specific code version (stored in $log->{tag}).
+# $log->{formatted} holds the result.
+#
+# The process is done in several individual steps, documented below in the code.
+#
+sub format_log
+{
+ my $log = $_[0];
+
+ my $merge;
+ my $commit;
+ my $commits;
+
+ for (@{$log->{lines}}) {
+ if(/^commit (\S+)/) {
+ $commit = { ref => $1 };
+ push @{$commits}, $commit;
+
+ if(
+ $merge &&
+ $merge->{merge_end} eq substr($commit->{ref}, 0, length($merge->{merge_end}))
+ ) {
+ undef $merge;
+ }
+
+ if($merge) {
+ $commit->{merge_ref} = $merge->{ref};
+ $commit->{date} = $merge->{date};
+ $commit->{author} = $merge->{author};
+ # add to all commits so it's not lost when we re-arrange
+ $commit->{merge_msg} = $merge->{msg};
+ }
+
+ next;
+ }
+
+ if(/^Merge: (\S+)/ && !$merge) {
+ if($commit) {
+ $merge = { merge_end => $1, ref => $commit->{ref} } unless $merge;
+ }
+ next;
+ }
+
+ if(/^Date:\s+(\S.*)/) {
+ $commit->{date} ||= $1 if $commit;
+ $merge->{date} ||= $1 if $merge;
+ next;
+ }
+
+ if(/^Author:\s+(\S.*)/) {
+ $commit->{author} ||= $1 if $commit;
+ $merge->{author} ||= $1 if $merge;
+ next;
+ }
+
+ if($merge) {
+ if(/^ Merge pull request (#\d+) from (\S+)/) {
+ if($config->{github_project}) {
+ push @{$merge->{msg}}, "merge gh#$config->{github_project}$1";
+ }
+ else {
+ push @{$merge->{msg}}, "merge pr $2";
+ }
+ }
+ elsif(/^ Merge branch '([^']+)'( into)?/) {
+ push @{$merge->{msg}}, "merge branch $1" if $2 eq "";
+ }
+ elsif(/^ Merge remote-tracking branch /) {
+ # ignore
+ }
+ elsif(s/^ //) {
+ push @{$commit->{lines}}, $_ unless /^# /;
+ }
+ }
+ elsif($commit) {
+ if(s/^ //) {
+ push @{$commit->{lines}}, $_ unless /^# /;
+ }
+ }
+ }
+
+ # Note: the individual steps below work on the array @$commits and modify
+ # its content.
+
+ # step 1
+ # - if there are paragraphs starting with '@log@' or '@+log@'
+ # - delete first paragraph (short summary)
+ # - else
+ # - keep only first paragraph
+ # - if there is a paragraph starting with '@-log', delete entire log
+ # - tag commits that have a '@log@' tag so we can delete untagged commits
+ # belonging to the same merge commit later
+
+ my $tagged_merges = {};
+
+ for my $commit (@$commits) {
+ # skip leading empty lines
+ for (@{$commit->{lines}}) {
+ last if !/^\s*$/;
+ shift @{$commit->{lines}};
+ }
+ my $para_cnt = 0;
+ my $delete_all = 0;
+ my $delete_first = 0;
+ for (@{$commit->{lines}}) {
+ $para_cnt++ if $_ eq "";
+ $para_cnt = 0, $delete_first = 1 if /^\@\+log\@/;
+ $delete_all = 1 if /^\@\-log\@/;
+ if(/^\@log\@/) {
+ $para_cnt = 0;
+ $commit->{clear} = 1;
+ $tagged_merges->{$commit->{merge_ref}} = 1 if $commit->{merge_ref} || $log->{was_untagged};
+ }
+ $_ = undef if $para_cnt;
+ }
+ shift @{$commit->{lines}} if $delete_first;
+ $commit->{lines} = [] if $delete_all;
+ }
+
+ # step 2
+ # - clean up tagged commits or commits belonging to tagged merges
+
+ for my $commit (@$commits) {
+ next unless $commit->{clear} || $tagged_merges->{$commit->{merge_ref}};
+ for (@{$commit->{lines}}) {
+ last if /^\@\+?log\@/;
+ $_ = undef;
+ }
+ }
+
+ # step 3
+ # - join lines
+
+ for my $commit (@$commits) {
+ my $lines;
+ my $line;
+
+ for (@{$commit->{lines}}) {
+ next if $_ eq "";
+ if(
+ s/^\s*[+\-][\-\s]*// ||
+ s/^\@\+?log\@// ||
+ $line eq ""
+ ) {
+ s/^\s*//;
+ push @$lines, $line if $line ne "";
+ $line = $_;
+ }
+ else {
+ s/^\s*//;
+ $line .= " " if $line ne "";
+ $line .= $_;
+ }
+ }
+ push @$lines, $line if $line ne "";
+
+ $commit->{formatted} = $lines if $lines;
+ }
+
+ # step 4
+ # - fix small glitches
+
+ for my $commit (@$commits) {
+ next unless $commit->{formatted};
+ for (@{$commit->{formatted}}) {
+ s/(fate|bnc|bsc|boo|jsc)\s*(#[a-z\d\-]+)/\L$1\E$2/ig;
+ }
+ }
+
+ # step 5
+ # - add merge info at the top or bottom (depending on $opt_merge_msg_before)
+
+ my $merge_logged;
+
+ for my $commit ($opt_merge_msg_before ? reverse(@$commits) : @$commits) {
+ next unless $commit->{formatted};
+
+ if($commit->{merge_ref} && !$merge_logged->{$commit->{merge_ref}}) {
+ $merge_logged->{$commit->{merge_ref}} = 1;
+ if($commit->{merge_msg}) {
+ if($opt_merge_msg_before) {
+ unshift @{$commit->{formatted}}, @{$commit->{merge_msg}};
+ }
+ else {
+ push @{$commit->{formatted}}, @{$commit->{merge_msg}};
+ }
+ }
+ }
+ }
+
+ # step 6
+ # - join commit messages with same author (optionally even with different dates)
+
+ my $commit0;
+
+ for my $commit (@$commits) {
+ next if !$commit->{formatted};
+ $commit0 = $commit, next if !$commit0;
+
+ if(
+ # $commit->{merge_ref} eq $commit0->{merge_ref} &&
+ (
+ $opt_join_author && ($commit->{author} eq $commit0->{author})
+ && (!$opt_keep_date || $commit->{date} eq $commit0->{date})
+ )
+ || $opt_format eq 'internal'
+ ) {
+ unshift @{$commit0->{formatted}}, @{$commit->{formatted}};
+ delete $commit->{formatted};
+ }
+ else {
+ $commit0 = $commit;
+ }
+ }
+
+ # step 7
+ # - add version tag at the end of the first log entry
+
+ for my $commit (@$commits) {
+ next unless $commit->{formatted};
+
+ if($opt_format eq 'obs') {
+ push @{$commit->{formatted}}, $log->{tags}[0]{version} if defined $log->{tags}[0]{version};
+ }
+ else {
+ # push @{$commit->{formatted}}, tag_to_str($log->{tags}[0]);
+ }
+
+ last;
+ }
+
+ # step 8
+ # - remove identical lines
+
+ for my $commit (@$commits) {
+ next unless $commit->{formatted};
+ my %k;
+ $commit->{formatted} = [ grep { !$k{$_}++ } @{$commit->{formatted}} ]
+ }
+
+ # step 9
+ # - remove shortened lines (that match the beginning of other lines)
+
+ for my $commit (@$commits) {
+ next unless $commit->{formatted};
+
+ # return 1 if some other commit line starts with function arg
+ my $is_substr = sub {
+ my $str = $_[0];
+ $str =~ s/\s*…$//; # github likes to shorten lines with ' …'
+ my $str_len = length $str;
+ for (@{$commit->{formatted}}) {
+ return 1 if $str_len < length($_) && $str eq substr($_, 0, $str_len);
+ }
+
+ return 0;
+ };
+
+ $commit->{formatted} = [ grep { ! $is_substr->($_) } @{$commit->{formatted}} ]
+ }
+
+ # step 10
+ # - add bugzilla reference to Weblate commits
+
+ for my $commit (@$commits) {
+ next unless $commit->{formatted};
+ for (@{$commit->{formatted}}) {
+ $_ = add_bugzilla_to_weblate $_;
+ }
+ }
+
+ # step 11
+ # - add line breaks
+
+ for my $commit (@$commits) {
+ next unless $commit->{formatted};
+ for (@{$commit->{formatted}}) {
+ $_ = add_line_breaks $_;
+ }
+ }
+
+ # step 12
+ # - generate final log message
+ #
+ # note: non-(open)suse email addresses are replaced by $opt_default_email
+
+ my $formated_log;
+
+ for my $commit (@$commits) {
+ next unless $commit->{formatted} && @{$commit->{formatted}};
+
+ if($opt_format eq 'obs') {
+ $formated_log .= "-" x $opt_sep_width . "\n";
+ $formated_log .= format_date_obs($commit->{date});
+ }
+ else {
+ $formated_log .= format_date_iso($commit->{date});
+ }
+ if($opt_format eq 'obs') {
+ my $auth = $commit->{author};
+ $auth =~ s/^.*/;
+ $auth =~ s/>.*$//;
+ # replace non-suse e-mail addresses with a generic one
+ if($auth !~ /\@(suse\.(com|cz|de)|opensuse\.org)$/) {
+ $auth = $opt_default_email;
+ }
+ $formated_log .= " - $auth\n\n";
+ }
+ else {
+ $formated_log .= ":\t" . tag_to_str($log->{tags}[0]) . "\n";
+ }
+
+ for (@{$commit->{formatted}}) {
+ s/^/\t/mg if $opt_format eq 'internal';
+ $formated_log .= "$_\n";
+ }
+
+ $formated_log .= "\n";
+ }
+
+ $log->{formatted} = $formated_log;
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# new_text = add_line_breaks(text)
+#
+# Add line breaks to text.
+# - text: some text
+# - new_text: same text, reformatted
+#
+# Lines are formatted to have a maximal length of $opt_width. If this causes
+# the last line to be shorter than $opt_width_fuzz, it is appended to the
+# previous line.
+#
+sub add_line_breaks
+{
+ my @words = split /\s+/, @_[0];
+ my $remaining_len = length(join '', @words);
+
+ my $str = shift(@words);
+ my $len = length $str;
+
+ my $next_len;
+ my $word_len;
+
+ for (@words) {
+ $word_len = length;
+ $remaining_len -= $word_len;
+ $next_len = $len + $word_len + 1;
+ if(
+ $next_len >= $opt_width &&
+ $next_len + $remaining_len + 1 >= $opt_width + $opt_width_fuzz
+ ) {
+ $str .= "\n $_";
+ $len = $word_len;
+ }
+ else {
+ $str .= " $_";
+ $len += $word_len + 1;
+ }
+ }
+
+ return "- " . $str;
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# new_text = add_bugzilla_to_weblate(text)
+#
+# Add bugzilla number to an auto-generated Weblate commit.
+# - text: some text
+# - new_text: same text, "($opt_weblate)" added
+#
+sub add_bugzilla_to_weblate
+{
+ my $text = @_[0];
+
+ if($opt_weblate ne "") {
+ if($text =~ /Translated using Weblate/ && $text !~ /\($opt_weblate\)/) {
+ $text .= " ($opt_weblate)";
+ }
+ }
+
+ return $text;
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# seconds = raw_date_to_s(git_date)
+#
+# Convert git raw date to seconds.
+# - git_date: raw git format (e.g. "1443184889 +0200")
+# - seconds: the seconds part (e.g. "1443184889")
+#
+sub raw_date_to_s
+{
+ return (split / /, $_[0])[0];
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# date = format_date_obs(git_date)
+#
+# Convert git raw date to obs format.
+# - git_date: raw git format (e.g. "1443184889 +0200")
+# - date: obs format ("Fri Sep 25 12:41:29 UTC 2015")
+#
+sub format_date_obs
+{
+ my @d = gmtime(raw_date_to_s($_[0]));
+
+ return
+ qw(Sun Mon Tue Wed Thu Fri Sat)[$d[6]] . " " .
+ qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$d[4]] . " " .
+ $d[3] . " " .
+ sprintf("%02d:%02d:%02d", $d[2], $d[1], $d[0]) . " UTC " .
+ (1900 + $d[5]);
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# date = format_date_iso(git_date)
+#
+# Convert git raw date to iso format.
+# - git_date: raw git format (e.g. "1443184889 +0200")
+# - date: obs format ("2015-09-25")
+#
+sub format_date_iso
+{
+ my @d = gmtime(raw_date_to_s($_[0]));
+
+ return sprintf("%04d-%02d-%02d", 1900 + $d[5], $d[4] + 1, $d[3]);
+}
diff --git a/grub_build b/grub_build
new file mode 100755
index 0000000..03a67fa
--- /dev/null
+++ b/grub_build
@@ -0,0 +1,39 @@
+#! /bin/sh
+
+# script to update gfxboot sources in grub2 and rebuild it
+#
+# use --bios or --efi option to rebuild only either target
+#
+
+. ./config_vars
+
+
+# - - - lets start... - - -
+
+user_spec=`stat -c '%u:%g' $HOME`
+
+if [ ! -d $grub_dir ] ; then
+ echo "no grub build project prepared"
+ exit 1
+fi
+
+(
+ cd $grub_dir
+
+ if [ `stat -c '%u:%g' .` != "$user_spec" ] ; then
+ $su chown "$user_spec" -R .
+ echo adjusting grub build tree ownership
+ fi
+)
+
+mkdir -p $grub_dir/grub-core/$grub_module
+cp -a $grub_files $grub_dir/grub-core/$grub_module
+
+if [ "$1" != "--efi" ] ; then
+ $su chroot --userspec "$user_spec" $grub_root/ sh -c "cd $grub_build/build ; make"
+fi
+
+if [ "$1" != "--bios" ] ; then
+ $su chroot --userspec "$user_spec" $grub_root/ sh -c "cd $grub_build/build-efi ; make"
+fi
+
diff --git a/mk_grub_test b/mk_grub_test
new file mode 100755
index 0000000..c45b88c
--- /dev/null
+++ b/mk_grub_test
@@ -0,0 +1,75 @@
+#! /bin/bash
+
+# script to copy the updated gfxboot module from the grub2 tree and build a
+# test cd with them
+#
+
+. ./config_vars
+
+rm -rf $grub_iso_dir
+cp -a $grub_iso_src $grub_iso_dir
+
+mkdir -p $grub_iso_dir/EFI/BOOT
+
+test_grub_dir=$grub_iso_dir/boot/x86_64/grub2-efi
+mkdir $test_grub_dir
+
+mods="
+ gfxboot gfxterm
+ video videoinfo vga vbe
+ biosdisk linux
+ ext2 btrfs ext2 xfs jfs reiserfs
+ all_video boot cat chain configfile echo
+ font gzio halt iso9660
+ jpeg minicmd normal part_apple part_msdos part_gpt
+ password_pbkdf2 png reboot search search_fs_uuid
+ search_fs_file search_label sleep test video fat loadenv
+"
+
+efimods="
+ gfxboot gfxterm
+ video videoinfo efi_gop
+ linuxefi
+ ext2 btrfs ext2 xfs jfs reiserfs
+ all_video boot cat chain configfile echo
+ font gzio halt iso9660
+ jpeg minicmd normal part_apple part_msdos part_gpt
+ password_pbkdf2 png reboot search search_fs_uuid
+ search_fs_file search_label sleep test video fat loadenv
+"
+
+# - - - lets start... - - -
+
+echo re-building $grub_iso
+
+(
+ mods=$(echo $mods)
+ sw 0 chroot $grub_root/ sh -c "cd b/build ; ./grub-mkimage -d grub-core -O i386-pc -o core.img --prefix=/boot/x86_64/grub2-efi $mods"
+ cd $grub_dir/build
+ cat grub-core/cdboot.img core.img >cd.img
+)
+
+cp $grub_dir/build/cd.img $test_grub_dir
+
+cp files/grub.cfg $test_grub_dir
+perl -pi -e 's/(linux|initrd)efi/$1/' $test_grub_dir/grub.cfg
+
+(
+ efimods=$(echo $efimods)
+ sw 0 chroot $grub_root/ sh -c "cd b/build-efi ; ./grub-mkimage -d grub-core -O x86_64-efi -o grub.efi --prefix= $efimods"
+ cd $grub_dir/build-efi
+ cp grub.efi $grub_iso_dir/EFI/BOOT/bootx64.efi
+ mcopy -i $grub_iso_dir/boot/x86_64/efi grub.efi ::/EFI/BOOT/bootx64.efi
+)
+
+cp files/grub.cfg $grub_iso_dir/EFI/BOOT/
+mcopy -i $grub_iso_dir/boot/x86_64/efi files/grub.cfg ::/EFI/BOOT
+
+cp files/* $test_grub_dir
+
+for i in $test_grub_dir/*.gs ; do
+ ./gfxboot-compile -c ${i/.gs/.gc} $i
+done
+
+mksusecd --nano --no-hybrid --no-digest --grub2 -c $grub_iso $grub_iso_dir
+
diff --git a/mk_reference b/mk_reference
new file mode 100755
index 0000000..eda3e33
--- /dev/null
+++ b/mk_reference
@@ -0,0 +1,247 @@
+#! /usr/bin/perl
+
+use strict;
+
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+$Data::Dumper::Terse = 1;
+$Data::Dumper::Indent = 1;
+
+sub word_sort;
+sub process_comment;
+sub xref;
+
+# vocabulary definitions
+my $prim = $ARGV[0];
+# C source
+my $source = $ARGV[1];
+# doc template
+my $doc = $ARGV[2];
+# generated doc
+my $dst = $ARGV[3];
+
+my $vocab;
+my $groups;
+
+open my $f, $prim or die "$prim: $!\n";
+
+while(<$f>) {
+ next if /^\s*(#|$)/;
+ my @i = split /\s+/;
+ my $name = $i[1] ? $i[1] : $i[0];
+ $vocab->{$name}{name} = $name;
+ $vocab->{$name}{str} = $i[0];
+}
+
+close $f;
+
+open my $f, $source or die "$source: $!\n";
+
+my $comm;
+while(<$f>) {
+ if(m#^// ?(.*?)$#) {
+ my $c = $1;
+ push @$comm, $c unless $c =~ /^- - -/;
+ next;
+ }
+ if(/^void gfx_prim_([^\(]+)/ && exists $vocab->{$1}) {
+ $vocab->{$1}{doc} = $comm;
+ undef $comm;
+ next;
+ }
+ if(/^\}/) {
+ undef $comm;
+ next;
+ }
+}
+
+close $f;
+
+process_comment $vocab->{$_} for keys %$vocab;
+
+my $doc_template;
+
+open my $f, $doc or die "$doc: $!\n";
+{
+ local $/;
+ $doc_template = <$f>;
+}
+close $f;
+
+my $all_docs;
+
+for (sort { word_sort } keys %$vocab) {
+ next if @{$vocab->{$_}{doc}} == 0;
+ my $str = $vocab->{$_}{doc_str};
+
+ $str = xref $str, $_;
+
+ $all_docs .= $str;
+ $all_docs .= "\n";
+}
+
+$doc_template =~ s/PRIMITIVE_WORD_LIST/$all_docs/;
+
+open my $f, ">", $dst or die "$dst: $!\n";
+
+print $f $doc_template;
+
+close $f;
+
+my $undoc;
+
+for (sort { word_sort } keys %$vocab) {
+ next if @{$vocab->{$_}{doc}} != 0;
+ push @$undoc, $vocab->{$_}{name};
+}
+
+print "undocumented words: ", join(", ", @$undoc), "\n" if $undoc;
+
+# print Dumper $vocab;
+
+
+sub process_comment
+{
+ my $d = $_[0];
+
+ if($d->{doc}) {
+ while(@{$d->{doc}} >= 1) {
+ if($d->{doc}[-1] eq "") {
+ pop @{$d->{doc}}
+ }
+ else {
+ last;
+ }
+ }
+ }
+
+ my $vars;
+ for my $c (@{$d->{doc}}) {
+ if($c =~ /^\(.*--.*\)/) {
+ while($c =~ m/([a-z0-9_]+)/g) {
+ my $v1 = $1;
+ my $v2 = $v1;
+ $v2 =~ s/_(\S+)/~$1~/;
+ $vars->{$v1} = "__${v2}__";
+ }
+ }
+ }
+
+ my $all_vars = join '|', keys %$vars;
+
+ my $doc;
+ my $new_para;
+ my $example;
+ my $cnt = 0;
+ my $item_start;
+ my $notes;
+ for my $c (@{$d->{doc}}) {
+ $cnt++;
+ if($cnt == 1) {
+ $doc = "* **+$d->{str}+** - $c [[$d->{name}]]\n";
+ next;
+ }
+ if($example) {
+ $doc .= "$c\n";
+ next;
+ }
+ if($c eq "") {
+ $doc .= "+\n" unless $new_para;
+ $new_para = 1;
+ next;
+ }
+ if($c =~ /^groups?:\s*(\S+)/i) {
+ for my $g (split /,/, $1) {
+ $groups->{$g}{$d->{name}} = 1;
+ $d->{groups}{$g} = 1;
+ }
+ next;
+ }
+ if($c =~ /^examples?:/i) {
+ if($item_start) {
+ $doc .= "--\n+\n";
+ $item_start = 0;
+ }
+ $doc .= ".Examples\n```\n";
+ $example = 1;
+ next;
+ }
+ $new_para = 0;
+ my $x = $c;
+ if($c =~ /^\(.*--.*\)/) {
+ $x = "** $x";
+ $x = "--\n$x" if !$item_start;
+ $item_start = 1;
+ }
+ elsif(
+ $item_start &&
+ $c ne "" &&
+ (!($c =~ /^($all_vars):/ || ($c =~ /^($all_vars)\b/ && $c =~ /\b($all_vars)\b/)) || $all_vars eq "")
+ ) {
+ $x = "XXX--\n$x";
+ $item_start = 0;
+ $notes = 1;
+ }
+ else {
+ $x = "+\n$x" unless $notes;
+ }
+ for my $v (keys %$vars) {
+ $x =~ s/\b$v\b/$vars->{$v}/g;
+ }
+ $doc .= "$x\n";
+ }
+
+ $doc .= "--\n" if $item_start;
+ $doc =~ s/\+\nXXX--\n/--\n+\n/;
+ $doc .= "```\n" if $example;
+
+ if($d->{groups}) {
+# $doc .= "+\nSee also: XXX_XREF\n";
+ $doc .= "XXX_XREF";
+ }
+
+ $doc =~ s/\+\n\+\n/+\n/g;
+
+ $d->{doc_str} = $doc;
+}
+
+
+sub xref
+{
+ my $doc = $_[0];
+ my $name = $_[1];
+
+ my $refs;
+
+ if($vocab->{$name}{groups}) {
+ for my $g (keys %{$vocab->{$name}{groups}}) {
+ for my $n (keys %{$groups->{$g}}) {
+ $refs->{$n} = "xref:$n\[+$vocab->{$n}{str}+\]";
+ }
+ }
+ delete $refs->{$name};
+
+ my $r = join ", ", map { $refs->{$_} } sort { word_sort } keys %$refs;
+
+ if($r ne "") {
+ $doc =~ s/XXX_XREF/+\nSee also: $r\n/;
+ }
+ else {
+ $doc =~ s/XXX_XREF//;
+ }
+ }
+
+ return $doc;
+}
+
+
+sub word_sort
+{
+ my $x = $vocab->{$a}{str};
+ my $y = $vocab->{$b}{str};
+
+ my $x = $x eq '{' || $x eq '}' ? " $x" : $x;
+ my $y = $y eq '{' || $y eq '}' ? " $y" : $y;
+
+ return $x cmp $y;
+}
diff --git a/mk_vocabulary b/mk_vocabulary
new file mode 100755
index 0000000..7cb97ad
--- /dev/null
+++ b/mk_vocabulary
@@ -0,0 +1,127 @@
+#! /usr/bin/perl
+
+use strict;
+
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+$Data::Dumper::Terse = 1;
+$Data::Dumper::Indent = 1;
+
+sub print_list;
+
+# vocabulary definitions
+my $prim = $ARGV[0];
+# type definitions
+my $type = $ARGV[1];
+# generated C header file
+my $dst = $ARGV[2];
+
+my $vocab;
+my $types;
+
+open my $f, $prim or die "$prim: $!\n";
+
+while(<$f>) {
+ next if /^\s*(#|$)/;
+ my @i = split /\s+/;
+ push @$vocab, { str => $i[0], name => $i[1] ? $i[1] : $i[0] };
+}
+
+close $f;
+
+open my $f, $type or die "$type: $!\n";
+
+while(<$f>) {
+ next if /^\s*(#|$)/;
+ my @i = split /\s+/;
+ push @$types, { str => $i[0], name => $i[1] ? $i[1] : $i[0] };
+}
+
+close $f;
+
+open my $f, ">", $dst or die "$dst: $!\n";
+
+print $f <<"----"
+#define GFXBOOT_MAGIC 0x60ad7a42a91251L
+
+#ifdef WITH_PRIM_NAMES
+const char *prim_names[] = {
+----
+;
+
+my $list;
+push @$list, "\"$_->{str}\"" for @$vocab;
+print_list $f, $list, 8;
+
+print $f "\n};\n#endif\n\n";
+
+
+print $f "enum {\n";
+
+my $list;
+push @$list, "prim_idx_$_->{name}" for @$vocab;
+print_list $f, $list, 4;
+
+print $f "\n};\n\n";
+
+
+print $f "#ifdef WITH_PRIM_HEADERS\n";
+
+my $list;
+push @$list, "gfx_prim_$_->{name}" for @$vocab;
+print $f "static void $_(void);\n" for @$list;
+
+
+print $f "\nstatic void (*gfx_prim_list[])(void) = {\n";
+
+print_list $f, $list, 4;
+
+print $f "\n};\n#endif\n\n";
+
+
+print $f "#ifdef WITH_TYPE_NAMES\nconst char *type_name[] = {\n";
+
+my $list;
+push @$list, "\"$_->{str}\"" for @$types;
+print_list $f, $list, 8;
+
+print $f "\n};\n#endif\n\n";
+
+
+print $f <<"----"
+#define TYPE_EXPECTS_DATA(a) ((a) >= t_comment)
+
+typedef enum {
+----
+;
+
+my $list;
+push @$list, "t_$_->{name}" for @$types;
+print_list $f, $list, 4;
+
+print $f "\n} type_t;\n";
+
+close $f;
+
+
+sub print_list
+{
+ my $f = $_[0];
+ my $list = $_[1];
+ my $cols = $_[2];
+
+ my $cnt = 0;
+ for (@$list) {
+ print $f "," if $cnt;
+ if($cnt % $cols) {
+ print $f " ";
+ }
+ else {
+ print $f "\n" if $cnt;
+ print $f " ";
+ }
+ print $f "$_";
+ $cnt++;
+ }
+}
+
diff --git a/mk_x11_test b/mk_x11_test
new file mode 100755
index 0000000..256dfaa
--- /dev/null
+++ b/mk_x11_test
@@ -0,0 +1,12 @@
+#! /bin/bash
+
+rm -rf x11
+mkdir x11
+
+cp files/* x11
+rm x11/*~
+
+for i in x11/*.gs ; do
+ # ./gfxboot-compile -Oc ${i/.gs/.gc} $i
+ ./gfxboot-compile -O1 -vc ${i/.gs/.gc} -l $i.log $i
+done
diff --git a/patches/grub-2.04.diff b/patches/grub-2.04.diff
new file mode 100644
index 0000000..f034148
--- /dev/null
+++ b/patches/grub-2.04.diff
@@ -0,0 +1,256 @@
+diff -ru grub-2.04.orig/include/grub/menu_viewer.h grub-2.04/include/grub/menu_viewer.h
+--- grub-2.04.orig/include/grub/menu_viewer.h 2020-03-15 17:27:58.095304000 +0100
++++ grub-2.04/include/grub/menu_viewer.h 2020-03-11 20:15:05.837002361 +0100
+@@ -35,6 +35,7 @@
+ void (*clear_timeout) (void *data);
+ void (*scroll_chosen_entry) (void *data, int diren);
+ void (*fini) (void *fini);
++ int (*process_key) (int *key);
+ };
+
+ void grub_menu_register_viewer (struct grub_menu_viewer *viewer);
+Only in grub-2.04/include/grub: menu_viewer.h~
+diff -ru grub-2.04.orig/include/grub/video.h grub-2.04/include/grub/video.h
+--- grub-2.04.orig/include/grub/video.h 2018-11-24 18:13:02.000000000 +0100
++++ grub-2.04/include/grub/video.h 2020-03-11 20:34:46.551694570 +0100
+@@ -331,7 +331,9 @@
+ grub_video_mode_type_t mode_type,
+ grub_video_mode_type_t mode_mask);
+
+- grub_err_t (*get_info) (struct grub_video_mode_info *mode_info);
++ grub_err_t (*get_info) (struct grub_video_mode_info *mode_info, void **framebuffer);
++
++ grub_err_t (*get_raw_info) (struct grub_video_mode_info *mode_info, void **framebuffer);
+
+ grub_err_t (*get_info_and_fini) (struct grub_video_mode_info *mode_info,
+ void **framebuffer);
+@@ -437,6 +439,8 @@
+
+ grub_err_t EXPORT_FUNC (grub_video_get_info) (struct grub_video_mode_info *mode_info);
+
++grub_err_t EXPORT_FUNC (grub_video_get_raw_info) (struct grub_video_mode_info *mode_info, void **framebuffer);
++
+ /* Framebuffer address may change as a part of normal operation
+ (e.g. double buffering). That's why you need to stop video subsystem to be
+ sure that framebuffer address doesn't change. To ensure this abstraction
+Only in grub-2.04/include/grub: video.h~
+diff -ru grub-2.04.orig/include/grub/video_fb.h grub-2.04/include/grub/video_fb.h
+--- grub-2.04.orig/include/grub/video_fb.h 2018-11-24 18:13:02.000000000 +0100
++++ grub-2.04/include/grub/video_fb.h 2020-03-11 20:23:10.733793798 +0100
+@@ -42,7 +42,7 @@
+ EXPORT_FUNC(grub_video_fb_fini) (void);
+
+ grub_err_t
+-EXPORT_FUNC(grub_video_fb_get_info) (struct grub_video_mode_info *mode_info);
++EXPORT_FUNC(grub_video_fb_get_info) (struct grub_video_mode_info *mode_info, void **framebuf);
+
+ grub_err_t
+ EXPORT_FUNC(grub_video_fb_get_palette) (unsigned int start, unsigned int count,
+Only in grub-2.04/include/grub: video_fb.h~
+diff -ru -x Makefile.core.am -x Makefile.in -x ChangeLog grub-2.04.orig/grub-core/Makefile.core.def grub-2.04/grub-core/Makefile.core.def
+--- grub-2.04.orig/grub-core/Makefile.core.def 2020-03-15 17:27:58.083304168 +0100
++++ grub-2.04/grub-core/Makefile.core.def 2020-04-19 20:15:45.725352760 +0200
+@@ -1576,6 +1576,29 @@
+ };
+
+ module = {
++ name = gfxboot;
++ common = gfxboot/gfxboot.c;
++ common = gfxboot/gfxboot_array.c;
++ common = gfxboot/gfxboot_canvas.c;
++ common = gfxboot/gfxboot_context.c;
++ common = gfxboot/gfxboot_debug.c;
++ common = gfxboot/gfxboot_draw.c;
++ common = gfxboot/gfxboot_font.c;
++ common = gfxboot/gfxboot_grub.c;
++ common = gfxboot/gfxboot_gstate.c;
++ common = gfxboot/gfxboot_hash.c;
++ common = gfxboot/gfxboot_jpeg.c;
++ common = gfxboot/gfxboot_lib.c;
++ common = gfxboot/gfxboot_main.c;
++ common = gfxboot/gfxboot_malloc.c;
++ common = gfxboot/gfxboot_mem.c;
++ common = gfxboot/gfxboot_num.c;
++ common = gfxboot/gfxboot_obj.c;
++ common = gfxboot/gfxboot_olist.c;
++ common = gfxboot/gfxboot_prim.c;
++};
++
++module = {
+ name = hello;
+ common = hello/hello.c;
+ };
+Only in grub-2.04/grub-core: Makefile.core.def~
+Only in grub-2.04/grub-core: gfxboot
+diff -ru -x Makefile.core.am -x Makefile.in -x ChangeLog grub-2.04.orig/grub-core/normal/menu.c grub-2.04/grub-core/normal/menu.c
+--- grub-2.04.orig/grub-core/normal/menu.c 2020-03-15 17:27:58.095304000 +0100
++++ grub-2.04/grub-core/normal/menu.c 2020-03-15 17:56:52.779074968 +0100
+@@ -392,6 +392,19 @@
+
+ static struct grub_menu_viewer *viewers;
+
++static int
++menu_process_key (int *key)
++{
++ struct grub_menu_viewer *cur;
++ int action = 0;
++
++ for(cur = viewers; cur && key && *key; cur = cur->next) {
++ if(cur->process_key) action = cur->process_key(key);
++ }
++
++ return action;
++}
++
+ static void
+ menu_set_chosen_entry (int entry)
+ {
+@@ -659,8 +672,11 @@
+ int default_entry, current_entry;
+ int timeout;
+ enum timeout_style timeout_style;
++ int action;
++
++ *auto_boot = 0;
+
+- default_entry = get_entry_number (menu, "default");
++ default_entry = current_entry = get_entry_number (menu, "default");
+
+ workaround_snapshot_menu_default_entry (menu, "default", &default_entry);
+
+@@ -779,6 +795,18 @@
+
+ c = grub_getkey_noblock ();
+
++ action = menu_process_key (&c);
++
++ if ((action & 0xff))
++ {
++ *auto_boot = 0;
++ if ((action & 0x01)) menu_fini ();
++ if ((action & 0x02)) *auto_boot = 1;
++ if ((action & 0x04)) grub_cmdline_run (1, 0);
++ if ((action & 0x08)) goto refresh;
++ if ((action & 0x10)) return action >> 8;
++ }
++
+ /* Negative values are returned on error. */
+ if ((c != GRUB_TERM_NO_KEY) && (c > 0))
+ {
+Only in grub-2.04/grub-core/normal: menu.c~
+diff -ru -x Makefile.core.am -x Makefile.in -x ChangeLog grub-2.04.orig/grub-core/video/efi_gop.c grub-2.04/grub-core/video/efi_gop.c
+--- grub-2.04.orig/grub-core/video/efi_gop.c 2020-03-15 17:27:58.095304000 +0100
++++ grub-2.04/grub-core/video/efi_gop.c 2020-03-11 20:26:21.099060901 +0100
+@@ -588,6 +588,24 @@
+ return GRUB_ERR_NONE;
+ }
+
++static grub_err_t
++grub_video_gop_get_raw_info (struct grub_video_mode_info *mode_info, void **framebuf)
++{
++ grub_err_t err = GRUB_ERR_NONE;
++
++ if (mode_info)
++ {
++ err = grub_video_gop_fill_real_mode_info (gop->mode->mode, gop->mode->info, mode_info);
++ if (err)
++ grub_dprintf ("video", "GOP: couldn't fill mode info\n");
++ }
++
++ if (framebuf)
++ *framebuf = (char *) framebuffer.ptr;
++
++ return err;
++}
++
+ static struct grub_video_adapter grub_video_gop_adapter =
+ {
+ .name = "EFI GOP driver",
+@@ -599,6 +617,7 @@
+ .fini = grub_video_gop_fini,
+ .setup = grub_video_gop_setup,
+ .get_info = grub_video_fb_get_info,
++ .get_raw_info = grub_video_gop_get_raw_info,
+ .get_info_and_fini = grub_video_gop_get_info_and_fini,
+ .get_edid = grub_video_gop_get_edid,
+ .set_palette = grub_video_fb_set_palette,
+diff -ru -x Makefile.core.am -x Makefile.in -x ChangeLog grub-2.04.orig/grub-core/video/fb/video_fb.c grub-2.04/grub-core/video/fb/video_fb.c
+--- grub-2.04.orig/grub-core/video/fb/video_fb.c 2018-11-24 18:13:02.000000000 +0100
++++ grub-2.04/grub-core/video/fb/video_fb.c 2020-03-11 20:25:29.923792134 +0100
+@@ -25,6 +25,7 @@
+ #include
+ #include
+ #include
++#include
+
+ GRUB_MOD_LICENSE ("GPLv3+");
+
+@@ -355,11 +356,22 @@
+ }
+
+ grub_err_t
+-grub_video_fb_get_info (struct grub_video_mode_info *mode_info)
++grub_video_fb_get_info (struct grub_video_mode_info *mode_info, void **framebuf)
+ {
+- /* Copy mode info from active render target. */
+- grub_memcpy (mode_info, &framebuffer.render_target->mode_info,
+- sizeof (struct grub_video_mode_info));
++ if (mode_info)
++ {
++ /* Copy mode info from active render target. */
++ grub_memcpy (mode_info, &framebuffer.render_target->mode_info,
++ sizeof (struct grub_video_mode_info));
++ }
++
++ if (framebuf)
++ {
++ *framebuf = (void *) framebuffer.pages[framebuffer.displayed_page];
++ if(!*framebuf && framebuffer.render_target) {
++ *framebuf = (void *) framebuffer.render_target->data;
++ }
++ }
+
+ return GRUB_ERR_NONE;
+ }
+diff -ru -x Makefile.core.am -x Makefile.in -x ChangeLog grub-2.04.orig/grub-core/video/video.c grub-2.04/grub-core/video/video.c
+--- grub-2.04.orig/grub-core/video/video.c 2019-05-20 13:00:01.000000000 +0200
++++ grub-2.04/grub-core/video/video.c 2020-03-11 20:24:05.221003731 +0100
+@@ -60,7 +60,28 @@
+ return grub_errno;
+ }
+
+- return grub_video_adapter_active->get_info (mode_info);
++ return grub_video_adapter_active->get_info (mode_info, NULL);
++}
++
++/* Get (real) information about active video mode and framebuffer pointer. */
++grub_err_t
++grub_video_get_raw_info (struct grub_video_mode_info *mode_info, void **framebuffer)
++{
++ grub_err_t err = GRUB_ERR_NONE;
++
++ if (! grub_video_adapter_active)
++ return grub_error (GRUB_ERR_BAD_DEVICE, "no video mode activated");
++
++ if (grub_video_adapter_active->get_raw_info)
++ {
++ err = grub_video_adapter_active->get_raw_info (mode_info, framebuffer);
++ }
++ else
++ {
++ err = grub_video_adapter_active->get_info (mode_info, framebuffer);
++ }
++
++ return err;
+ }
+
+ grub_video_driver_id_t
+@@ -720,7 +741,7 @@
+ continue;
+ }
+
+- err = p->get_info (&mode_info);
++ err = p->get_info (&mode_info, NULL);
+ if (err != GRUB_ERR_NONE)
+ {
+ p->fini ();
diff --git a/patches/grub_diff b/patches/grub_diff
new file mode 100755
index 0000000..9c8d667
--- /dev/null
+++ b/patches/grub_diff
@@ -0,0 +1,6 @@
+#! /bin/bash
+
+cd ~/g/usr/src/packages/BUILD
+
+diff -ru grub-2.04{.orig,}/include >/tmp/dif
+diff -ru -x Makefile.core.am -x Makefile.in -x ChangeLog grub-2.04{.orig,}/grub-core >>/tmp/dif
diff --git a/run_tests b/run_tests
new file mode 100755
index 0000000..6ac2cae
--- /dev/null
+++ b/run_tests
@@ -0,0 +1,201 @@
+#! /usr/bin/perl
+
+use strict;
+
+use Getopt::Long;
+
+sub prepare_test;
+sub run_test;
+sub verify_test;
+sub table_head;
+
+my $testdir = "tests";
+
+# store reference output, don't do checks
+my $opt_create_reference;
+my $opt_test_pattern = "*";
+
+GetOptions(
+ 'create-reference|r' => \$opt_create_reference,
+ 'test|t=s' => \$opt_test_pattern,
+);
+
+die "error: no gfxboot-compile\n" unless -x "./gfxboot-compile";
+die "error: no gfxboot-x11\n" unless -x "./gfxboot-x11";
+
+my $tests = [ qw ( code mem trace basic code1 opt1 code2 opt2 gc screen ) ];
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+my $count = 0;
+my $failed = 0;
+my $ok = 0;
+
+table_head if !$opt_create_reference;
+
+for my $test (<$testdir/$opt_test_pattern>) {
+ next unless -d $test;
+ $count++;
+ prepare_test $test;
+ run_test $test;
+ prepare_test $test, 1;
+ run_test $test, 1;
+ prepare_test $test, 2;
+ run_test $test, 2;
+ $failed += verify_test $test if !$opt_create_reference;
+}
+
+if($opt_create_reference) {
+ print "$count test results created\n";
+}
+else {
+ $ok = $count - $failed;
+ my $s = sprintf "%4d tests ok", $ok;
+ $s .= sprintf ", %3d failed", $failed if $failed;
+ table_head $s;
+}
+
+exit $failed ? 1 : 0;
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+sub prepare_test
+{
+ my $test = $_[0];
+ my $ref = $opt_create_reference ? ".ref" : "";
+ my $opt = $_[1] ? "-O$_[1]" : "";
+
+ system "./gfxboot-compile $opt -v -c $test/main.gc -l $test/code$_[1].log$ref $test/main.gs";
+
+ if($ref && $opt) {
+# unlink "$test/code$_[1].log$ref";
+ }
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+sub run_test
+{
+ my $test = $_[0];
+ my $opt = $_[1] ? "_opt$_[1]" : "";
+ my $opt_log = "opt$_[1].log";
+ my $ref = $opt_create_reference ? ".ref" : "";
+
+ system "./gfxboot-x11 --no-x11 --file tests/test_script $test >$test/test$opt.log";
+
+ if(open my $f, "$test/test$opt.log") {
+ local $/;
+ $_ = <$f>;
+ close $f;
+
+ if(/\n(# --- trace ---\n.*\n)# --- trace_end ---\n/s) {
+ my $s = $1;
+ $s =~ s/\n[^\n]+trace_end[^\n]+$//s;
+ if(!$opt) {
+ if(open my $f, ">$test/trace.log$ref") {
+ print $f $s;
+ close $f;
+ }
+ }
+ my $opt_log_name = "$test/opt.log.ref";
+ if($opt) {
+ $opt_log_name = "$test/$opt_log";
+ }
+ if(open my $f, ">", $opt_log_name) {
+ $s =~ s/, (ofs|current) 0x[0-9a-f]+\b//g;
+ $s =~ s/, ip 0x[0-9a-f]+ \(0x[0-9a-f]+\)//g;
+ $s =~ s/, size \d+\b//g;
+ $s =~ s/\nIP: [^\n]+//g;
+ $s =~ s/(\nerror [^\n]+):[^\n]+/$1/g;
+ print $f $s;
+ close $f;
+ }
+ if(open my $f, ">", "$test/basic.log$ref") {
+ $s =~ s/#\d+(\.\d+\.\d+\.)/#xxxx$1/g;
+ print $f $s;
+ close $f;
+ }
+ if($opt) {
+ link "$test/opt.log.ref", "$test/$opt_log.ref";
+ }
+ }
+
+ if(!$opt) {
+ if(/\n(# --- mem ---\n.*\n)# --- mem_end ---\n/s) {
+ my $s = $1;
+ $s =~ s/\n[^\n]+mem_end[^\n]+$//s;
+ if(open my $f, ">$test/mem.log$ref") {
+ print $f $s;
+ close $f;
+ }
+ }
+
+ if(/\n(# --- gc ---\n.*\n)# --- gc_end ---\n/s) {
+ my $s = $1;
+ $s =~ s/\n[^\n]+gc_end[^\n]+$//s;
+ if(open my $f, ">$test/gc.log$ref") {
+ print $f $s;
+ close $f;
+ }
+ }
+
+ if(/\n(# --- screen ---\n.*\n)# --- screen_end ---\n/s) {
+ my $s = $1;
+ $s =~ s/\n[^\n]+screen_end[^\n]+$//s;
+ if(open my $f, ">$test/screen.log$ref") {
+ print $f $s;
+ close $f;
+ }
+ }
+ }
+ }
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+sub verify_test
+{
+ my $test = $_[0];
+ my $all_err = 0;
+
+ my $name = $test;
+ $name =~ s#^.*/##;
+
+ printf "%-26s|", $name;
+
+ my $msg;
+
+ for my $v (@$tests) {
+ my $res;
+ my $ref;
+ if(open my $f, "$test/$v.log") { local $/; $res = <$f>; close $f; }
+ if(open my $f, "$test/$v.log.ref") { local $/; $ref = <$f>; close $f; }
+
+ my $err;
+ if($v eq "gc" ) {
+ # gc should have cleaned up everything but for 2 objects:
+ # - 1. object list
+ # - 2. gc's own data
+ $err = $res !~ /olist
+== stack (#xxxx.1.1.array) ==
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.bool <0 (0x0)>
+ [1] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #0.0.nil
+ [1] #xxxx.1.1.num.bool <0 (0x0)>
+ [2] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <0 (0x0)>
+ [1] #0.0.nil
+ [2] #xxxx.1.1.num.bool <0 (0x0)>
+ [3] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <18 (0x12)>
+ [1] #xxxx.1.1.num.int <0 (0x0)>
+ [2] #0.0.nil
+ [3] #xxxx.1.1.num.bool <0 (0x0)>
+ [4] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <4660 (0x1234)>
+ [1] #xxxx.1.1.num.int <18 (0x12)>
+ [2] #xxxx.1.1.num.int <0 (0x0)>
+ [3] #0.0.nil
+ [4] #xxxx.1.1.num.bool <0 (0x0)>
+ [5] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [1] #xxxx.1.1.num.int <4660 (0x1234)>
+ [2] #xxxx.1.1.num.int <18 (0x12)>
+ [3] #xxxx.1.1.num.int <0 (0x0)>
+ [4] #0.0.nil
+ [5] #xxxx.1.1.num.bool <0 (0x0)>
+ [6] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [1] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [2] #xxxx.1.1.num.int <4660 (0x1234)>
+ [3] #xxxx.1.1.num.int <18 (0x12)>
+ [4] #xxxx.1.1.num.int <0 (0x0)>
+ [5] #0.0.nil
+ [6] #xxxx.1.1.num.bool <0 (0x0)>
+ [7] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [1] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [2] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [3] #xxxx.1.1.num.int <4660 (0x1234)>
+ [4] #xxxx.1.1.num.int <18 (0x12)>
+ [5] #xxxx.1.1.num.int <0 (0x0)>
+ [6] #0.0.nil
+ [7] #xxxx.1.1.num.bool <0 (0x0)>
+ [8] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [1] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [2] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [3] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [4] #xxxx.1.1.num.int <4660 (0x1234)>
+ [5] #xxxx.1.1.num.int <18 (0x12)>
+ [6] #xxxx.1.1.num.int <0 (0x0)>
+ [7] #0.0.nil
+ [8] #xxxx.1.1.num.bool <0 (0x0)>
+ [9] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [1] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [2] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [3] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [4] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [5] #xxxx.1.1.num.int <4660 (0x1234)>
+ [6] #xxxx.1.1.num.int <18 (0x12)>
+ [7] #xxxx.1.1.num.int <0 (0x0)>
+ [8] #0.0.nil
+ [9] #xxxx.1.1.num.bool <0 (0x0)>
+ [10] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [1] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [2] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [3] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [4] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [5] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [6] #xxxx.1.1.num.int <4660 (0x1234)>
+ [7] #xxxx.1.1.num.int <18 (0x12)>
+ [8] #xxxx.1.1.num.int <0 (0x0)>
+ [9] #0.0.nil
+ [10] #xxxx.1.1.num.bool <0 (0x0)>
+ [11] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [1] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [2] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [3] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [4] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [5] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [6] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [7] #xxxx.1.1.num.int <4660 (0x1234)>
+ [8] #xxxx.1.1.num.int <18 (0x12)>
+ [9] #xxxx.1.1.num.int <0 (0x0)>
+ [10] #0.0.nil
+ [11] #xxxx.1.1.num.bool <0 (0x0)>
+ [12] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [1] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [2] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [3] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [4] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [5] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [6] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [7] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [8] #xxxx.1.1.num.int <4660 (0x1234)>
+ [9] #xxxx.1.1.num.int <18 (0x12)>
+ [10] #xxxx.1.1.num.int <0 (0x0)>
+ [11] #0.0.nil
+ [12] #xxxx.1.1.num.bool <0 (0x0)>
+ [13] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [1] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [2] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [3] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [4] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [5] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [6] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [7] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [8] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [9] #xxxx.1.1.num.int <4660 (0x1234)>
+ [10] #xxxx.1.1.num.int <18 (0x12)>
+ [11] #xxxx.1.1.num.int <0 (0x0)>
+ [12] #0.0.nil
+ [13] #xxxx.1.1.num.bool <0 (0x0)>
+ [14] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <127 (0x7f)>
+ [1] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [2] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [3] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [4] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [5] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [6] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [7] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [8] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [9] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [10] #xxxx.1.1.num.int <4660 (0x1234)>
+ [11] #xxxx.1.1.num.int <18 (0x12)>
+ [12] #xxxx.1.1.num.int <0 (0x0)>
+ [13] #0.0.nil
+ [14] #xxxx.1.1.num.bool <0 (0x0)>
+ [15] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <128 (0x80)>
+ [1] #xxxx.1.1.num.int <127 (0x7f)>
+ [2] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [3] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [4] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [5] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [6] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [7] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [8] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [9] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [10] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [11] #xxxx.1.1.num.int <4660 (0x1234)>
+ [12] #xxxx.1.1.num.int <18 (0x12)>
+ [13] #xxxx.1.1.num.int <0 (0x0)>
+ [14] #0.0.nil
+ [15] #xxxx.1.1.num.bool <0 (0x0)>
+ [16] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <256 (0x100)>
+ [1] #xxxx.1.1.num.int <128 (0x80)>
+ [2] #xxxx.1.1.num.int <127 (0x7f)>
+ [3] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [4] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [5] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [6] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [7] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [8] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [9] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [10] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [11] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [12] #xxxx.1.1.num.int <4660 (0x1234)>
+ [13] #xxxx.1.1.num.int <18 (0x12)>
+ [14] #xxxx.1.1.num.int <0 (0x0)>
+ [15] #0.0.nil
+ [16] #xxxx.1.1.num.bool <0 (0x0)>
+ [17] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [1] #xxxx.1.1.num.int <256 (0x100)>
+ [2] #xxxx.1.1.num.int <128 (0x80)>
+ [3] #xxxx.1.1.num.int <127 (0x7f)>
+ [4] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [5] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [6] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [7] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [8] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [9] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [10] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [11] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [12] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [13] #xxxx.1.1.num.int <4660 (0x1234)>
+ [14] #xxxx.1.1.num.int <18 (0x12)>
+ [15] #xxxx.1.1.num.int <0 (0x0)>
+ [16] #0.0.nil
+ [17] #xxxx.1.1.num.bool <0 (0x0)>
+ [18] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [1] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [2] #xxxx.1.1.num.int <256 (0x100)>
+ [3] #xxxx.1.1.num.int <128 (0x80)>
+ [4] #xxxx.1.1.num.int <127 (0x7f)>
+ [5] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [6] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [7] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [8] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [9] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [10] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [11] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [12] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [13] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [14] #xxxx.1.1.num.int <4660 (0x1234)>
+ [15] #xxxx.1.1.num.int <18 (0x12)>
+ [16] #xxxx.1.1.num.int <0 (0x0)>
+ [17] #0.0.nil
+ [18] #xxxx.1.1.num.bool <0 (0x0)>
+ [19] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [1] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [2] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [3] #xxxx.1.1.num.int <256 (0x100)>
+ [4] #xxxx.1.1.num.int <128 (0x80)>
+ [5] #xxxx.1.1.num.int <127 (0x7f)>
+ [6] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [7] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [8] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [9] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [10] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [11] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [12] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [13] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [14] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [15] #xxxx.1.1.num.int <4660 (0x1234)>
+ [16] #xxxx.1.1.num.int <18 (0x12)>
+ [17] #xxxx.1.1.num.int <0 (0x0)>
+ [18] #0.0.nil
+ [19] #xxxx.1.1.num.bool <0 (0x0)>
+ [20] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [1] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [2] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [3] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [4] #xxxx.1.1.num.int <256 (0x100)>
+ [5] #xxxx.1.1.num.int <128 (0x80)>
+ [6] #xxxx.1.1.num.int <127 (0x7f)>
+ [7] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [8] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [9] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [10] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [11] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [12] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [13] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [14] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [15] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [16] #xxxx.1.1.num.int <4660 (0x1234)>
+ [17] #xxxx.1.1.num.int <18 (0x12)>
+ [18] #xxxx.1.1.num.int <0 (0x0)>
+ [19] #0.0.nil
+ [20] #xxxx.1.1.num.bool <0 (0x0)>
+ [21] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [1] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [2] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [3] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [4] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [5] #xxxx.1.1.num.int <256 (0x100)>
+ [6] #xxxx.1.1.num.int <128 (0x80)>
+ [7] #xxxx.1.1.num.int <127 (0x7f)>
+ [8] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [9] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [10] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [11] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [12] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [13] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [14] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [15] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [16] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [17] #xxxx.1.1.num.int <4660 (0x1234)>
+ [18] #xxxx.1.1.num.int <18 (0x12)>
+ [19] #xxxx.1.1.num.int <0 (0x0)>
+ [20] #0.0.nil
+ [21] #xxxx.1.1.num.bool <0 (0x0)>
+ [22] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [1] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [2] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [3] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [4] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [5] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [6] #xxxx.1.1.num.int <256 (0x100)>
+ [7] #xxxx.1.1.num.int <128 (0x80)>
+ [8] #xxxx.1.1.num.int <127 (0x7f)>
+ [9] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [10] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [11] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [12] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [13] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [14] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [15] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [16] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [17] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [18] #xxxx.1.1.num.int <4660 (0x1234)>
+ [19] #xxxx.1.1.num.int <18 (0x12)>
+ [20] #xxxx.1.1.num.int <0 (0x0)>
+ [21] #0.0.nil
+ [22] #xxxx.1.1.num.bool <0 (0x0)>
+ [23] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <0 (0x0)>
+ [1] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [2] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [3] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [4] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [5] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [6] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [7] #xxxx.1.1.num.int <256 (0x100)>
+ [8] #xxxx.1.1.num.int <128 (0x80)>
+ [9] #xxxx.1.1.num.int <127 (0x7f)>
+ [10] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [11] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [12] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [13] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [14] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [15] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [16] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [17] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [18] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [19] #xxxx.1.1.num.int <4660 (0x1234)>
+ [20] #xxxx.1.1.num.int <18 (0x12)>
+ [21] #xxxx.1.1.num.int <0 (0x0)>
+ [22] #0.0.nil
+ [23] #xxxx.1.1.num.bool <0 (0x0)>
+ [24] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <10 (0xa)>
+ [1] #xxxx.1.1.num.int <0 (0x0)>
+ [2] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [3] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [4] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [5] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [6] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [7] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [8] #xxxx.1.1.num.int <256 (0x100)>
+ [9] #xxxx.1.1.num.int <128 (0x80)>
+ [10] #xxxx.1.1.num.int <127 (0x7f)>
+ [11] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [12] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [13] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [14] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [15] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [16] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [17] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [18] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [19] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [20] #xxxx.1.1.num.int <4660 (0x1234)>
+ [21] #xxxx.1.1.num.int <18 (0x12)>
+ [22] #xxxx.1.1.num.int <0 (0x0)>
+ [23] #0.0.nil
+ [24] #xxxx.1.1.num.bool <0 (0x0)>
+ [25] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <9 (0x9)>
+ [1] #xxxx.1.1.num.int <10 (0xa)>
+ [2] #xxxx.1.1.num.int <0 (0x0)>
+ [3] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [4] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [5] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [6] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [7] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [8] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [9] #xxxx.1.1.num.int <256 (0x100)>
+ [10] #xxxx.1.1.num.int <128 (0x80)>
+ [11] #xxxx.1.1.num.int <127 (0x7f)>
+ [12] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [13] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [14] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [15] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [16] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [17] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [18] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [19] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [20] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [21] #xxxx.1.1.num.int <4660 (0x1234)>
+ [22] #xxxx.1.1.num.int <18 (0x12)>
+ [23] #xxxx.1.1.num.int <0 (0x0)>
+ [24] #0.0.nil
+ [25] #xxxx.1.1.num.bool <0 (0x0)>
+ [26] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <39 (0x27)>
+ [1] #xxxx.1.1.num.int <9 (0x9)>
+ [2] #xxxx.1.1.num.int <10 (0xa)>
+ [3] #xxxx.1.1.num.int <0 (0x0)>
+ [4] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [5] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [6] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [7] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [8] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [9] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [10] #xxxx.1.1.num.int <256 (0x100)>
+ [11] #xxxx.1.1.num.int <128 (0x80)>
+ [12] #xxxx.1.1.num.int <127 (0x7f)>
+ [13] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [14] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [15] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [16] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [17] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [18] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [19] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [20] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [21] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [22] #xxxx.1.1.num.int <4660 (0x1234)>
+ [23] #xxxx.1.1.num.int <18 (0x12)>
+ [24] #xxxx.1.1.num.int <0 (0x0)>
+ [25] #0.0.nil
+ [26] #xxxx.1.1.num.bool <0 (0x0)>
+ [27] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <92 (0x5c)>
+ [1] #xxxx.1.1.num.int <39 (0x27)>
+ [2] #xxxx.1.1.num.int <9 (0x9)>
+ [3] #xxxx.1.1.num.int <10 (0xa)>
+ [4] #xxxx.1.1.num.int <0 (0x0)>
+ [5] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [6] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [7] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [8] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [9] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [10] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [11] #xxxx.1.1.num.int <256 (0x100)>
+ [12] #xxxx.1.1.num.int <128 (0x80)>
+ [13] #xxxx.1.1.num.int <127 (0x7f)>
+ [14] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [15] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [16] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [17] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [18] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [19] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [20] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [21] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [22] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [23] #xxxx.1.1.num.int <4660 (0x1234)>
+ [24] #xxxx.1.1.num.int <18 (0x12)>
+ [25] #xxxx.1.1.num.int <0 (0x0)>
+ [26] #0.0.nil
+ [27] #xxxx.1.1.num.bool <0 (0x0)>
+ [28] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <97 (0x61)>
+ [1] #xxxx.1.1.num.int <92 (0x5c)>
+ [2] #xxxx.1.1.num.int <39 (0x27)>
+ [3] #xxxx.1.1.num.int <9 (0x9)>
+ [4] #xxxx.1.1.num.int <10 (0xa)>
+ [5] #xxxx.1.1.num.int <0 (0x0)>
+ [6] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [7] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [8] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [9] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [10] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [11] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [12] #xxxx.1.1.num.int <256 (0x100)>
+ [13] #xxxx.1.1.num.int <128 (0x80)>
+ [14] #xxxx.1.1.num.int <127 (0x7f)>
+ [15] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [16] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [17] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [18] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [19] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [20] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [21] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [22] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [23] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [24] #xxxx.1.1.num.int <4660 (0x1234)>
+ [25] #xxxx.1.1.num.int <18 (0x12)>
+ [26] #xxxx.1.1.num.int <0 (0x0)>
+ [27] #0.0.nil
+ [28] #xxxx.1.1.num.bool <0 (0x0)>
+ [29] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [1] #xxxx.1.1.num.int <97 (0x61)>
+ [2] #xxxx.1.1.num.int <92 (0x5c)>
+ [3] #xxxx.1.1.num.int <39 (0x27)>
+ [4] #xxxx.1.1.num.int <9 (0x9)>
+ [5] #xxxx.1.1.num.int <10 (0xa)>
+ [6] #xxxx.1.1.num.int <0 (0x0)>
+ [7] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [8] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [9] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [10] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [11] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [12] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [13] #xxxx.1.1.num.int <256 (0x100)>
+ [14] #xxxx.1.1.num.int <128 (0x80)>
+ [15] #xxxx.1.1.num.int <127 (0x7f)>
+ [16] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [17] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [18] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [19] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [20] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [21] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [22] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [23] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [24] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [25] #xxxx.1.1.num.int <4660 (0x1234)>
+ [26] #xxxx.1.1.num.int <18 (0x12)>
+ [27] #xxxx.1.1.num.int <0 (0x0)>
+ [28] #0.0.nil
+ [29] #xxxx.1.1.num.bool <0 (0x0)>
+ [30] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [1] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [2] #xxxx.1.1.num.int <97 (0x61)>
+ [3] #xxxx.1.1.num.int <92 (0x5c)>
+ [4] #xxxx.1.1.num.int <39 (0x27)>
+ [5] #xxxx.1.1.num.int <9 (0x9)>
+ [6] #xxxx.1.1.num.int <10 (0xa)>
+ [7] #xxxx.1.1.num.int <0 (0x0)>
+ [8] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [9] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [10] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [11] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [12] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [13] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [14] #xxxx.1.1.num.int <256 (0x100)>
+ [15] #xxxx.1.1.num.int <128 (0x80)>
+ [16] #xxxx.1.1.num.int <127 (0x7f)>
+ [17] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [18] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [19] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [20] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [21] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [22] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [23] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [24] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [25] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [26] #xxxx.1.1.num.int <4660 (0x1234)>
+ [27] #xxxx.1.1.num.int <18 (0x12)>
+ [28] #xxxx.1.1.num.int <0 (0x0)>
+ [29] #0.0.nil
+ [30] #xxxx.1.1.num.bool <0 (0x0)>
+ [31] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <78934 (0x13456)>
+ [1] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [2] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [3] #xxxx.1.1.num.int <97 (0x61)>
+ [4] #xxxx.1.1.num.int <92 (0x5c)>
+ [5] #xxxx.1.1.num.int <39 (0x27)>
+ [6] #xxxx.1.1.num.int <9 (0x9)>
+ [7] #xxxx.1.1.num.int <10 (0xa)>
+ [8] #xxxx.1.1.num.int <0 (0x0)>
+ [9] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [10] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [11] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [12] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [13] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [14] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [15] #xxxx.1.1.num.int <256 (0x100)>
+ [16] #xxxx.1.1.num.int <128 (0x80)>
+ [17] #xxxx.1.1.num.int <127 (0x7f)>
+ [18] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [19] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [20] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [21] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [22] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [23] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [24] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [25] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [26] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [27] #xxxx.1.1.num.int <4660 (0x1234)>
+ [28] #xxxx.1.1.num.int <18 (0x12)>
+ [29] #xxxx.1.1.num.int <0 (0x0)>
+ [30] #0.0.nil
+ [31] #xxxx.1.1.num.bool <0 (0x0)>
+ [32] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [1] #xxxx.1.1.num.int <78934 (0x13456)>
+ [2] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [3] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [4] #xxxx.1.1.num.int <97 (0x61)>
+ [5] #xxxx.1.1.num.int <92 (0x5c)>
+ [6] #xxxx.1.1.num.int <39 (0x27)>
+ [7] #xxxx.1.1.num.int <9 (0x9)>
+ [8] #xxxx.1.1.num.int <10 (0xa)>
+ [9] #xxxx.1.1.num.int <0 (0x0)>
+ [10] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [11] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [12] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [13] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [14] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [15] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [16] #xxxx.1.1.num.int <256 (0x100)>
+ [17] #xxxx.1.1.num.int <128 (0x80)>
+ [18] #xxxx.1.1.num.int <127 (0x7f)>
+ [19] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [20] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [21] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [22] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [23] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [24] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [25] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [26] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [27] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [28] #xxxx.1.1.num.int <4660 (0x1234)>
+ [29] #xxxx.1.1.num.int <18 (0x12)>
+ [30] #xxxx.1.1.num.int <0 (0x0)>
+ [31] #0.0.nil
+ [32] #xxxx.1.1.num.bool <0 (0x0)>
+ [33] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.1.mem.ro
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.mem.str.ro <#xxxx.1.2.mem.ro, "€">
+ [1] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [2] #xxxx.1.1.num.int <78934 (0x13456)>
+ [3] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [4] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [5] #xxxx.1.1.num.int <97 (0x61)>
+ [6] #xxxx.1.1.num.int <92 (0x5c)>
+ [7] #xxxx.1.1.num.int <39 (0x27)>
+ [8] #xxxx.1.1.num.int <9 (0x9)>
+ [9] #xxxx.1.1.num.int <10 (0xa)>
+ [10] #xxxx.1.1.num.int <0 (0x0)>
+ [11] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [12] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [13] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [14] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [15] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [16] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [17] #xxxx.1.1.num.int <256 (0x100)>
+ [18] #xxxx.1.1.num.int <128 (0x80)>
+ [19] #xxxx.1.1.num.int <127 (0x7f)>
+ [20] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [21] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [22] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [23] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [24] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [25] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [26] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [27] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [28] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [29] #xxxx.1.1.num.int <4660 (0x1234)>
+ [30] #xxxx.1.1.num.int <18 (0x12)>
+ [31] #xxxx.1.1.num.int <0 (0x0)>
+ [32] #0.0.nil
+ [33] #xxxx.1.1.num.bool <0 (0x0)>
+ [34] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.2.mem.ro
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.mem.str.ro <#xxxx.1.3.mem.ro, "€ XX X">
+ [1] #xxxx.1.1.mem.str.ro <#xxxx.1.3.mem.ro, "€">
+ [2] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [3] #xxxx.1.1.num.int <78934 (0x13456)>
+ [4] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [5] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [6] #xxxx.1.1.num.int <97 (0x61)>
+ [7] #xxxx.1.1.num.int <92 (0x5c)>
+ [8] #xxxx.1.1.num.int <39 (0x27)>
+ [9] #xxxx.1.1.num.int <9 (0x9)>
+ [10] #xxxx.1.1.num.int <10 (0xa)>
+ [11] #xxxx.1.1.num.int <0 (0x0)>
+ [12] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [13] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [14] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [15] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [16] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [17] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [18] #xxxx.1.1.num.int <256 (0x100)>
+ [19] #xxxx.1.1.num.int <128 (0x80)>
+ [20] #xxxx.1.1.num.int <127 (0x7f)>
+ [21] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [22] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [23] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [24] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [25] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [26] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [27] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [28] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [29] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [30] #xxxx.1.1.num.int <4660 (0x1234)>
+ [31] #xxxx.1.1.num.int <18 (0x12)>
+ [32] #xxxx.1.1.num.int <0 (0x0)>
+ [33] #0.0.nil
+ [34] #xxxx.1.1.num.bool <0 (0x0)>
+ [35] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.3.mem.ro
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.mem.str.ro <#xxxx.1.4.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [1] #xxxx.1.1.mem.str.ro <#xxxx.1.4.mem.ro, "€ XX X">
+ [2] #xxxx.1.1.mem.str.ro <#xxxx.1.4.mem.ro, "€">
+ [3] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [4] #xxxx.1.1.num.int <78934 (0x13456)>
+ [5] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [6] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [7] #xxxx.1.1.num.int <97 (0x61)>
+ [8] #xxxx.1.1.num.int <92 (0x5c)>
+ [9] #xxxx.1.1.num.int <39 (0x27)>
+ [10] #xxxx.1.1.num.int <9 (0x9)>
+ [11] #xxxx.1.1.num.int <10 (0xa)>
+ [12] #xxxx.1.1.num.int <0 (0x0)>
+ [13] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [14] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [15] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [16] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [17] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [18] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [19] #xxxx.1.1.num.int <256 (0x100)>
+ [20] #xxxx.1.1.num.int <128 (0x80)>
+ [21] #xxxx.1.1.num.int <127 (0x7f)>
+ [22] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [23] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [24] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [25] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [26] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [27] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [28] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [29] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [30] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [31] #xxxx.1.1.num.int <4660 (0x1234)>
+ [32] #xxxx.1.1.num.int <18 (0x12)>
+ [33] #xxxx.1.1.num.int <0 (0x0)>
+ [34] #0.0.nil
+ [35] #xxxx.1.1.num.bool <0 (0x0)>
+ [36] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.4.mem.ro
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.mem.code.ro <#xxxx.1.5.mem.ro>
+ [1] #xxxx.1.1.mem.str.ro <#xxxx.1.5.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [2] #xxxx.1.1.mem.str.ro <#xxxx.1.5.mem.ro, "€ XX X">
+ [3] #xxxx.1.1.mem.str.ro <#xxxx.1.5.mem.ro, "€">
+ [4] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [5] #xxxx.1.1.num.int <78934 (0x13456)>
+ [6] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [7] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [8] #xxxx.1.1.num.int <97 (0x61)>
+ [9] #xxxx.1.1.num.int <92 (0x5c)>
+ [10] #xxxx.1.1.num.int <39 (0x27)>
+ [11] #xxxx.1.1.num.int <9 (0x9)>
+ [12] #xxxx.1.1.num.int <10 (0xa)>
+ [13] #xxxx.1.1.num.int <0 (0x0)>
+ [14] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [15] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [16] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [17] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [18] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [19] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [20] #xxxx.1.1.num.int <256 (0x100)>
+ [21] #xxxx.1.1.num.int <128 (0x80)>
+ [22] #xxxx.1.1.num.int <127 (0x7f)>
+ [23] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [24] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [25] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [26] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [27] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [28] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [29] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [30] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [31] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [32] #xxxx.1.1.num.int <4660 (0x1234)>
+ [33] #xxxx.1.1.num.int <18 (0x12)>
+ [34] #xxxx.1.1.num.int <0 (0x0)>
+ [35] #0.0.nil
+ [36] #xxxx.1.1.num.bool <0 (0x0)>
+ [37] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.5.mem.ro
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [1] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [2] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [3] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [4] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [5] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [6] #xxxx.1.1.num.int <78934 (0x13456)>
+ [7] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [8] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [9] #xxxx.1.1.num.int <97 (0x61)>
+ [10] #xxxx.1.1.num.int <92 (0x5c)>
+ [11] #xxxx.1.1.num.int <39 (0x27)>
+ [12] #xxxx.1.1.num.int <9 (0x9)>
+ [13] #xxxx.1.1.num.int <10 (0xa)>
+ [14] #xxxx.1.1.num.int <0 (0x0)>
+ [15] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [16] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [17] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [18] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [19] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [20] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [21] #xxxx.1.1.num.int <256 (0x100)>
+ [22] #xxxx.1.1.num.int <128 (0x80)>
+ [23] #xxxx.1.1.num.int <127 (0x7f)>
+ [24] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [25] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [26] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [27] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [28] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [29] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [30] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [31] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [32] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [33] #xxxx.1.1.num.int <4660 (0x1234)>
+ [34] #xxxx.1.1.num.int <18 (0x12)>
+ [35] #xxxx.1.1.num.int <0 (0x0)>
+ [36] #0.0.nil
+ [37] #xxxx.1.1.num.bool <0 (0x0)>
+ [38] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.1.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.2.num.prim <2 (0x2)>
+ [1] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [2] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [3] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [4] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [5] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [6] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [7] #xxxx.1.1.num.int <78934 (0x13456)>
+ [8] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [9] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [10] #xxxx.1.1.num.int <97 (0x61)>
+ [11] #xxxx.1.1.num.int <92 (0x5c)>
+ [12] #xxxx.1.1.num.int <39 (0x27)>
+ [13] #xxxx.1.1.num.int <9 (0x9)>
+ [14] #xxxx.1.1.num.int <10 (0xa)>
+ [15] #xxxx.1.1.num.int <0 (0x0)>
+ [16] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [17] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [18] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [19] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [20] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [21] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [22] #xxxx.1.1.num.int <256 (0x100)>
+ [23] #xxxx.1.1.num.int <128 (0x80)>
+ [24] #xxxx.1.1.num.int <127 (0x7f)>
+ [25] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [26] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [27] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [28] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [29] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [30] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [31] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [32] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [33] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [34] #xxxx.1.1.num.int <4660 (0x1234)>
+ [35] #xxxx.1.1.num.int <18 (0x12)>
+ [36] #xxxx.1.1.num.int <0 (0x0)>
+ [37] #0.0.nil
+ [38] #xxxx.1.1.num.bool <0 (0x0)>
+ [39] #xxxx.1.1.num.bool <1 (0x1)>
+GC: --#xxxx.1.2.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.array
+ [1] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [2] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [3] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [4] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [5] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [6] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [7] #xxxx.1.1.num.int <78934 (0x13456)>
+ [8] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [9] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [10] #xxxx.1.1.num.int <97 (0x61)>
+ [11] #xxxx.1.1.num.int <92 (0x5c)>
+ [12] #xxxx.1.1.num.int <39 (0x27)>
+ [13] #xxxx.1.1.num.int <9 (0x9)>
+ [14] #xxxx.1.1.num.int <10 (0xa)>
+ [15] #xxxx.1.1.num.int <0 (0x0)>
+ [16] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [17] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [18] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [19] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [20] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [21] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [22] #xxxx.1.1.num.int <256 (0x100)>
+ [23] #xxxx.1.1.num.int <128 (0x80)>
+ [24] #xxxx.1.1.num.int <127 (0x7f)>
+ [25] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [26] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [27] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [28] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [29] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [30] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [31] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [32] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [33] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [34] #xxxx.1.1.num.int <4660 (0x1234)>
+ [35] #xxxx.1.1.num.int <18 (0x12)>
+ [36] #xxxx.1.1.num.int <0 (0x0)>
+ [37] #0.0.nil
+ [38] #xxxx.1.1.num.bool <0 (0x0)>
+ [39] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.1.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.2.num.prim <2 (0x2)>
+ [1] #xxxx.1.1.array
+ [2] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [3] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [4] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [5] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [6] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [7] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [8] #xxxx.1.1.num.int <78934 (0x13456)>
+ [9] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [10] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [11] #xxxx.1.1.num.int <97 (0x61)>
+ [12] #xxxx.1.1.num.int <92 (0x5c)>
+ [13] #xxxx.1.1.num.int <39 (0x27)>
+ [14] #xxxx.1.1.num.int <9 (0x9)>
+ [15] #xxxx.1.1.num.int <10 (0xa)>
+ [16] #xxxx.1.1.num.int <0 (0x0)>
+ [17] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [18] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [19] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [20] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [21] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [22] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [23] #xxxx.1.1.num.int <256 (0x100)>
+ [24] #xxxx.1.1.num.int <128 (0x80)>
+ [25] #xxxx.1.1.num.int <127 (0x7f)>
+ [26] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [27] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [28] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [29] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [30] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [31] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [32] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [33] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [34] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [35] #xxxx.1.1.num.int <4660 (0x1234)>
+ [36] #xxxx.1.1.num.int <18 (0x12)>
+ [37] #xxxx.1.1.num.int <0 (0x0)>
+ [38] #0.0.nil
+ [39] #xxxx.1.1.num.bool <0 (0x0)>
+ [40] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <10 (0xa)>
+ [1] #xxxx.1.2.num.prim <2 (0x2)>
+ [2] #xxxx.1.1.array
+ [3] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [4] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [5] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [6] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [8] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [9] #xxxx.1.1.num.int <78934 (0x13456)>
+ [10] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [11] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [12] #xxxx.1.1.num.int <97 (0x61)>
+ [13] #xxxx.1.1.num.int <92 (0x5c)>
+ [14] #xxxx.1.1.num.int <39 (0x27)>
+ [15] #xxxx.1.1.num.int <9 (0x9)>
+ [16] #xxxx.1.1.num.int <10 (0xa)>
+ [17] #xxxx.1.1.num.int <0 (0x0)>
+ [18] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [19] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [20] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [21] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [22] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [23] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [24] #xxxx.1.1.num.int <256 (0x100)>
+ [25] #xxxx.1.1.num.int <128 (0x80)>
+ [26] #xxxx.1.1.num.int <127 (0x7f)>
+ [27] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [28] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [29] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [30] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [31] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [32] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [33] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [34] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [35] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [36] #xxxx.1.1.num.int <4660 (0x1234)>
+ [37] #xxxx.1.1.num.int <18 (0x12)>
+ [38] #xxxx.1.1.num.int <0 (0x0)>
+ [39] #0.0.nil
+ [40] #xxxx.1.1.num.bool <0 (0x0)>
+ [41] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <20 (0x14)>
+ [1] #xxxx.1.1.num.int <10 (0xa)>
+ [2] #xxxx.1.2.num.prim <2 (0x2)>
+ [3] #xxxx.1.1.array
+ [4] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [5] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [6] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [9] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [10] #xxxx.1.1.num.int <78934 (0x13456)>
+ [11] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [12] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [13] #xxxx.1.1.num.int <97 (0x61)>
+ [14] #xxxx.1.1.num.int <92 (0x5c)>
+ [15] #xxxx.1.1.num.int <39 (0x27)>
+ [16] #xxxx.1.1.num.int <9 (0x9)>
+ [17] #xxxx.1.1.num.int <10 (0xa)>
+ [18] #xxxx.1.1.num.int <0 (0x0)>
+ [19] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [20] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [21] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [22] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [23] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [24] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [25] #xxxx.1.1.num.int <256 (0x100)>
+ [26] #xxxx.1.1.num.int <128 (0x80)>
+ [27] #xxxx.1.1.num.int <127 (0x7f)>
+ [28] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [29] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [30] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [31] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [32] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [33] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [34] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [35] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [36] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [37] #xxxx.1.1.num.int <4660 (0x1234)>
+ [38] #xxxx.1.1.num.int <18 (0x12)>
+ [39] #xxxx.1.1.num.int <0 (0x0)>
+ [40] #0.0.nil
+ [41] #xxxx.1.1.num.bool <0 (0x0)>
+ [42] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <30 (0x1e)>
+ [1] #xxxx.1.1.num.int <20 (0x14)>
+ [2] #xxxx.1.1.num.int <10 (0xa)>
+ [3] #xxxx.1.2.num.prim <2 (0x2)>
+ [4] #xxxx.1.1.array
+ [5] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [6] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [9] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [10] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [11] #xxxx.1.1.num.int <78934 (0x13456)>
+ [12] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [13] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [14] #xxxx.1.1.num.int <97 (0x61)>
+ [15] #xxxx.1.1.num.int <92 (0x5c)>
+ [16] #xxxx.1.1.num.int <39 (0x27)>
+ [17] #xxxx.1.1.num.int <9 (0x9)>
+ [18] #xxxx.1.1.num.int <10 (0xa)>
+ [19] #xxxx.1.1.num.int <0 (0x0)>
+ [20] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [21] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [22] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [23] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [24] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [25] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [26] #xxxx.1.1.num.int <256 (0x100)>
+ [27] #xxxx.1.1.num.int <128 (0x80)>
+ [28] #xxxx.1.1.num.int <127 (0x7f)>
+ [29] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [30] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [31] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [32] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [33] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [34] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [35] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [36] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [37] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [38] #xxxx.1.1.num.int <4660 (0x1234)>
+ [39] #xxxx.1.1.num.int <18 (0x12)>
+ [40] #xxxx.1.1.num.int <0 (0x0)>
+ [41] #0.0.nil
+ [42] #xxxx.1.1.num.bool <0 (0x0)>
+ [43] #xxxx.1.1.num.bool <1 (0x1)>
+GC: --#xxxx.1.2.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.array
+ [1] #xxxx.1.1.array
+ [2] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [3] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [4] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [5] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [6] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [7] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [8] #xxxx.1.1.num.int <78934 (0x13456)>
+ [9] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [10] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [11] #xxxx.1.1.num.int <97 (0x61)>
+ [12] #xxxx.1.1.num.int <92 (0x5c)>
+ [13] #xxxx.1.1.num.int <39 (0x27)>
+ [14] #xxxx.1.1.num.int <9 (0x9)>
+ [15] #xxxx.1.1.num.int <10 (0xa)>
+ [16] #xxxx.1.1.num.int <0 (0x0)>
+ [17] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [18] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [19] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [20] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [21] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [22] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [23] #xxxx.1.1.num.int <256 (0x100)>
+ [24] #xxxx.1.1.num.int <128 (0x80)>
+ [25] #xxxx.1.1.num.int <127 (0x7f)>
+ [26] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [27] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [28] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [29] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [30] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [31] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [32] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [33] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [34] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [35] #xxxx.1.1.num.int <4660 (0x1234)>
+ [36] #xxxx.1.1.num.int <18 (0x12)>
+ [37] #xxxx.1.1.num.int <0 (0x0)>
+ [38] #0.0.nil
+ [39] #xxxx.1.1.num.bool <0 (0x0)>
+ [40] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.1.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.2.num.prim <2 (0x2)>
+ [1] #xxxx.1.1.array
+ [2] #xxxx.1.1.array
+ [3] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [4] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [5] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [6] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [8] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [9] #xxxx.1.1.num.int <78934 (0x13456)>
+ [10] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [11] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [12] #xxxx.1.1.num.int <97 (0x61)>
+ [13] #xxxx.1.1.num.int <92 (0x5c)>
+ [14] #xxxx.1.1.num.int <39 (0x27)>
+ [15] #xxxx.1.1.num.int <9 (0x9)>
+ [16] #xxxx.1.1.num.int <10 (0xa)>
+ [17] #xxxx.1.1.num.int <0 (0x0)>
+ [18] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [19] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [20] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [21] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [22] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [23] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [24] #xxxx.1.1.num.int <256 (0x100)>
+ [25] #xxxx.1.1.num.int <128 (0x80)>
+ [26] #xxxx.1.1.num.int <127 (0x7f)>
+ [27] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [28] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [29] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [30] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [31] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [32] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [33] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [34] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [35] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [36] #xxxx.1.1.num.int <4660 (0x1234)>
+ [37] #xxxx.1.1.num.int <18 (0x12)>
+ [38] #xxxx.1.1.num.int <0 (0x0)>
+ [39] #0.0.nil
+ [40] #xxxx.1.1.num.bool <0 (0x0)>
+ [41] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <1 (0x1)>
+ [1] #xxxx.1.2.num.prim <2 (0x2)>
+ [2] #xxxx.1.1.array
+ [3] #xxxx.1.1.array
+ [4] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [5] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [6] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [9] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [10] #xxxx.1.1.num.int <78934 (0x13456)>
+ [11] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [12] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [13] #xxxx.1.1.num.int <97 (0x61)>
+ [14] #xxxx.1.1.num.int <92 (0x5c)>
+ [15] #xxxx.1.1.num.int <39 (0x27)>
+ [16] #xxxx.1.1.num.int <9 (0x9)>
+ [17] #xxxx.1.1.num.int <10 (0xa)>
+ [18] #xxxx.1.1.num.int <0 (0x0)>
+ [19] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [20] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [21] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [22] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [23] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [24] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [25] #xxxx.1.1.num.int <256 (0x100)>
+ [26] #xxxx.1.1.num.int <128 (0x80)>
+ [27] #xxxx.1.1.num.int <127 (0x7f)>
+ [28] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [29] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [30] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [31] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [32] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [33] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [34] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [35] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [36] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [37] #xxxx.1.1.num.int <4660 (0x1234)>
+ [38] #xxxx.1.1.num.int <18 (0x12)>
+ [39] #xxxx.1.1.num.int <0 (0x0)>
+ [40] #0.0.nil
+ [41] #xxxx.1.1.num.bool <0 (0x0)>
+ [42] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <2 (0x2)>
+ [1] #xxxx.1.1.num.int <1 (0x1)>
+ [2] #xxxx.1.2.num.prim <2 (0x2)>
+ [3] #xxxx.1.1.array
+ [4] #xxxx.1.1.array
+ [5] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [6] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [9] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [10] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [11] #xxxx.1.1.num.int <78934 (0x13456)>
+ [12] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [13] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [14] #xxxx.1.1.num.int <97 (0x61)>
+ [15] #xxxx.1.1.num.int <92 (0x5c)>
+ [16] #xxxx.1.1.num.int <39 (0x27)>
+ [17] #xxxx.1.1.num.int <9 (0x9)>
+ [18] #xxxx.1.1.num.int <10 (0xa)>
+ [19] #xxxx.1.1.num.int <0 (0x0)>
+ [20] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [21] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [22] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [23] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [24] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [25] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [26] #xxxx.1.1.num.int <256 (0x100)>
+ [27] #xxxx.1.1.num.int <128 (0x80)>
+ [28] #xxxx.1.1.num.int <127 (0x7f)>
+ [29] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [30] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [31] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [32] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [33] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [34] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [35] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [36] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [37] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [38] #xxxx.1.1.num.int <4660 (0x1234)>
+ [39] #xxxx.1.1.num.int <18 (0x12)>
+ [40] #xxxx.1.1.num.int <0 (0x0)>
+ [41] #0.0.nil
+ [42] #xxxx.1.1.num.bool <0 (0x0)>
+ [43] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.2.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.3.num.prim <2 (0x2)>
+ [1] #xxxx.1.1.num.int <2 (0x2)>
+ [2] #xxxx.1.1.num.int <1 (0x1)>
+ [3] #xxxx.1.3.num.prim <2 (0x2)>
+ [4] #xxxx.1.1.array
+ [5] #xxxx.1.1.array
+ [6] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [7] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [9] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [10] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [11] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [12] #xxxx.1.1.num.int <78934 (0x13456)>
+ [13] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [14] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [15] #xxxx.1.1.num.int <97 (0x61)>
+ [16] #xxxx.1.1.num.int <92 (0x5c)>
+ [17] #xxxx.1.1.num.int <39 (0x27)>
+ [18] #xxxx.1.1.num.int <9 (0x9)>
+ [19] #xxxx.1.1.num.int <10 (0xa)>
+ [20] #xxxx.1.1.num.int <0 (0x0)>
+ [21] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [22] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [23] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [24] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [25] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [26] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [27] #xxxx.1.1.num.int <256 (0x100)>
+ [28] #xxxx.1.1.num.int <128 (0x80)>
+ [29] #xxxx.1.1.num.int <127 (0x7f)>
+ [30] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [31] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [32] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [33] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [34] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [35] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [36] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [37] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [38] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [39] #xxxx.1.1.num.int <4660 (0x1234)>
+ [40] #xxxx.1.1.num.int <18 (0x12)>
+ [41] #xxxx.1.1.num.int <0 (0x0)>
+ [42] #0.0.nil
+ [43] #xxxx.1.1.num.bool <0 (0x0)>
+ [44] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <3 (0x3)>
+ [1] #xxxx.1.3.num.prim <2 (0x2)>
+ [2] #xxxx.1.1.num.int <2 (0x2)>
+ [3] #xxxx.1.1.num.int <1 (0x1)>
+ [4] #xxxx.1.3.num.prim <2 (0x2)>
+ [5] #xxxx.1.1.array
+ [6] #xxxx.1.1.array
+ [7] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [8] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [9] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [10] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [11] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [12] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [13] #xxxx.1.1.num.int <78934 (0x13456)>
+ [14] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [15] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [16] #xxxx.1.1.num.int <97 (0x61)>
+ [17] #xxxx.1.1.num.int <92 (0x5c)>
+ [18] #xxxx.1.1.num.int <39 (0x27)>
+ [19] #xxxx.1.1.num.int <9 (0x9)>
+ [20] #xxxx.1.1.num.int <10 (0xa)>
+ [21] #xxxx.1.1.num.int <0 (0x0)>
+ [22] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [23] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [24] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [25] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [26] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [27] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [28] #xxxx.1.1.num.int <256 (0x100)>
+ [29] #xxxx.1.1.num.int <128 (0x80)>
+ [30] #xxxx.1.1.num.int <127 (0x7f)>
+ [31] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [32] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [33] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [34] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [35] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [36] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [37] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [38] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [39] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [40] #xxxx.1.1.num.int <4660 (0x1234)>
+ [41] #xxxx.1.1.num.int <18 (0x12)>
+ [42] #xxxx.1.1.num.int <0 (0x0)>
+ [43] #0.0.nil
+ [44] #xxxx.1.1.num.bool <0 (0x0)>
+ [45] #xxxx.1.1.num.bool <1 (0x1)>
+GC: --#xxxx.1.3.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.array
+ [1] #xxxx.1.1.num.int <2 (0x2)>
+ [2] #xxxx.1.1.num.int <1 (0x1)>
+ [3] #xxxx.1.2.num.prim <2 (0x2)>
+ [4] #xxxx.1.1.array
+ [5] #xxxx.1.1.array
+ [6] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [7] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [9] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [10] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [11] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [12] #xxxx.1.1.num.int <78934 (0x13456)>
+ [13] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [14] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [15] #xxxx.1.1.num.int <97 (0x61)>
+ [16] #xxxx.1.1.num.int <92 (0x5c)>
+ [17] #xxxx.1.1.num.int <39 (0x27)>
+ [18] #xxxx.1.1.num.int <9 (0x9)>
+ [19] #xxxx.1.1.num.int <10 (0xa)>
+ [20] #xxxx.1.1.num.int <0 (0x0)>
+ [21] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [22] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [23] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [24] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [25] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [26] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [27] #xxxx.1.1.num.int <256 (0x100)>
+ [28] #xxxx.1.1.num.int <128 (0x80)>
+ [29] #xxxx.1.1.num.int <127 (0x7f)>
+ [30] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [31] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [32] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [33] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [34] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [35] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [36] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [37] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [38] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [39] #xxxx.1.1.num.int <4660 (0x1234)>
+ [40] #xxxx.1.1.num.int <18 (0x12)>
+ [41] #xxxx.1.1.num.int <0 (0x0)>
+ [42] #0.0.nil
+ [43] #xxxx.1.1.num.bool <0 (0x0)>
+ [44] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <4 (0x4)>
+ [1] #xxxx.1.1.array
+ [2] #xxxx.1.1.num.int <2 (0x2)>
+ [3] #xxxx.1.1.num.int <1 (0x1)>
+ [4] #xxxx.1.2.num.prim <2 (0x2)>
+ [5] #xxxx.1.1.array
+ [6] #xxxx.1.1.array
+ [7] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [8] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [9] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [10] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [11] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [12] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [13] #xxxx.1.1.num.int <78934 (0x13456)>
+ [14] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [15] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [16] #xxxx.1.1.num.int <97 (0x61)>
+ [17] #xxxx.1.1.num.int <92 (0x5c)>
+ [18] #xxxx.1.1.num.int <39 (0x27)>
+ [19] #xxxx.1.1.num.int <9 (0x9)>
+ [20] #xxxx.1.1.num.int <10 (0xa)>
+ [21] #xxxx.1.1.num.int <0 (0x0)>
+ [22] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [23] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [24] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [25] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [26] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [27] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [28] #xxxx.1.1.num.int <256 (0x100)>
+ [29] #xxxx.1.1.num.int <128 (0x80)>
+ [30] #xxxx.1.1.num.int <127 (0x7f)>
+ [31] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [32] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [33] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [34] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [35] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [36] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [37] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [38] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [39] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [40] #xxxx.1.1.num.int <4660 (0x1234)>
+ [41] #xxxx.1.1.num.int <18 (0x12)>
+ [42] #xxxx.1.1.num.int <0 (0x0)>
+ [43] #0.0.nil
+ [44] #xxxx.1.1.num.bool <0 (0x0)>
+ [45] #xxxx.1.1.num.bool <1 (0x1)>
+GC: --#xxxx.1.2.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.array
+ [1] #xxxx.1.1.array
+ [2] #xxxx.1.1.array
+ [3] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [4] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [5] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [6] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [8] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [9] #xxxx.1.1.num.int <78934 (0x13456)>
+ [10] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [11] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [12] #xxxx.1.1.num.int <97 (0x61)>
+ [13] #xxxx.1.1.num.int <92 (0x5c)>
+ [14] #xxxx.1.1.num.int <39 (0x27)>
+ [15] #xxxx.1.1.num.int <9 (0x9)>
+ [16] #xxxx.1.1.num.int <10 (0xa)>
+ [17] #xxxx.1.1.num.int <0 (0x0)>
+ [18] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [19] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [20] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [21] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [22] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [23] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [24] #xxxx.1.1.num.int <256 (0x100)>
+ [25] #xxxx.1.1.num.int <128 (0x80)>
+ [26] #xxxx.1.1.num.int <127 (0x7f)>
+ [27] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [28] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [29] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [30] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [31] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [32] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [33] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [34] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [35] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [36] #xxxx.1.1.num.int <4660 (0x1234)>
+ [37] #xxxx.1.1.num.int <18 (0x12)>
+ [38] #xxxx.1.1.num.int <0 (0x0)>
+ [39] #0.0.nil
+ [40] #xxxx.1.1.num.bool <0 (0x0)>
+ [41] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.1.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.2.num.prim <4 (0x4)>
+ [1] #xxxx.1.1.array
+ [2] #xxxx.1.1.array
+ [3] #xxxx.1.1.array
+ [4] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [5] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [6] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [9] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [10] #xxxx.1.1.num.int <78934 (0x13456)>
+ [11] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [12] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [13] #xxxx.1.1.num.int <97 (0x61)>
+ [14] #xxxx.1.1.num.int <92 (0x5c)>
+ [15] #xxxx.1.1.num.int <39 (0x27)>
+ [16] #xxxx.1.1.num.int <9 (0x9)>
+ [17] #xxxx.1.1.num.int <10 (0xa)>
+ [18] #xxxx.1.1.num.int <0 (0x0)>
+ [19] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [20] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [21] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [22] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [23] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [24] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [25] #xxxx.1.1.num.int <256 (0x100)>
+ [26] #xxxx.1.1.num.int <128 (0x80)>
+ [27] #xxxx.1.1.num.int <127 (0x7f)>
+ [28] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [29] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [30] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [31] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [32] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [33] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [34] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [35] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [36] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [37] #xxxx.1.1.num.int <4660 (0x1234)>
+ [38] #xxxx.1.1.num.int <18 (0x12)>
+ [39] #xxxx.1.1.num.int <0 (0x0)>
+ [40] #0.0.nil
+ [41] #xxxx.1.1.num.bool <0 (0x0)>
+ [42] #xxxx.1.1.num.bool <1 (0x1)>
+GC: --#xxxx.1.2.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.hash
+ [1] #xxxx.1.1.array
+ [2] #xxxx.1.1.array
+ [3] #xxxx.1.1.array
+ [4] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [5] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [6] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [9] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [10] #xxxx.1.1.num.int <78934 (0x13456)>
+ [11] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [12] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [13] #xxxx.1.1.num.int <97 (0x61)>
+ [14] #xxxx.1.1.num.int <92 (0x5c)>
+ [15] #xxxx.1.1.num.int <39 (0x27)>
+ [16] #xxxx.1.1.num.int <9 (0x9)>
+ [17] #xxxx.1.1.num.int <10 (0xa)>
+ [18] #xxxx.1.1.num.int <0 (0x0)>
+ [19] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [20] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [21] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [22] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [23] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [24] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [25] #xxxx.1.1.num.int <256 (0x100)>
+ [26] #xxxx.1.1.num.int <128 (0x80)>
+ [27] #xxxx.1.1.num.int <127 (0x7f)>
+ [28] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [29] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [30] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [31] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [32] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [33] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [34] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [35] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [36] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [37] #xxxx.1.1.num.int <4660 (0x1234)>
+ [38] #xxxx.1.1.num.int <18 (0x12)>
+ [39] #xxxx.1.1.num.int <0 (0x0)>
+ [40] #0.0.nil
+ [41] #xxxx.1.1.num.bool <0 (0x0)>
+ [42] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.1.num.prim
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.2.num.prim <4 (0x4)>
+ [1] #xxxx.1.1.hash
+ [2] #xxxx.1.1.array
+ [3] #xxxx.1.1.array
+ [4] #xxxx.1.1.array
+ [5] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [6] #xxxx.1.1.mem.code.ro <#xxxx.1.6.mem.ro>
+ [7] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€ XX X">
+ [9] #xxxx.1.1.mem.str.ro <#xxxx.1.6.mem.ro, "€">
+ [10] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [11] #xxxx.1.1.num.int <78934 (0x13456)>
+ [12] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [13] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [14] #xxxx.1.1.num.int <97 (0x61)>
+ [15] #xxxx.1.1.num.int <92 (0x5c)>
+ [16] #xxxx.1.1.num.int <39 (0x27)>
+ [17] #xxxx.1.1.num.int <9 (0x9)>
+ [18] #xxxx.1.1.num.int <10 (0xa)>
+ [19] #xxxx.1.1.num.int <0 (0x0)>
+ [20] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [21] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [22] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [23] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [24] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [25] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [26] #xxxx.1.1.num.int <256 (0x100)>
+ [27] #xxxx.1.1.num.int <128 (0x80)>
+ [28] #xxxx.1.1.num.int <127 (0x7f)>
+ [29] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [30] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [31] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [32] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [33] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [34] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [35] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [36] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [37] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [38] #xxxx.1.1.num.int <4660 (0x1234)>
+ [39] #xxxx.1.1.num.int <18 (0x12)>
+ [40] #xxxx.1.1.num.int <0 (0x0)>
+ [41] #0.0.nil
+ [42] #xxxx.1.1.num.bool <0 (0x0)>
+ [43] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.6.mem.ro
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.mem.str.ro <#xxxx.1.7.mem.ro, "a10">
+ [1] #xxxx.1.2.num.prim <4 (0x4)>
+ [2] #xxxx.1.1.hash
+ [3] #xxxx.1.1.array
+ [4] #xxxx.1.1.array
+ [5] #xxxx.1.1.array
+ [6] #xxxx.1.1.mem.code.ro <#xxxx.1.7.mem.ro>
+ [7] #xxxx.1.1.mem.code.ro <#xxxx.1.7.mem.ro>
+ [8] #xxxx.1.1.mem.str.ro <#xxxx.1.7.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [9] #xxxx.1.1.mem.str.ro <#xxxx.1.7.mem.ro, "€ XX X">
+ [10] #xxxx.1.1.mem.str.ro <#xxxx.1.7.mem.ro, "€">
+ [11] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [12] #xxxx.1.1.num.int <78934 (0x13456)>
+ [13] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [14] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [15] #xxxx.1.1.num.int <97 (0x61)>
+ [16] #xxxx.1.1.num.int <92 (0x5c)>
+ [17] #xxxx.1.1.num.int <39 (0x27)>
+ [18] #xxxx.1.1.num.int <9 (0x9)>
+ [19] #xxxx.1.1.num.int <10 (0xa)>
+ [20] #xxxx.1.1.num.int <0 (0x0)>
+ [21] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [22] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [23] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [24] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [25] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [26] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [27] #xxxx.1.1.num.int <256 (0x100)>
+ [28] #xxxx.1.1.num.int <128 (0x80)>
+ [29] #xxxx.1.1.num.int <127 (0x7f)>
+ [30] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [31] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [32] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [33] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [34] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [35] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [36] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [37] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [38] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [39] #xxxx.1.1.num.int <4660 (0x1234)>
+ [40] #xxxx.1.1.num.int <18 (0x12)>
+ [41] #xxxx.1.1.num.int <0 (0x0)>
+ [42] #0.0.nil
+ [43] #xxxx.1.1.num.bool <0 (0x0)>
+ [44] #xxxx.1.1.num.bool <1 (0x1)>
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func
+== stack (#xxxx.1.1.array) ==
+ [0] #xxxx.1.1.num.int <100 (0x64)>
+ [1] #xxxx.1.1.mem.str.ro <#xxxx.1.7.mem.ro, "a10">
+ [2] #xxxx.1.2.num.prim <4 (0x4)>
+ [3] #xxxx.1.1.hash
+ [4] #xxxx.1.1.array
+ [5] #xxxx.1.1.array
+ [6] #xxxx.1.1.array
+ [7] #xxxx.1.1.mem.code.ro <#xxxx.1.7.mem.ro>
+ [8] #xxxx.1.1.mem.code.ro <#xxxx.1.7.mem.ro>
+ [9] #xxxx.1.1.mem.str.ro <#xxxx.1.7.mem.ro, "1234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567834 56781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678345678123456781 2345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678">
+ [10] #xxxx.1.1.mem.str.ro <#xxxx.1.7.mem.ro, "€ XX X">
+ [11] #xxxx.1.1.mem.str.ro <#xxxx.1.7.mem.ro, "€">
+ [12] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [13] #xxxx.1.1.num.int <78934 (0x13456)>
+ [14] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [15] #xxxx.1.1.num.int <8364 (0x20ac)>
+ [16] #xxxx.1.1.num.int <97 (0x61)>
+ [17] #xxxx.1.1.num.int <92 (0x5c)>
+ [18] #xxxx.1.1.num.int <39 (0x27)>
+ [19] #xxxx.1.1.num.int <9 (0x9)>
+ [20] #xxxx.1.1.num.int <10 (0xa)>
+ [21] #xxxx.1.1.num.int <0 (0x0)>
+ [22] #xxxx.1.1.num.int <-254 (0xffffffffffffff02)>
+ [23] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [24] #xxxx.1.1.num.int <-129 (0xffffffffffffff7f)>
+ [25] #xxxx.1.1.num.int <-128 (0xffffffffffffff80)>
+ [26] #xxxx.1.1.num.int <-2 (0xfffffffffffffffe)>
+ [27] #xxxx.1.1.num.int <-1 (0xffffffffffffffff)>
+ [28] #xxxx.1.1.num.int <256 (0x100)>
+ [29] #xxxx.1.1.num.int <128 (0x80)>
+ [30] #xxxx.1.1.num.int <127 (0x7f)>
+ [31] #xxxx.1.1.num.int <-4294967294 (0xffffffff00000002)>
+ [32] #xxxx.1.1.num.int <4294967294 (0xfffffffe)>
+ [33] #xxxx.1.1.num.int <7911603569390985488 (0x6dcba98765432110)>
+ [34] #xxxx.1.1.num.int <-7911603569390985488 (0x923456789abcdef0)>
+ [35] #xxxx.1.1.num.int <-1311768467463790320 (0xedcba98765432110)>
+ [36] #xxxx.1.1.num.int <1311768467463790320 (0x123456789abcdef0)>
+ [37] #xxxx.1.1.num.int <78187493530 (0x123456789a)>
+ [38] #xxxx.1.1.num.int <305419896 (0x12345678)>
+ [39] #xxxx.1.1.num.int <1193046 (0x123456)>
+ [40] #xxxx.1.1.num.int <4660 (0x1234)>
+ [41] #xxxx.1.1.num.int <18 (0x12)>
+ [42] #xxxx.1.1.num.int <0 (0x0)>
+ [43] #0.0.nil
+ [44] #xxxx.1.1.num.bool <0 (0x0)>
+ [45] #xxxx.1.1.num.bool <1 (0x1)>
+GC: ++#xxxx.1.7.mem.ro
+== backtrace ==
+ [0] #xxxx.1.1.ctx.func