mirror of https://github.com/Wilfred/difftastic/
commit
39bd04002c
@ -0,0 +1,5 @@
|
||||
use fmt;
|
||||
|
||||
export fn main() void = {
|
||||
fmt::println("Hello, world!");
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
../tree-sitter-hare/queries/highlights.scm
|
||||
@ -0,0 +1 @@
|
||||
tree-sitter-hare/src/
|
||||
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "tree-sitter-hare"
|
||||
description = "hare grammar for the tree-sitter parsing library"
|
||||
version = "0.0.1"
|
||||
keywords = ["incremental", "parsing", "hare"]
|
||||
categories = ["parsing", "text-editors"]
|
||||
repository = "https://github.com/tree-sitter/tree-sitter-javascript"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
||||
build = "bindings/rust/build.rs"
|
||||
include = [
|
||||
"bindings/rust/*",
|
||||
"grammar.js",
|
||||
"queries/*",
|
||||
"src/*",
|
||||
]
|
||||
|
||||
[lib]
|
||||
path = "bindings/rust/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
tree-sitter = "0.17"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
@ -0,0 +1,14 @@
|
||||
# tree-sitter-hare
|
||||
[Hare](https://harelang.org/) grammar for the popular incremental parser generator [tree-sitter](https://tree-sitter.github.io/tree-sitter/).
|
||||
|
||||
**status**: the majority of sources in `examples` (Hare stdlib) is recognized fine - sources containing
|
||||
`\\` in strings fail, due to them getting parsed as comments
|
||||
as a consequence to tree-sitter precedence rules.
|
||||
|
||||
For a list of features offered by tree-sitter refer to their site. Summarily,
|
||||
one of the main features is context-aware highlight (e.g. local variables are highlighted
|
||||
differently than function parameters).
|
||||
|
||||
This grammar can be used in a number of editors and different situations (github
|
||||
uses it, for example) - at this stage it must be inserted manually into your editor.
|
||||
See, for example, [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter#advanced-setup).
|
||||
@ -0,0 +1,19 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "tree_sitter_hare_binding",
|
||||
"include_dirs": [
|
||||
"<!(node -e \"require('nan')\")",
|
||||
"src"
|
||||
],
|
||||
"sources": [
|
||||
"bindings/node/binding.cc",
|
||||
"src/parser.c",
|
||||
# If your language uses an external scanner, add it here.
|
||||
],
|
||||
"cflags_c": [
|
||||
"-std=c99",
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
#include "tree_sitter/parser.h"
|
||||
#include <node.h>
|
||||
#include "nan.h"
|
||||
|
||||
using namespace v8;
|
||||
|
||||
extern "C" TSLanguage * tree_sitter_hare();
|
||||
|
||||
namespace {
|
||||
|
||||
NAN_METHOD(New) {}
|
||||
|
||||
void Init(Local<Object> exports, Local<Object> module) {
|
||||
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
|
||||
tpl->SetClassName(Nan::New("Language").ToLocalChecked());
|
||||
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
|
||||
Local<Function> constructor = Nan::GetFunction(tpl).ToLocalChecked();
|
||||
Local<Object> instance = constructor->NewInstance(Nan::GetCurrentContext()).ToLocalChecked();
|
||||
Nan::SetInternalFieldPointer(instance, 0, tree_sitter_hare());
|
||||
|
||||
Nan::Set(instance, Nan::New("name").ToLocalChecked(), Nan::New("hare").ToLocalChecked());
|
||||
Nan::Set(module, Nan::New("exports").ToLocalChecked(), instance);
|
||||
}
|
||||
|
||||
NODE_MODULE(tree_sitter_hare_binding, Init)
|
||||
|
||||
} // namespace
|
||||
@ -0,0 +1,19 @@
|
||||
try {
|
||||
module.exports = require("../../build/Release/tree_sitter_hare_binding");
|
||||
} catch (error1) {
|
||||
if (error1.code !== 'MODULE_NOT_FOUND') {
|
||||
throw error1;
|
||||
}
|
||||
try {
|
||||
module.exports = require("../../build/Debug/tree_sitter_hare_binding");
|
||||
} catch (error2) {
|
||||
if (error2.code !== 'MODULE_NOT_FOUND') {
|
||||
throw error2;
|
||||
}
|
||||
throw error1
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
module.exports.nodeTypeInfo = require("../../src/node-types.json");
|
||||
} catch (_) {}
|
||||
@ -0,0 +1,40 @@
|
||||
fn main() {
|
||||
let src_dir = std::path::Path::new("src");
|
||||
|
||||
let mut c_config = cc::Build::new();
|
||||
c_config.include(&src_dir);
|
||||
c_config
|
||||
.flag_if_supported("-Wno-unused-parameter")
|
||||
.flag_if_supported("-Wno-unused-but-set-variable")
|
||||
.flag_if_supported("-Wno-trigraphs");
|
||||
let parser_path = src_dir.join("parser.c");
|
||||
c_config.file(&parser_path);
|
||||
|
||||
// If your language uses an external scanner written in C,
|
||||
// then include this block of code:
|
||||
|
||||
/*
|
||||
let scanner_path = src_dir.join("scanner.c");
|
||||
c_config.file(&scanner_path);
|
||||
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
|
||||
*/
|
||||
|
||||
c_config.compile("parser");
|
||||
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
|
||||
|
||||
// If your language uses an external scanner written in C++,
|
||||
// then include this block of code:
|
||||
|
||||
/*
|
||||
let mut cpp_config = cc::Build::new();
|
||||
cpp_config.cpp(true);
|
||||
cpp_config.include(&src_dir);
|
||||
cpp_config
|
||||
.flag_if_supported("-Wno-unused-parameter")
|
||||
.flag_if_supported("-Wno-unused-but-set-variable");
|
||||
let scanner_path = src_dir.join("scanner.cc");
|
||||
cpp_config.file(&scanner_path);
|
||||
cpp_config.compile("scanner");
|
||||
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
|
||||
*/
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
//! This crate provides hare language support for the [tree-sitter][] parsing library.
|
||||
//!
|
||||
//! Typically, you will use the [language][language func] function to add this language to a
|
||||
//! tree-sitter [Parser][], and then use the parser to parse some code:
|
||||
//!
|
||||
//! ```
|
||||
//! let code = "";
|
||||
//! let mut parser = tree_sitter::Parser::new();
|
||||
//! parser.set_language(tree_sitter_hare::language()).expect("Error loading hare grammar");
|
||||
//! let tree = parser.parse(code, None).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
|
||||
//! [language func]: fn.language.html
|
||||
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
|
||||
//! [tree-sitter]: https://tree-sitter.github.io/
|
||||
|
||||
use tree_sitter::Language;
|
||||
|
||||
extern "C" {
|
||||
fn tree_sitter_hare() -> Language;
|
||||
}
|
||||
|
||||
/// Get the tree-sitter [Language][] for this grammar.
|
||||
///
|
||||
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
|
||||
pub fn language() -> Language {
|
||||
unsafe { tree_sitter_hare() }
|
||||
}
|
||||
|
||||
/// The content of the [`node-types.json`][] file for this grammar.
|
||||
///
|
||||
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
|
||||
pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json");
|
||||
|
||||
// Uncomment these to include any queries that this grammar contains
|
||||
|
||||
// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm");
|
||||
// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm");
|
||||
// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
|
||||
// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_can_load_grammar() {
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
parser
|
||||
.set_language(super::language())
|
||||
.expect("Error loading hare language");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
// Returns the new PID to the parent, void to the child, or errno if something
|
||||
// goes wrong.
|
||||
export fn clone(
|
||||
stack: nullable *void,
|
||||
flags: int,
|
||||
parent_tid: nullable *int,
|
||||
child_tid: nullable *int,
|
||||
tls: u64,
|
||||
) (int | void | errno) = {
|
||||
return match (wrap_return(syscall5(SYS_clone,
|
||||
flags: u64,
|
||||
stack: uintptr: u64,
|
||||
parent_tid: uintptr: u64,
|
||||
tls,
|
||||
child_tid: uintptr: u64))) {
|
||||
u: u64 => switch (u) {
|
||||
0 => void,
|
||||
* => u: int,
|
||||
},
|
||||
err: errno => err,
|
||||
};
|
||||
};
|
||||
|
||||
export def O_DIRECTORY: int = 0o40000;
|
||||
@ -0,0 +1,13 @@
|
||||
let start: timespec = timespec { ... };
|
||||
|
||||
fn time_start() void = {
|
||||
clock_gettime(CLOCK_MONOTONIC, &start) as void;
|
||||
};
|
||||
|
||||
// Returns elapsed time as (seconds, milliseconds)
|
||||
fn time_stop() (size, size) = {
|
||||
let end: timespec = timespec { ... };
|
||||
clock_gettime(CLOCK_MONOTONIC, &end) as void;
|
||||
return ((end.tv_sec - start.tv_sec): size,
|
||||
(end.tv_nsec - start.tv_nsec): size / 10000z);
|
||||
};
|
||||
@ -0,0 +1,262 @@
|
||||
use bufio;
|
||||
use fmt;
|
||||
use io;
|
||||
use io::{mode};
|
||||
use strings;
|
||||
|
||||
@test fn unget() void = {
|
||||
let buf = bufio::fixed(strings::toutf8("z"), mode::READ);
|
||||
let lexer = init(buf, "<test>");
|
||||
unget(&lexer, 'x');
|
||||
unget(&lexer, 'y');
|
||||
assert(next(&lexer) as rune == 'y');
|
||||
assert(next(&lexer) as rune == 'x');
|
||||
assert(next(&lexer) as rune == 'z');
|
||||
assert(next(&lexer) is io::EOF);
|
||||
unget(&lexer, io::EOF);
|
||||
assert(next(&lexer) is io::EOF);
|
||||
};
|
||||
|
||||
@test fn unlex() void = {
|
||||
let lexer = init(io::empty, "<test>");
|
||||
unlex(&lexer, (btoken::IF, location {
|
||||
path = "<test>",
|
||||
line = 1234,
|
||||
col = 1234,
|
||||
}));
|
||||
let t = lex(&lexer) as (token, location);
|
||||
assert(t.0 is btoken);
|
||||
assert(t.0 as btoken == btoken::IF);
|
||||
assert(t.1.path == "<test>");
|
||||
assert(t.1.line == 1234 && t.1.col == 1234);
|
||||
};
|
||||
|
||||
fn litassert(expected: literal, actual: literal) void = match (expected) {
|
||||
e: u8 => assert(actual as u8 == e),
|
||||
e: u16 => assert(actual as u16 == e),
|
||||
e: u32 => assert(actual as u32 == e),
|
||||
e: u64 => assert(actual as u64 == e),
|
||||
e: uint => assert(actual as uint == e),
|
||||
e: uintptr => assert(actual as uintptr == e),
|
||||
e: i8 => assert(actual as i8 == e),
|
||||
e: i16 => assert(actual as i16 == e),
|
||||
e: i32 => assert(actual as i32 == e),
|
||||
e: i64 => assert(actual as i64 == e),
|
||||
e: int => assert(actual as int == e),
|
||||
e: iconst => assert(actual as iconst == e),
|
||||
e: f32 => assert(actual as f32 == e),
|
||||
e: f64 => assert(actual as f64 == e),
|
||||
e: fconst => assert(actual as fconst == e),
|
||||
e: rune => assert(actual as rune == e),
|
||||
e: str => assert(actual as str == e),
|
||||
};
|
||||
|
||||
fn lextest(in: str, expected: [](uint, uint, token)) void = {
|
||||
let buf = bufio::fixed(strings::toutf8(in), mode::READ);
|
||||
let lexer = init(buf, "<test>");
|
||||
for (let i = 0z; i < len(expected); i += 1) {
|
||||
let eline = expected[i].0, ecol = expected[i].1,
|
||||
etok = expected[i].2;
|
||||
let tl = match (lex(&lexer)) {
|
||||
tl: (token, location) => tl,
|
||||
io::EOF => {
|
||||
fmt::errorfln("unexpected EOF at {}", i);
|
||||
abort();
|
||||
},
|
||||
err: error => {
|
||||
fmt::errorfln("{}: {}", i, strerror(err));
|
||||
abort();
|
||||
},
|
||||
};
|
||||
let tok = tl.0, loc = tl.1;
|
||||
match (tok) {
|
||||
b: btoken => if (!(etok is btoken) || etok as btoken != b) {
|
||||
fmt::errorfln("bad token at {}: got {}, wanted {}",
|
||||
i, tokstr(tok), tokstr(etok));
|
||||
abort();
|
||||
},
|
||||
n: name => if (!(etok is name) || etok as name != n) {
|
||||
fmt::errorfln("bad token at {}: got {}, wanted {}",
|
||||
i, tokstr(tok), tokstr(etok));
|
||||
abort();
|
||||
},
|
||||
l: literal => if (!(etok is literal)) {
|
||||
fmt::errorfln("bad token at {}: got {}, wanted {}",
|
||||
i, tokstr(tok), tokstr(etok));
|
||||
abort();
|
||||
} else {
|
||||
litassert(l, etok as literal);
|
||||
},
|
||||
* => abort("TODO"),
|
||||
};
|
||||
assert(loc.path == "<test>");
|
||||
if (loc.line != eline || loc.col != ecol) {
|
||||
fmt::errorfln("bad line/col at {}: got {},{}; wanted {},{}",
|
||||
i, loc.line, loc.col, eline, ecol);
|
||||
abort();
|
||||
};
|
||||
};
|
||||
assert(lex(&lexer) is io::EOF);
|
||||
};
|
||||
|
||||
@test fn lex1() void = {
|
||||
const in = "~,{[(}]);";
|
||||
const expected: [_](uint, uint, token) = [
|
||||
(1, 1, btoken::BNOT),
|
||||
(1, 2, btoken::COMMA),
|
||||
(1, 3, btoken::LBRACE),
|
||||
(1, 4, btoken::LBRACKET),
|
||||
(1, 5, btoken::LPAREN),
|
||||
(1, 6, btoken::RBRACE),
|
||||
(1, 7, btoken::RBRACKET),
|
||||
(1, 8, btoken::RPAREN),
|
||||
(1, 9, btoken::SEMICOLON),
|
||||
];
|
||||
lextest(in, expected);
|
||||
};
|
||||
|
||||
@test fn lex2() void = {
|
||||
// Ends with = to test =, EOF
|
||||
const in = "^ ^^ ^= * *= % %= + += - -= : :: & && &= | || |= = == / /= =";
|
||||
const expected: [_](uint, uint, token) = [
|
||||
(1, 1, btoken::BXOR),
|
||||
(1, 3, btoken::LXOR),
|
||||
(1, 6, btoken::BXOREQ),
|
||||
(1, 9, btoken::TIMES),
|
||||
(1, 11, btoken::TIMESEQ),
|
||||
(1, 14, btoken::MODULO),
|
||||
(1, 16, btoken::MODEQ),
|
||||
(1, 19, btoken::PLUS),
|
||||
(1, 21, btoken::PLUSEQ),
|
||||
(1, 24, btoken::MINUS),
|
||||
(1, 26, btoken::MINUSEQ),
|
||||
(1, 29, btoken::COLON),
|
||||
(1, 31, btoken::DOUBLE_COLON),
|
||||
(1, 34, btoken::BAND),
|
||||
(1, 36, btoken::LAND),
|
||||
(1, 39, btoken::ANDEQ),
|
||||
(1, 42, btoken::BOR),
|
||||
(1, 44, btoken::LOR),
|
||||
(1, 47, btoken::OREQ),
|
||||
(1, 50, btoken::EQUAL),
|
||||
(1, 52, btoken::LEQUAL),
|
||||
(1, 55, btoken::DIV),
|
||||
(1, 57, btoken::DIVEQ),
|
||||
(1, 60, btoken::EQUAL),
|
||||
];
|
||||
lextest(in, expected);
|
||||
};
|
||||
|
||||
@test fn lex3() void = {
|
||||
const in = ". .. ... < << <= <<= > >> >= >>= >>";
|
||||
const expected: [_](uint, uint, token) = [
|
||||
(1, 1, btoken::DOT),
|
||||
(1, 3, btoken::SLICE),
|
||||
(1, 6, btoken::ELLIPSIS),
|
||||
(1, 10, btoken::LESS),
|
||||
(1, 12, btoken::LSHIFT),
|
||||
(1, 15, btoken::LESSEQ),
|
||||
(1, 18, btoken::LSHIFTEQ),
|
||||
(1, 22, btoken::GREATER),
|
||||
(1, 24, btoken::RSHIFT),
|
||||
(1, 27, btoken::GREATEREQ),
|
||||
(1, 30, btoken::RSHIFTEQ),
|
||||
(1, 34, btoken::RSHIFT),
|
||||
];
|
||||
lextest(in, expected);
|
||||
};
|
||||
|
||||
@test fn lexname() void = {
|
||||
const in = "hello world return void foobar";
|
||||
const expected: [_](uint, uint, token) = [
|
||||
(1, 1, "hello": name),
|
||||
(1, 7, "world": name),
|
||||
(1, 13, btoken::RETURN),
|
||||
(1, 20, btoken::VOID),
|
||||
(1, 25, "foobar": name),
|
||||
];
|
||||
lextest(in, expected);
|
||||
};
|
||||
|
||||
@test fn keywords() void = {
|
||||
let keywords = bmap[..btoken::LAST_KEYWORD+1];
|
||||
for (let i = 0z; i < len(keywords); i += 1) {
|
||||
let lexer = init(bufio::fixed(
|
||||
strings::toutf8(keywords[i]), mode::READ),
|
||||
"<test>");
|
||||
let tl = match (lex(&lexer)) {
|
||||
tl: (token, location) => tl,
|
||||
* => abort(),
|
||||
};
|
||||
let tok = tl.0;
|
||||
assert(tok is btoken);
|
||||
assert(tok as btoken == i: btoken);
|
||||
};
|
||||
};
|
||||
|
||||
@test fn comments() void = {
|
||||
const in = "hello world // foo\nbar";
|
||||
const expected: [_](uint, uint, token) = [
|
||||
(1, 1, "hello": name),
|
||||
(1, 7, "world": name),
|
||||
(2, 1, "bar": name),
|
||||
];
|
||||
lextest(in, expected);
|
||||
};
|
||||
|
||||
@test fn runes() void = {
|
||||
const in = "'a' 'b' '\\a' '\\b' '\\f' '\\n' '\\r' '\\t' '\\v' '\\0' "
|
||||
"'\\\\' '\\\'' '\\x0A' '\\u1234' '\\U12345678'";
|
||||
const expected: [_](uint, uint, token) = [
|
||||
(1, 1, 'a'),
|
||||
(1, 5, 'b'),
|
||||
(1, 9, '\a'),
|
||||
(1, 14, '\b'),
|
||||
(1, 19, '\f'),
|
||||
(1, 24, '\n'),
|
||||
(1, 29, '\r'),
|
||||
(1, 34, '\t'),
|
||||
(1, 39, '\v'),
|
||||
(1, 44, '\0'),
|
||||
(1, 49, '\\'),
|
||||
(1, 54, '\''),
|
||||
(1, 59, '\x0A'),
|
||||
(1, 66, '\u1234'),
|
||||
(1, 75, '\U12345678'),
|
||||
];
|
||||
lextest(in, expected);
|
||||
};
|
||||
|
||||
@test fn strings() void = {
|
||||
const in = "\"a\" \"b\" \"\\a\" \"\\b\" \"\\f\" \"\\n\" \"\\r\" "
|
||||
"\"\\t\" \"\\v\" \"\\0\" \"\\\\\" \"\\\'\"";
|
||||
const expected: [_](uint, uint, token) = [
|
||||
(1, 1, "a": literal),
|
||||
(1, 5, "b": literal),
|
||||
(1, 9, "\a": literal),
|
||||
(1, 14, "\b": literal),
|
||||
(1, 19, "\f": literal),
|
||||
(1, 24, "\n": literal),
|
||||
(1, 29, "\r": literal),
|
||||
(1, 34, "\t": literal),
|
||||
(1, 39, "\v": literal),
|
||||
(1, 44, "\0": literal),
|
||||
(1, 49, "\\": literal),
|
||||
(1, 54, "\'": literal),
|
||||
];
|
||||
// TODO: test \x and \u and \U
|
||||
lextest(in, expected);
|
||||
const in = "\"ab\\a\\b\\f\\n\\r\\t\\v\\0\\\\\\'\"";
|
||||
const expected: [_](uint, uint, token) = [
|
||||
(1, 1, "ab\a\b\f\n\r\t\v\0\\\'": literal),
|
||||
];
|
||||
lextest(in, expected);
|
||||
const in = "\"hello world\" \"こんにちは\" \"return\" \"foo\"";
|
||||
const expected: [_](uint, uint, token) = [
|
||||
(1, 1, "hello world": literal),
|
||||
(1, 15, "こんにちは": literal),
|
||||
(1, 23, "return": literal),
|
||||
(1, 32, "foo": literal),
|
||||
];
|
||||
lextest(in, expected);
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
// Returns the new PID to the parent, void to the child, or errno if something
|
||||
// goes wrong.
|
||||
export fn clone(
|
||||
stack: nullable *void,
|
||||
flags: int,
|
||||
parent_tid: nullable *int,
|
||||
child_tid: nullable *int,
|
||||
tls: u64,
|
||||
) (int | void | errno) = {
|
||||
return match (wrap_return(syscall5(SYS_clone,
|
||||
flags: u64,
|
||||
stack: uintptr: u64,
|
||||
parent_tid: uintptr: u64,
|
||||
child_tid: uintptr: u64,
|
||||
tls))) {
|
||||
u: u64 => switch (u) {
|
||||
0 => void,
|
||||
* => u: int,
|
||||
},
|
||||
err: errno => err,
|
||||
};
|
||||
};
|
||||
|
||||
export def O_DIRECTORY: int = 0o200000;
|
||||
@ -0,0 +1,16 @@
|
||||
export @noreturn @symbol("rt.abort") fn _abort(msg: str) void = {
|
||||
reason = abort_reason { loc = "", msg = msg };
|
||||
longjmp(&jmp, 1);
|
||||
};
|
||||
|
||||
// See harec:include/gen.h
|
||||
const reasons: [_]str = [
|
||||
"slice or array access out of bounds", // 0
|
||||
"type assertion failed", // 1
|
||||
"out of memory", // 2
|
||||
];
|
||||
|
||||
export @noreturn fn abort_fixed(loc: str, i: int) void = {
|
||||
reason = abort_reason { loc = loc, msg = reasons[i] };
|
||||
longjmp(&jmp, 1);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
fn platform_abort(msg: str) void = {
|
||||
const prefix = "Abort: ";
|
||||
const linefeed = "\n";
|
||||
write(2, *(&prefix: **void): *const char, len(prefix));
|
||||
write(2, *(&msg: **void): *const char, len(msg));
|
||||
write(2, *(&linefeed: **void): *const char, 1);
|
||||
kill(getpid(), SIGABRT);
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
// A UNIX socket address.
|
||||
export type addr = str;
|
||||
|
||||
// Invalid UNIX socket path.
|
||||
export type invalid = void!;
|
||||
@ -0,0 +1,23 @@
|
||||
// Minimum value which can be stored in an int type.
|
||||
export def INT_MIN: int = I32_MIN;
|
||||
|
||||
// Maximum value which can be stored in an int type.
|
||||
export def INT_MAX: int = I32_MAX;
|
||||
|
||||
// Minimum value which can be stored in a uint type
|
||||
export def UINT_MIN: uint = U32_MIN;
|
||||
|
||||
// Maximum value which can be stored in a uint type.
|
||||
export def UINT_MAX: uint = U32_MAX;
|
||||
|
||||
// Minimum value which can be stored in a size type
|
||||
export def SIZE_MIN: size = U64_MIN;
|
||||
|
||||
// Maximum value which can be stored in a size type.
|
||||
export def SIZE_MAX: size = U64_MAX;
|
||||
|
||||
// Minimum value which can be stored in a uintptr type
|
||||
export def UINTPTR_MIN: uintptr = U64_MIN: uintptr;
|
||||
|
||||
// Maximum value which can be stored in a uintptr type.
|
||||
export def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
|
||||
@ -0,0 +1,23 @@
|
||||
// Minimum value which can be stored in an int type.
|
||||
export def INT_MIN: int = I32_MIN;
|
||||
|
||||
// Maximum value which can be stored in an int type.
|
||||
export def INT_MAX: int = I32_MAX;
|
||||
|
||||
// Minimum value which can be stored in a uint type
|
||||
export def UINT_MIN: uint = U32_MIN;
|
||||
|
||||
// Maximum value which can be stored in a uint type.
|
||||
export def UINT_MAX: uint = U32_MAX;
|
||||
|
||||
// Minimum value which can be stored in a size type
|
||||
export def SIZE_MIN: size = U64_MIN;
|
||||
|
||||
// Maximum value which can be stored in a size type.
|
||||
export def SIZE_MAX: size = U64_MAX;
|
||||
|
||||
// Minimum value which can be stored in a uintptr type
|
||||
export def UINTPTR_MIN: uintptr = U64_MIN: uintptr;
|
||||
|
||||
// Maximum value which can be stored in a uintptr type.
|
||||
export def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
|
||||
@ -0,0 +1,73 @@
|
||||
// Reads a u16 from a buffer in big-endian order.
|
||||
export fn begetu16(buf: []u8) u16 = {
|
||||
return
|
||||
(buf[0] << 8u16) |
|
||||
(buf[1] << 0);
|
||||
};
|
||||
|
||||
// Writes a u16 into a buffer in big-endian order.
|
||||
export fn beputu16(buf: []u8, in: u16) void = {
|
||||
buf[1] = (in >> 0): u8;
|
||||
buf[0] = (in >> 8): u8;
|
||||
};
|
||||
|
||||
// Reads a u32 from a buffer in big-endian order.
|
||||
export fn begetu32(buf: []u8) u32 = {
|
||||
return
|
||||
(buf[0] << 24u32) |
|
||||
(buf[1] << 16u32) |
|
||||
(buf[2] << 8u32) |
|
||||
(buf[3] << 0);
|
||||
};
|
||||
|
||||
// Writes a u32 into a buffer in big-endian order.
|
||||
export fn beputu32(buf: []u8, in: u32) void = {
|
||||
buf[3] = (in >> 0): u8;
|
||||
buf[2] = (in >> 8): u8;
|
||||
buf[1] = (in >> 16): u8;
|
||||
buf[0] = (in >> 24): u8;
|
||||
};
|
||||
|
||||
// Reads a u64 from a buffer in big-endian order.
|
||||
export fn begetu64(buf: []u8) u64 = {
|
||||
return
|
||||
(buf[0] << 56u64) |
|
||||
(buf[1] << 48u64) |
|
||||
(buf[2] << 40u64) |
|
||||
(buf[3] << 32u64) |
|
||||
(buf[4] << 24u64) |
|
||||
(buf[5] << 16u64) |
|
||||
(buf[6] << 8u64) |
|
||||
(buf[7] << 0);
|
||||
};
|
||||
|
||||
// Writes a u64 into a buffer in big-endian order.
|
||||
export fn beputu64(buf: []u8, in: u64) void = {
|
||||
buf[7] = (in >> 0): u8;
|
||||
buf[6] = (in >> 8): u8;
|
||||
buf[5] = (in >> 16): u8;
|
||||
buf[4] = (in >> 24): u8;
|
||||
buf[3] = (in >> 32): u8;
|
||||
buf[2] = (in >> 40): u8;
|
||||
buf[1] = (in >> 48): u8;
|
||||
buf[0] = (in >> 56): u8;
|
||||
};
|
||||
|
||||
@test fn big() void = {
|
||||
let buf: [8]u8 = [0...];
|
||||
beputu16(buf, 0x1234);
|
||||
assert(buf[0] == 0x12 && buf[1] == 0x34);
|
||||
assert(begetu16(buf) == 0x1234);
|
||||
|
||||
beputu32(buf, 0x12345678);
|
||||
assert(buf[0] == 0x12 && buf[1] == 0x34
|
||||
&& buf[2] == 0x56 && buf[3] == 0x78);
|
||||
assert(begetu32(buf) == 0x12345678);
|
||||
|
||||
beputu64(buf, 0x1234567887654321);
|
||||
assert(buf[0] == 0x12 && buf[1] == 0x34
|
||||
&& buf[2] == 0x56 && buf[3] == 0x78
|
||||
&& buf[4] == 0x87 && buf[5] == 0x65
|
||||
&& buf[6] == 0x43 && buf[7] == 0x21);
|
||||
assert(begetu64(buf) == 0x1234567887654321);
|
||||
};
|
||||
@ -0,0 +1,48 @@
|
||||
// crypto::math provides constant-time mathematical operations useful for
|
||||
// cryptographic algorithms.
|
||||
|
||||
// Rotates a 32-bit unsigned integer left by k bits. k may be negative to rotate
|
||||
// right instead, or see [rotr32].
|
||||
export fn rotl32(x: u32, k: int) u32 = {
|
||||
const n = 32u32;
|
||||
const s = k: u32 & (n - 1);
|
||||
return x << s | x >> (n - s);
|
||||
};
|
||||
|
||||
// Rotates a 32-bit unsigned integer right by k bits. k may be negative to
|
||||
// rotate left instead, or see [rotl32].
|
||||
export fn rotr32(x: u32, k: int) u32 = rotl32(x, -k);
|
||||
|
||||
@test fn lrot32() void = {
|
||||
let a = 0b11110000111100001111000011110000u32;
|
||||
assert(rotl32(a, 2) == 0b11000011110000111100001111000011u32);
|
||||
assert(rotl32(a, -2) == 0b00111100001111000011110000111100u32);
|
||||
assert(rotl32(a, 32) == 0b11110000111100001111000011110000u32);
|
||||
assert(rotl32(a, 64) == 0b11110000111100001111000011110000u32);
|
||||
};
|
||||
|
||||
// Rotates a 64-bit unsigned integer left by k bits. k may be negative to rotate
|
||||
// right instead, or see [rotr64].
|
||||
export fn rotl64(x: u64, k: int) u64 = {
|
||||
const n = 64u64;
|
||||
const s = k: u64 & (n - 1);
|
||||
return x << s | x >> (n - s);
|
||||
};
|
||||
|
||||
// Rotates a 64-bit unsigned integer right by k bits. k may be negative to rotate
|
||||
// left instead, or see [rotl64].
|
||||
export fn rotr64(x: u64, k: int) u64 = rotl64(x, -k);
|
||||
|
||||
@test fn lrot64() void = {
|
||||
let a = 1u64;
|
||||
assert(rotl64(a, 1) == 0b10);
|
||||
assert(rotl64(a, -1) == 0b1000000000000000000000000000000000000000000000000000000000000000);
|
||||
assert(rotl64(a, 39) == (1u64 << 39));
|
||||
|
||||
let a = 0b1111000011110000111100001111000011110000111100001111000011110000u64;
|
||||
assert(rotl64(a, 64) == a);
|
||||
assert(rotl64(a, 0) == a);
|
||||
assert(rotl64(a, 2) == 0b1100001111000011110000111100001111000011110000111100001111000011u64);
|
||||
assert(rotl64(a, -2) == 0b0011110000111100001111000011110000111100001111000011110000111100u64);
|
||||
|
||||
};
|
||||
@ -0,0 +1,294 @@
|
||||
use bytes;
|
||||
use encoding::utf8;
|
||||
use errors;
|
||||
use io;
|
||||
use strings;
|
||||
|
||||
export type bufstream = struct {
|
||||
stream: io::stream,
|
||||
source: *io::stream,
|
||||
rbuffer: []u8,
|
||||
wbuffer: []u8,
|
||||
ravail: size,
|
||||
wavail: size,
|
||||
flush: []u8,
|
||||
unread: []u8,
|
||||
};
|
||||
|
||||
export fn static_buffered(
|
||||
src: *io::stream,
|
||||
rbuf: []u8,
|
||||
wbuf: []u8,
|
||||
s: *bufstream,
|
||||
) *io::stream = {
|
||||
static let flush_default = ['\n': u32: u8];
|
||||
*s = bufstream {
|
||||
stream = io::stream {
|
||||
name = src.name,
|
||||
closer = &buffered_close_static,
|
||||
unwrap = &buffered_unwrap,
|
||||
},
|
||||
source = src,
|
||||
rbuffer = rbuf,
|
||||
wbuffer = wbuf,
|
||||
flush = flush_default,
|
||||
...
|
||||
};
|
||||
if (len(rbuf) != 0) {
|
||||
s.stream.reader = &buffered_read;
|
||||
};
|
||||
if (len(wbuf) != 0) {
|
||||
s.stream.writer = &buffered_write;
|
||||
};
|
||||
if (len(rbuf) != 0 && len(wbuf) != 0) {
|
||||
assert(rbuf: *[*]u8 != wbuf: *[*]u8,
|
||||
"Cannot use bufio::buffered with same buffer for reads and writes");
|
||||
};
|
||||
return &s.stream;
|
||||
};
|
||||
|
||||
// Creates a stream which buffers reads and writes for the underlying stream.
|
||||
// This is generally used to improve performance of small reads/writes for
|
||||
// sources where I/O operations are costly, such as if they invoke a syscall or
|
||||
// take place over the network.
|
||||
//
|
||||
// The caller should supply one or both of a read and write buffer as a slice of
|
||||
// the desired buffer, or empty slices if read or write functionality is
|
||||
// disabled. The same buffer may not be used for both reads and writes.
|
||||
//
|
||||
// The caller is responsible for closing the underlying stream and freeing the
|
||||
// provided buffers after the buffered stream is closed.
|
||||
export fn buffered(
|
||||
src: *io::stream,
|
||||
rbuf: []u8,
|
||||
wbuf: []u8,
|
||||
) *io::stream = {
|
||||
let s = alloc(bufstream { ... });
|
||||
let st = static_buffered(src, rbuf, wbuf, s);
|
||||
st.closer = &buffered_close;
|
||||
return st;
|
||||
};
|
||||
|
||||
// Flushes pending writes to the underlying stream.
|
||||
export fn flush(s: *io::stream) (io::error | void) = {
|
||||
assert(s.writer == &buffered_write,
|
||||
"bufio::flushed used on non-buffered stream");
|
||||
let s = s: *bufstream;
|
||||
io::write(s.source, s.wbuffer[..s.wavail])?;
|
||||
s.wavail = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
// Sets the list of bytes which will cause the stream to flush when written. By
|
||||
// default, the stream will flush when a newline (\n) is written.
|
||||
export fn setflush(s: *io::stream, b: []u8) void = {
|
||||
assert(s.writer == &buffered_write,
|
||||
"bufio: setflush used on non-buffered stream");
|
||||
let s = s: *bufstream;
|
||||
s.flush = b;
|
||||
};
|
||||
|
||||
// Returns true if this is a buffered stream.
|
||||
export fn isbuffered(s: *io::stream) bool = {
|
||||
return s.reader == &buffered_read || s.writer == &buffered_write;
|
||||
};
|
||||
|
||||
// Returns true if this stream or any underlying streams are buffered.
|
||||
export fn any_isbuffered(s: *io::stream) bool = {
|
||||
for (!isbuffered(s)) {
|
||||
s = match (io::source(s)) {
|
||||
errors::unsupported => return false,
|
||||
s: *io::stream => s,
|
||||
};
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// Unreads a slice of bytes. The next read calls on this buffer will consume the
|
||||
// un-read data before consuming further data from the underlying source, or any
|
||||
// buffered data.
|
||||
export fn unread(s: *io::stream, buf: []u8) void = {
|
||||
assert(isbuffered(s), "bufio: unread used on non-buffered stream");
|
||||
let s = s: *bufstream;
|
||||
append(s.unread, ...buf);
|
||||
};
|
||||
|
||||
// Unreads a rune; see [unread].
|
||||
export fn unreadrune(s: *io::stream, rn: rune) void = {
|
||||
assert(isbuffered(s), "bufio: unread used on non-buffered stream");
|
||||
let s = s: *bufstream;
|
||||
append(s.unread, ...utf8::encoderune(rn));
|
||||
};
|
||||
|
||||
fn buffered_close(s: *io::stream) void = {
|
||||
assert(s.closer == &buffered_close);
|
||||
if (s.writer != null) {
|
||||
flush(s);
|
||||
};
|
||||
free(s);
|
||||
};
|
||||
|
||||
fn buffered_close_static(s: *io::stream) void = {
|
||||
assert(s.closer == &buffered_close_static);
|
||||
if (s.writer != null) {
|
||||
flush(s);
|
||||
};
|
||||
free(s);
|
||||
};
|
||||
|
||||
fn buffered_unwrap(s: *io::stream) *io::stream = {
|
||||
assert(s.unwrap == &buffered_unwrap);
|
||||
let s = s: *bufstream;
|
||||
return s.source;
|
||||
};
|
||||
|
||||
fn buffered_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
|
||||
assert(s.reader == &buffered_read);
|
||||
let s = s: *bufstream;
|
||||
if (len(s.unread) != 0) {
|
||||
let n = if (len(buf) < len(s.unread)) len(buf) else len(s.unread);
|
||||
buf[..n] = s.unread[..n];
|
||||
delete(s.unread[..n]);
|
||||
return n;
|
||||
};
|
||||
|
||||
let n = if (len(buf) < len(s.rbuffer)) len(buf) else len(s.rbuffer);
|
||||
if (n > s.ravail) {
|
||||
let z = match (io::read(s.source, s.rbuffer[s.ravail..])) {
|
||||
err: io::error => return err,
|
||||
io::EOF => {
|
||||
if (s.ravail == 0) {
|
||||
return io::EOF;
|
||||
};
|
||||
0z;
|
||||
},
|
||||
z: size => z,
|
||||
};
|
||||
s.ravail += z;
|
||||
n = if (n > s.ravail) s.ravail else n;
|
||||
assert(n != 0);
|
||||
};
|
||||
|
||||
buf[..n] = s.rbuffer[..n];
|
||||
s.rbuffer[..len(s.rbuffer) - n] = s.rbuffer[n..];
|
||||
s.ravail -= n;
|
||||
return n;
|
||||
};
|
||||
|
||||
fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = {
|
||||
assert(s.writer == &buffered_write);
|
||||
let s = s: *bufstream;
|
||||
let buf = buf;
|
||||
|
||||
let doflush = false;
|
||||
for (let i = 0z; i < len(buf); i += 1) {
|
||||
for (let j = 0z; j < len(s.flush); j += 1) {
|
||||
if (buf[i] == s.flush[j]) {
|
||||
doflush = true;
|
||||
break;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let z = 0z;
|
||||
for (len(buf) > 0) {
|
||||
let avail = len(s.wbuffer) - s.wavail;
|
||||
if (avail == 0) {
|
||||
flush(&s.stream)?;
|
||||
avail = len(s.wbuffer);
|
||||
};
|
||||
|
||||
const n = if (avail < len(buf)) avail else len(buf);
|
||||
s.wbuffer[s.wavail..s.wavail + n] = buf[..n];
|
||||
buf = buf[n..];
|
||||
s.wavail += n;
|
||||
z += n;
|
||||
};
|
||||
|
||||
if (doflush) {
|
||||
flush(&s.stream)?;
|
||||
};
|
||||
|
||||
return z;
|
||||
};
|
||||
|
||||
@test fn buffered_read() void = {
|
||||
let sourcebuf: []u8 = [1, 3, 3, 7];
|
||||
let source = fixed(sourcebuf, io::mode::READ);
|
||||
let fb = source: *fixed_stream;
|
||||
defer io::close(source);
|
||||
|
||||
let rbuf: [1024]u8 = [0...];
|
||||
let f = buffered(source, rbuf, []);
|
||||
defer io::close(f);
|
||||
|
||||
let buf: [1024]u8 = [0...];
|
||||
assert(io::read(f, buf[..2]) as size == 2);
|
||||
assert(len(fb.buf) == 0, "fixed stream was not fully consumed");
|
||||
assert(bytes::equal(buf[..2], [1, 3]));
|
||||
|
||||
assert(io::read(f, buf[2..]) as size == 2);
|
||||
assert(bytes::equal(buf[..4], [1, 3, 3, 7]));
|
||||
assert(io::read(f, buf) is io::EOF);
|
||||
|
||||
let sourcebuf: [32]u8 = [1, 3, 3, 7, 0...];
|
||||
sourcebuf[32..36] = [7, 3, 3, 1];
|
||||
let source = fixed(sourcebuf, io::mode::READ);
|
||||
let fb = source: *fixed_stream;
|
||||
defer io::close(source);
|
||||
|
||||
let rbuf: [16]u8 = [0...];
|
||||
let f = buffered(source, rbuf, []);
|
||||
defer io::close(f);
|
||||
|
||||
let buf: [32]u8 = [0...];
|
||||
assert(io::read(f, buf) as size == 16);
|
||||
assert(len(fb.buf) == 16);
|
||||
|
||||
assert(io::read(f, buf[16..]) as size == 16);
|
||||
assert(bytes::equal(buf, sourcebuf));
|
||||
assert(io::read(f, buf) is io::EOF);
|
||||
assert(len(fb.buf) == 0);
|
||||
};
|
||||
|
||||
@test fn buffered_write() void = {
|
||||
// Normal case
|
||||
let sink = dynamic(io::mode::WRITE);
|
||||
defer io::close(sink);
|
||||
|
||||
let wbuf: [1024]u8 = [0...];
|
||||
let f = buffered(sink, [], wbuf);
|
||||
defer io::close(f);
|
||||
|
||||
assert(io::write(f, [1, 3, 3, 7]) as size == 4);
|
||||
assert(len(buffer(sink)) == 0);
|
||||
assert(io::write(f, [1, 3, 3, 7]) as size == 4);
|
||||
assert(flush(f) is void);
|
||||
assert(bytes::equal(buffer(sink), [1, 3, 3, 7, 1, 3, 3, 7]));
|
||||
|
||||
// Test flushing via buffer exhaustion
|
||||
let sink = dynamic(io::mode::WRITE);
|
||||
defer io::close(sink);
|
||||
|
||||
let wbuf: [4]u8 = [0...];
|
||||
let f = buffered(sink, [], wbuf);
|
||||
|
||||
assert(io::write(f, [1, 3, 3, 7]) as size == 4);
|
||||
assert(len(buffer(sink)) == 0);
|
||||
assert(io::write(f, [1, 3, 3, 7]) as size == 4);
|
||||
assert(bytes::equal(buffer(sink), [1, 3, 3, 7]));
|
||||
io::close(f); // Should flush
|
||||
assert(bytes::equal(buffer(sink), [1, 3, 3, 7, 1, 3, 3, 7]));
|
||||
|
||||
// Test flushing via flush characters
|
||||
let sink = dynamic(io::mode::WRITE);
|
||||
defer io::close(sink);
|
||||
|
||||
let wbuf: [1024]u8 = [0...];
|
||||
let f = buffered(sink, [], wbuf);
|
||||
|
||||
assert(io::write(f, strings::toutf8("hello")) as size == 5);
|
||||
assert(len(buffer(sink)) == 0);
|
||||
assert(io::write(f, strings::toutf8(" world!\n")) as size == 8);
|
||||
assert(bytes::equal(buffer(sink), strings::toutf8("hello world!\n")));
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
use ascii;
|
||||
|
||||
fn isnamestart(rn: rune) bool = {
|
||||
if (rn == ':' || rn == '_' || ascii::isalpha(rn)) return true;
|
||||
let rn = rn: u32;
|
||||
return
|
||||
(rn >= 0xC0 && rn <= 0xD6) ||
|
||||
(rn >= 0xD8 && rn <= 0xF6) ||
|
||||
(rn >= 0xF8 && rn <= 0x2FF) ||
|
||||
(rn >= 0x370 && rn <= 0x37D) ||
|
||||
(rn >= 0x37F && rn <= 0x1FFF) ||
|
||||
(rn >= 0x200C && rn <= 0x200D) ||
|
||||
(rn >= 0x2070 && rn <= 0x218F) ||
|
||||
(rn >= 0x2C00 && rn <= 0x2FEF) ||
|
||||
(rn >= 0x3001 && rn <= 0xD7FF) ||
|
||||
(rn >= 0xF900 && rn <= 0xFDCF) ||
|
||||
(rn >= 0xFDF0 && rn <= 0xFFFD) ||
|
||||
(rn >= 0x10000 && rn <= 0xEFFFF);
|
||||
};
|
||||
|
||||
fn isname(rn: rune) bool = {
|
||||
if (isnamestart(rn) || rn == '-' || rn == '.' || ascii::isdigit(rn)) {
|
||||
return true;
|
||||
};
|
||||
let rn = rn: u32;
|
||||
return
|
||||
(rn == 0xB7) ||
|
||||
(rn >= 0x300 && rn <= 0x36F) ||
|
||||
(rn >= 0x203F && rn <= 0x2040);
|
||||
};
|
||||
@ -0,0 +1,42 @@
|
||||
// A tagged union of all signed integer types.
|
||||
export type signed = (i8 | i16 | i32 | i64 | int);
|
||||
|
||||
// A tagged union of all unsigned integer types, excluding uintptr.
|
||||
export type unsigned = (u8 | u16 | u32 | u64 | uint | size);
|
||||
|
||||
// A tagged union of all integer types.
|
||||
export type integer = (...signed | ...unsigned);
|
||||
|
||||
// A tagged union of all floating point numeric types.
|
||||
export type floating = (f32 | f64);
|
||||
|
||||
// A tagged union of all numeric types.
|
||||
export type numeric = (...integer | ...floating);
|
||||
|
||||
// A type representing the internal structure of strings, useful for low-level
|
||||
// string manipulation.
|
||||
export type string = struct {
|
||||
// UTF-8 encoded octets, plus a NUL terminator.
|
||||
data: nullable *[*]u8,
|
||||
|
||||
// The length capacity, in octets of UTF-8 data, not including the NUL
|
||||
// terminator.
|
||||
length: size,
|
||||
|
||||
// The allocated capacity, in octets of UTF-8 data, not including the
|
||||
// NUL terminator.
|
||||
capacity: size,
|
||||
};
|
||||
|
||||
// A type representing the internal structure of slices, useful for low-level
|
||||
// slice manipulation.
|
||||
export type slice = struct {
|
||||
// The slice contents.
|
||||
data: nullable *void,
|
||||
|
||||
// The number of members of the slice.
|
||||
length: size,
|
||||
|
||||
// The allocated capacity (in members) of data.
|
||||
capacity: size,
|
||||
};
|
||||
@ -0,0 +1,120 @@
|
||||
use ascii;
|
||||
use errors;
|
||||
use os;
|
||||
use strings;
|
||||
|
||||
// Prepares a [command] based on its name and a list of arguments. The argument
|
||||
// list should not start with the command name; it will be added for you. The
|
||||
// argument list is borrowed from the strings you pass into this command.
|
||||
//
|
||||
// If 'name' does not contain a '/', the $PATH will be consulted to find the
|
||||
// correct executable. If path resolution fails, nocmd is returned.
|
||||
//
|
||||
// let cmd = exec::cmd("echo", "hello world");
|
||||
// let proc = exec::start(&cmd);
|
||||
// let status = exec::wait(&proc);
|
||||
// assert(exec::status(status) == 0);
|
||||
//
|
||||
// By default, the new command will inherit the current process's environment.
|
||||
export fn cmd(name: str, args: str...) (command | error) = {
|
||||
let env = os::getenvs();
|
||||
let cmd = command {
|
||||
platform: platform_cmd =
|
||||
if (strings::contains(name, '/')) match (open(name)) {
|
||||
err: errors::opaque => return nocmd,
|
||||
p: platform_cmd => p,
|
||||
} else match (lookup(name)) {
|
||||
void => return nocmd,
|
||||
p: platform_cmd => p,
|
||||
},
|
||||
argv = alloc([], len(args) + 1z),
|
||||
env = alloc([], len(env)),
|
||||
...
|
||||
};
|
||||
append(cmd.argv, name, ...args);
|
||||
append(cmd.env, ...env);
|
||||
return cmd;
|
||||
};
|
||||
|
||||
// Sets the 0th value of argv for this command. It is uncommon to need this.
|
||||
export fn setname(cmd: *command, name: str) void = {
|
||||
free(cmd.argv[0]);
|
||||
cmd.argv[0] = name;
|
||||
};
|
||||
|
||||
// Frees state associated with a command. You only need to call this if you do
|
||||
// not execute the command with [exec] or [start]; in those cases the state is
|
||||
// cleaned up for you.
|
||||
export fn finish(cmd: *command) void = {
|
||||
platform_finish(cmd);
|
||||
free(cmd.argv);
|
||||
};
|
||||
|
||||
// Executes a prepared command in the current address space, overwriting the
|
||||
// running process with the new command.
|
||||
export @noreturn fn exec(cmd: *command) void = {
|
||||
defer finish(cmd); // Note: doesn't happen if exec succeeds
|
||||
platform_exec(cmd);
|
||||
abort("os::exec::exec failed");
|
||||
};
|
||||
|
||||
// Starts a prepared command in a new process.
|
||||
export fn start(cmd: *command) (error | process) = {
|
||||
defer finish(cmd);
|
||||
return match (platform_start(cmd)) {
|
||||
err: errors::opaque => err,
|
||||
proc: process => proc,
|
||||
};
|
||||
};
|
||||
|
||||
// Empties the environment variables for the command. By default, the command
|
||||
// inherits the environment of the parent process.
|
||||
export fn clearenv(cmd: *command) void = {
|
||||
cmd.env = [];
|
||||
};
|
||||
|
||||
// Adds or sets a variable in the command environment. This does not affect the
|
||||
// current process environment. The 'key' must be a valid environment variable
|
||||
// name per POSIX definition 3.235. This includes underscores and alphanumeric
|
||||
// ASCII characters, and cannot begin with a number.
|
||||
export fn setenv(cmd: *command, key: str, value: str) void = {
|
||||
let iter = strings::iter(key);
|
||||
for (let i = 0z; true; i += 1) match (strings::next(&iter)) {
|
||||
void => break,
|
||||
r: rune => if (i == 0) assert(r == '_' || ascii::isalpha(r),
|
||||
"Invalid environment variable")
|
||||
else assert(r == '_' || ascii::isalnum(r),
|
||||
"Invalid environment variable"),
|
||||
};
|
||||
|
||||
// XXX: This can be a binary search
|
||||
let fullkey = strings::concat(key, "=");
|
||||
defer free(fullkey);
|
||||
for (let i = 0z; i < len(cmd.env); i += 1) {
|
||||
if (strings::has_prefix(cmd.env[i], fullkey)) {
|
||||
delete(cmd.env[i]);
|
||||
break;
|
||||
};
|
||||
};
|
||||
append(cmd.env, strings::concat(fullkey, value));
|
||||
};
|
||||
|
||||
fn lookup(name: str) (platform_cmd | void) = {
|
||||
const path = match (os::getenv("PATH")) {
|
||||
void => return,
|
||||
s: str => s,
|
||||
};
|
||||
let tok = strings::tokenize(path, ":");
|
||||
for (true) {
|
||||
const item = match (strings::next_token(&tok)) {
|
||||
void => break,
|
||||
s: str => s,
|
||||
};
|
||||
let path = strings::concat(item, "/", name);
|
||||
defer free(path);
|
||||
match (open(path)) {
|
||||
err: errors::opaque => continue,
|
||||
p: platform_cmd => return p,
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
// The requested resource is not available.
|
||||
export type busy = void!;
|
||||
|
||||
// An attempt was made to create a resource which already exists.
|
||||
export type exists = void!;
|
||||
|
||||
// An function was called with an invalid combination of arguments.
|
||||
export type invalid = void!;
|
||||
|
||||
// The user does not have permission to use this resource.
|
||||
export type noaccess = void!;
|
||||
|
||||
// An entry was requested which does not exist.
|
||||
export type noentry = void;
|
||||
|
||||
// The requested operation caused a numeric overflow condition.
|
||||
export type overflow = void!;
|
||||
|
||||
// The requested operation is not supported.
|
||||
export type unsupported = void!;
|
||||
|
||||
// A tagged union of all error types.
|
||||
export type error = (
|
||||
busy |
|
||||
exists |
|
||||
invalid |
|
||||
noaccess |
|
||||
noentry |
|
||||
overflow |
|
||||
unsupported |
|
||||
opaque
|
||||
);
|
||||
@ -0,0 +1,34 @@
|
||||
// Concatenates two or more strings. The caller must free the return value.
|
||||
export fn concat(strs: str...) str = {
|
||||
let z = 0z;
|
||||
for (let i = 0z; i < len(strs); i += 1) {
|
||||
z += len(strs[i]);
|
||||
};
|
||||
let new: []u8 = alloc([], z);
|
||||
for (let i = 0z; i < len(strs); i += 1) {
|
||||
append(new, ...toutf8(strs[i]));
|
||||
};
|
||||
return fromutf8_unsafe(new[..z]);
|
||||
};
|
||||
|
||||
@test fn concat() void = {
|
||||
let s = concat("hello ", "world");
|
||||
assert(s == "hello world");
|
||||
free(s);
|
||||
|
||||
s = concat("hello", " ", "world");
|
||||
assert(s == "hello world");
|
||||
free(s);
|
||||
|
||||
s = concat("hello", "", "world");
|
||||
assert(s == "helloworld");
|
||||
free(s);
|
||||
|
||||
s = concat("", "");
|
||||
assert(s == "");
|
||||
free(s);
|
||||
|
||||
s = concat();
|
||||
assert(s == "");
|
||||
free(s);
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
use bytes;
|
||||
use encoding::utf8;
|
||||
|
||||
// Returns true if a string contains a rune or a sub-string.
|
||||
export fn contains(haystack: str, needle: (str | rune)) bool = match (needle) {
|
||||
s: str => bytes::contains(toutf8(haystack), toutf8(s)),
|
||||
r: rune => bytes::contains(toutf8(haystack), utf8::encoderune(r)),
|
||||
};
|
||||
|
||||
@test fn contains() void = {
|
||||
assert(contains("hello world", "hello"));
|
||||
assert(contains("hello world", "world"));
|
||||
assert(contains("hello world", ""));
|
||||
assert(!contains("hello world", "foobar"));
|
||||
};
|
||||
@ -0,0 +1,109 @@
|
||||
use dirs;
|
||||
use fmt;
|
||||
use fs;
|
||||
use hare::ast;
|
||||
use os;
|
||||
use path;
|
||||
use strings;
|
||||
use strio;
|
||||
|
||||
export type context = struct {
|
||||
// Filesystem to use for the cache and source files.
|
||||
fs: *fs::fs,
|
||||
// List of paths to search, generally populated from HAREPATH plus some
|
||||
// baked-in default.
|
||||
paths: []str,
|
||||
// Path to the Hare cache, generally populated from HARECACHE and
|
||||
// defaulting to $XDG_CACHE_HOME/hare.
|
||||
cache: str,
|
||||
// Build tags to apply to this context.
|
||||
tags: []tag,
|
||||
// List of -D arguments passed to harec
|
||||
defines: []str,
|
||||
};
|
||||
|
||||
// Initializes a new context with the system default configuration. The tag list
|
||||
// and list of defines (arguments passed with harec -D) is borrowed from the
|
||||
// caller. The harepath parameter is not borrowed, but it is ignored if HAREPATH
|
||||
// is set in the process environment.
|
||||
export fn context_init(tags: []tag, defs: []str, harepath: str) context = {
|
||||
let ctx = context {
|
||||
fs = os::cwd,
|
||||
tags = tags,
|
||||
defines = defs,
|
||||
paths: []str = match (os::getenv("HAREPATH")) {
|
||||
void => {
|
||||
let path: []str = alloc([
|
||||
strings::dup(harepath),
|
||||
dirs::data("hare"),
|
||||
]);
|
||||
path;
|
||||
},
|
||||
s: str => {
|
||||
let sl = strings::split(s, ":");
|
||||
let path: []str = alloc([], len(sl) + 1);
|
||||
for (let i = 0z; i < len(sl); i += 1) {
|
||||
append(path, strings::dup(sl[i]));
|
||||
};
|
||||
append(path, strings::dup("."));
|
||||
free(sl);
|
||||
path;
|
||||
},
|
||||
},
|
||||
cache: str = match (os::getenv("HARECACHE")) {
|
||||
void => dirs::cache("hare"),
|
||||
s: str => strings::dup(s),
|
||||
},
|
||||
...
|
||||
};
|
||||
return ctx;
|
||||
};
|
||||
|
||||
// Frees resources associated with this context.
|
||||
export fn context_finish(ctx: *context) void = {
|
||||
for (let i = 0z; i < len(ctx.paths); i += 1) {
|
||||
free(ctx.paths[i]);
|
||||
};
|
||||
free(ctx.paths);
|
||||
free(ctx.cache);
|
||||
};
|
||||
|
||||
// Converts an identifier to a partial path (e.g. foo::bar becomes foo/bar). The
|
||||
// return value must be freed by the caller.
|
||||
export fn identpath(name: ast::ident) str = {
|
||||
let p = path::join(name[0]);
|
||||
for (let i = 1z; i < len(name); i += 1) {
|
||||
let q = path::join(p, name[i]);
|
||||
free(p);
|
||||
p = q;
|
||||
};
|
||||
return p;
|
||||
};
|
||||
|
||||
@test fn identpath() void = {
|
||||
let ident: ast::ident = ["foo", "bar", "baz"];
|
||||
let p = identpath(ident);
|
||||
defer free(p);
|
||||
assert(p == "foo/bar/baz");
|
||||
};
|
||||
|
||||
// Joins an ident string with underscores instead of double colons. The return
|
||||
// value must be freed by the caller.
|
||||
//
|
||||
// This is used for module names in environment variables and some file names.
|
||||
export fn identuscore(ident: ast::ident) str = {
|
||||
let buf = strio::dynamic();
|
||||
for (let i = 0z; i < len(ident); i += 1) {
|
||||
fmt::fprintf(buf, "{}{}", ident[i],
|
||||
if (i + 1 < len(ident)) "_"
|
||||
else "") as size;
|
||||
};
|
||||
return strio::finish(buf);
|
||||
};
|
||||
|
||||
@test fn identuscore() void = {
|
||||
let ident: ast::ident = ["foo", "bar", "baz"];
|
||||
let p = identuscore(ident);
|
||||
defer free(p);
|
||||
assert(p == "foo_bar_baz");
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
use errors;
|
||||
|
||||
// Copies data from one stream into another. Note that this function will never
|
||||
// return if the source stream is infinite.
|
||||
export fn copy(dest: *stream, src: *stream) (error | size) = {
|
||||
match (dest.copier) {
|
||||
null => void,
|
||||
c: *copier => match (c(dest, src)) {
|
||||
err: error => match (err) {
|
||||
errors::unsupported => void, // Use fallback
|
||||
* => return err,
|
||||
},
|
||||
s: size => return s,
|
||||
},
|
||||
};
|
||||
|
||||
let w = 0z;
|
||||
static let buf: [4096]u8 = [0...];
|
||||
for (true) {
|
||||
match (read(src, buf[..])?) {
|
||||
n: size => for (let i = 0z; i < n) {
|
||||
let r = write(dest, buf[i..n])?;
|
||||
w += r;
|
||||
i += r;
|
||||
},
|
||||
EOF => break,
|
||||
};
|
||||
};
|
||||
return w;
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
fn c_strlen(cstr: *const char) size = {
|
||||
const ptr = cstr: *[*]u8;
|
||||
let ln = 0z;
|
||||
for (ptr[ln] != 0; ln += 1) void;
|
||||
return ln;
|
||||
};
|
||||
|
||||
fn from_c_unsafe(cstr: *const char) const str = {
|
||||
const l = c_strlen(cstr);
|
||||
const s = struct {
|
||||
data: *[*]u8 = cstr: *[*]u8,
|
||||
length: size = l,
|
||||
capacity: size = l,
|
||||
};
|
||||
return *(&s: *const str);
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
use encoding::utf8;
|
||||
use types;
|
||||
use rt;
|
||||
|
||||
let emptybuf: [1]u8 = [0];
|
||||
|
||||
// A C-compatible empty string. Empty Hare strings have a null pointer instead
|
||||
// of containing only '\0', so a special string is needed for this case.
|
||||
export let c_empty: *const char = &emptybuf: *[*]u8: *const char;
|
||||
|
||||
// Computes the length of a NUL-terminated C string, in octets, in O(n). The
|
||||
// computed length does not include the NUL terminator.
|
||||
export fn cstrlen(cstr: *const char) size = {
|
||||
const ptr = cstr: *[*]u8;
|
||||
let ln = 0z;
|
||||
for (ptr[ln] != 0; ln += 1) void;
|
||||
return ln;
|
||||
};
|
||||
|
||||
// Converts a C string to a Hare string in O(n), and does not check if it's
|
||||
// valid UTF-8.
|
||||
export fn fromc_unsafe(cstr: *const char) const str = {
|
||||
const l = cstrlen(cstr);
|
||||
const s = types::string {
|
||||
data = cstr: *[*]u8,
|
||||
length = l,
|
||||
capacity = l,
|
||||
};
|
||||
return *(&s: *const str);
|
||||
};
|
||||
|
||||
// Converts a C string to a Hare string in O(n). If the string is not valid
|
||||
// UTF-8, abort.
|
||||
export fn fromc(cstr: *const char) const str = {
|
||||
let s = fromc_unsafe(cstr);
|
||||
assert(utf8::valid(s));
|
||||
return s;
|
||||
};
|
||||
|
||||
// Converts a Hare string to a C string. The result is allocated, the caller
|
||||
// must free it when they're done.
|
||||
export fn to_c(s: const str) *char = {
|
||||
let ptr = rt::malloc(len(s) + 1): nullable *[*]u8;
|
||||
let ptr = match (ptr) {
|
||||
null => abort("Out of memory"),
|
||||
p: *[*]u8 => p,
|
||||
};
|
||||
rt::memcpy(ptr, (&s: *types::string).data, len(s));
|
||||
ptr[len(s)] = 0;
|
||||
return ptr: *char;
|
||||
};
|
||||
@ -0,0 +1,102 @@
|
||||
def U: u8 = 0o1;
|
||||
def L: u8 = 0o2;
|
||||
def N: u8 = 0o4;
|
||||
def S: u8 = 0o10;
|
||||
def P: u8 = 0o20;
|
||||
def C: u8 = 0o40;
|
||||
def B: u8 = 0o100;
|
||||
def X: u8 = 0o200;
|
||||
|
||||
// LUT of bitfields with character attributes
|
||||
const cclass: []u8 = [
|
||||
// 0 1 2 3 4 5 6 7
|
||||
C, C, C, C, C, C, C, C, // 0
|
||||
C, S|C, S|C, S|C, S|C, S|C, C, C, // 10
|
||||
C, C, C, C, C, C, C, C, // 20
|
||||
C, C, C, C, C, C, C, C, // 30
|
||||
S|B, P, P, P, P, P, P, P, // 40
|
||||
P, P, P, P, P, P, P, P, // 50
|
||||
N|X, N|X, N|X, N|X, N|X, N|X, N|X, N|X, // 60
|
||||
N|X, N|X, P, P, P, P, P, P, // 70
|
||||
P, U|X, U|X, U|X, U|X, U|X, U|X, U, // 100
|
||||
U, U, U, U, U, U, U, U, // 110
|
||||
U, U, U, U, U, U, U, U, // 120
|
||||
U, U, U, P, P, P, P, P, // 130
|
||||
P, L|X, L|X, L|X, L|X, L|X, L|X, L, // 140
|
||||
L, L, L, L, L, L, L, L, // 150
|
||||
L, L, L, L, L, L, L, L, // 160
|
||||
L, L, L, P, P, P, P, C, // 170
|
||||
];
|
||||
|
||||
// True if an ASCII character is a letter
|
||||
export fn isalpha(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&(U|L) > 0;
|
||||
|
||||
// True if an ASCII character is uppercase
|
||||
export fn isupper(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&U > 0;
|
||||
|
||||
// True if an ASCII character is lowercase
|
||||
export fn islower(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&L > 0;
|
||||
|
||||
// True if an ASCII character is a digit
|
||||
export fn isdigit(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&N > 0;
|
||||
|
||||
// True if an ASCII character is a hexadecimal digit
|
||||
export fn isxdigit(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&X > 0;
|
||||
|
||||
// True if an ASCII character is a space.
|
||||
export fn isspace(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&S > 0;
|
||||
|
||||
// True if an ASCII character is punctuation.
|
||||
export fn ispunct(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&P > 0;
|
||||
|
||||
// True if an ASCII character is alphanumeric.
|
||||
export fn isalnum(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&(U|L|N) > 0;
|
||||
|
||||
// True if an ASCII character is printable.
|
||||
export fn isprint(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&(P|U|L|N|B) > 0;
|
||||
|
||||
// True if an ASCII character is any printable character other than space.
|
||||
export fn isgraph(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&(P|U|L|N) > 0;
|
||||
|
||||
// True if an ASCII character is a control character.
|
||||
export fn iscntrl(c: rune) bool =
|
||||
if (!isascii(c)) false else cclass[c: u32]&C > 0;
|
||||
|
||||
// True if a rune is a valid ASCII character.
|
||||
export fn isascii(c: rune) bool = c: u32 <= 0o177;
|
||||
|
||||
// Returns the uppercase form of an ASCII character, or the original character
|
||||
// if it was not a lowercase letter.
|
||||
export fn toupper(c: rune) rune = {
|
||||
return if (islower(c)) {
|
||||
(c: u32 - ('a': u32) + ('A': u32)): rune;
|
||||
} else c;
|
||||
};
|
||||
|
||||
// Returns the lowercase form of an ASCII character, or the original character
|
||||
// if it was not an uppercase letter.
|
||||
export fn tolower(c: rune) rune = {
|
||||
return if (isupper(c)) {
|
||||
(c: u32 - ('A': u32) + ('a': u32)): rune;
|
||||
} else c;
|
||||
};
|
||||
|
||||
@test fn ctype() void = {
|
||||
// Just some simple tests
|
||||
assert(isspace(' ') && !isspace('x') && !isspace('こ'));
|
||||
assert(isalnum('a') && isalnum('8') && !isalnum('こ'));
|
||||
assert(!ispunct('\0') && iscntrl('\b'));
|
||||
assert(isascii('a') && isascii('\0') && isascii('\x7F'));
|
||||
assert(!isascii('\x80') && !isascii('こ'));
|
||||
assert(tolower('A') == 'a' && tolower('こ') == 'こ');
|
||||
};
|
||||
@ -0,0 +1,192 @@
|
||||
use io;
|
||||
use fmt;
|
||||
use hare::ast;
|
||||
use hare::lex;
|
||||
use strio;
|
||||
|
||||
export fn decl(out: *io::stream, d: ast::decl) (size | io::error) = {
|
||||
let n = 0z;
|
||||
if (d.exported) {
|
||||
n += fmt::fprint(out, "export ")?;
|
||||
};
|
||||
match (d.decl) {
|
||||
g: []ast::decl_global => {
|
||||
n += fmt::fprint(out,
|
||||
if (g[0].is_const) "def " else "let ")?;
|
||||
for (let i = 0z; i < len(g); i += 1) {
|
||||
if (len(g[i].symbol) != 0) {
|
||||
n += fmt::fprintf(out,
|
||||
"@symbol(\"{}\") ", g[i].symbol)?;
|
||||
};
|
||||
n += ident(out, g[i].ident)?;
|
||||
n += fmt::fprint(out, ": ")?;
|
||||
n += _type(out, 0, g[i]._type)?;
|
||||
n += fmt::fprint(out, " = ")?;
|
||||
n += expr(out, 0, g[i].init)?;
|
||||
if (i + 1 < len(g)) {
|
||||
n += fmt::fprint(out, ", ")?;
|
||||
};
|
||||
};
|
||||
},
|
||||
t: []ast::decl_type => {
|
||||
n += fmt::fprint(out, "type ")?;
|
||||
for (let i = 0z; i < len(t); i += 1) {
|
||||
n += ident(out, t[i].ident)?;
|
||||
n += fmt::fprint(out, " = ")?;
|
||||
n += _type(out, 0, t[i]._type)?;
|
||||
if (i + 1 < len(t)) {
|
||||
n += fmt::fprint(out, ", ")?;
|
||||
};
|
||||
};
|
||||
},
|
||||
f: ast::decl_func => {
|
||||
n += fmt::fprint(out, switch (f.attrs) {
|
||||
ast::fndecl_attrs::NONE => "",
|
||||
ast::fndecl_attrs::FINI => "@fini ",
|
||||
ast::fndecl_attrs::INIT => "@init ",
|
||||
ast::fndecl_attrs::TEST => "@test ",
|
||||
})?;
|
||||
let p = f.prototype._type as ast::func_type;
|
||||
if (p.attrs & ast::func_attrs::NORETURN != 0) {
|
||||
n += fmt::fprint(out, "@noreturn ")?;
|
||||
};
|
||||
if (len(f.symbol) != 0) {
|
||||
n += fmt::fprintf(out, "@symbol(\"{}\") ",
|
||||
f.symbol)?;
|
||||
};
|
||||
n += fmt::fprint(out, "fn ")?;
|
||||
n += ident(out, f.ident)?;
|
||||
n += prototype(out, 0,
|
||||
f.prototype._type as ast::func_type)?;
|
||||
match (f.body) {
|
||||
void => void,
|
||||
e: ast::expr => {
|
||||
n += fmt::fprint(out, " = ")?;
|
||||
n += expr(out, 0, e)?;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
n += fmt::fprint(out, ";")?;
|
||||
return n;
|
||||
};
|
||||
|
||||
fn decl_test(d: ast::decl, expected: str) bool = {
|
||||
let buf = strio::dynamic();
|
||||
decl(buf, d) as size;
|
||||
let s = strio::finish(buf);
|
||||
defer free(s);
|
||||
return s == expected;
|
||||
};
|
||||
|
||||
@test fn decl() void = {
|
||||
let loc = lex::location {
|
||||
path = "<test>",
|
||||
line = 0,
|
||||
col = 0,
|
||||
};
|
||||
let type_int = ast::_type {
|
||||
loc = loc,
|
||||
flags = 0,
|
||||
_type = ast::builtin_type::INT,
|
||||
};
|
||||
let type_fn = ast::_type {
|
||||
loc = loc,
|
||||
flags = ast::type_flags::CONST,
|
||||
_type = ast::func_type {
|
||||
result = &type_int,
|
||||
attrs = ast::func_attrs::NORETURN,
|
||||
variadism = ast::variadism::HARE,
|
||||
params = [
|
||||
ast::func_param {
|
||||
loc = loc,
|
||||
name = "foo",
|
||||
_type = &type_int,
|
||||
},
|
||||
ast::func_param {
|
||||
loc = loc,
|
||||
name = "bar",
|
||||
_type = &type_int,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
let expr_void = void: ast::constant_expr: ast::expr;
|
||||
|
||||
let d = ast::decl {
|
||||
loc = loc,
|
||||
exported = false,
|
||||
decl = [
|
||||
ast::decl_global {
|
||||
is_const = false,
|
||||
symbol = "",
|
||||
ident = ["foo", "bar"],
|
||||
_type = type_int,
|
||||
init = expr_void,
|
||||
},
|
||||
ast::decl_global {
|
||||
is_const = false,
|
||||
symbol = "foobar",
|
||||
ident = ["baz"],
|
||||
_type = type_int,
|
||||
init = expr_void,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert(decl_test(d, "let foo::bar: int = void, @symbol(\"foobar\") baz: int = void;"));
|
||||
|
||||
d.exported = true;
|
||||
d.decl = [
|
||||
ast::decl_global {
|
||||
is_const = true,
|
||||
ident = ["foo"],
|
||||
_type = type_int,
|
||||
init = expr_void,
|
||||
...
|
||||
},
|
||||
];
|
||||
assert(decl_test(d, "export def foo: int = void;"));
|
||||
|
||||
d.exported = false;
|
||||
d.decl = [
|
||||
ast::decl_type {
|
||||
ident = ["foo"],
|
||||
_type = type_int,
|
||||
},
|
||||
ast::decl_type {
|
||||
ident = ["bar"],
|
||||
_type = type_int,
|
||||
},
|
||||
];
|
||||
assert(decl_test(d, "type foo = int, bar = int;"));
|
||||
|
||||
d.decl = ast::decl_func {
|
||||
symbol = "foo",
|
||||
ident = ["foo"],
|
||||
prototype = type_fn,
|
||||
body = void,
|
||||
attrs = ast::fndecl_attrs::FINI,
|
||||
};
|
||||
assert(decl_test(d, "@fini @noreturn @symbol(\"foo\") fn foo(foo: int, bar: int...) int;"));
|
||||
|
||||
type_fn._type = ast::func_type {
|
||||
result = &type_int,
|
||||
attrs = 0,
|
||||
variadism = ast::variadism::NONE,
|
||||
params = [
|
||||
ast::func_param {
|
||||
loc = loc,
|
||||
name = "",
|
||||
_type = &type_int,
|
||||
},
|
||||
],
|
||||
};
|
||||
d.decl = ast::decl_func {
|
||||
symbol = "",
|
||||
ident = ["foo"],
|
||||
prototype = type_fn,
|
||||
body = expr_void,
|
||||
attrs = 0,
|
||||
};
|
||||
assert(decl_test(d, "fn foo(_: int) int = void;"));
|
||||
};
|
||||
@ -0,0 +1,151 @@
|
||||
use types;
|
||||
|
||||
fn toutf8(in: str) []u8 = *(&in: *[]u8);
|
||||
|
||||
// The state for the UTF-8 decoder.
|
||||
export type decoder = struct {
|
||||
offs: size,
|
||||
src: []u8,
|
||||
};
|
||||
|
||||
// Initializes a new UTF-8 decoder.
|
||||
export fn decode(src: (str | []u8)) decoder = match (src) {
|
||||
s: str => decoder { src = toutf8(s), ... },
|
||||
b: []u8 => decoder { src = b, ... },
|
||||
};
|
||||
|
||||
// Indicates that more data is needed, i.e. that a partial UTF-8 sequence was
|
||||
// encountered.
|
||||
export type more = void;
|
||||
|
||||
// An error indicating that an invalid UTF-8 sequence was found.
|
||||
export type invalid = void!;
|
||||
|
||||
// Returns the next rune from a decoder. If the slice ends with a complete UTF-8
|
||||
// sequence, void is returned. If an incomplete sequence is encountered, more is
|
||||
// returned. And if an invalid sequence is encountered, invalid is returned.
|
||||
export fn next(d: *decoder) (rune | void | more | invalid) = {
|
||||
assert(d.offs <= len(d.src));
|
||||
if (d.offs == len(d.src)) {
|
||||
return;
|
||||
};
|
||||
|
||||
// XXX: It would be faster if we decoded and measured at the same time.
|
||||
const n = utf8sz(d.src[d.offs]);
|
||||
if (n == types::SIZE_MAX) {
|
||||
return invalid;
|
||||
} else if (d.offs + n > len(d.src)) {
|
||||
return more;
|
||||
};
|
||||
let bytes = d.src[d.offs..d.offs+n];
|
||||
d.offs += n;
|
||||
|
||||
let r = 0u32;
|
||||
if (bytes[0] < 128) {
|
||||
// ASCII
|
||||
return bytes[0]: u32: rune;
|
||||
};
|
||||
|
||||
const mask = masks[n - 1];
|
||||
r = bytes[0] & mask;
|
||||
for (let i = 1z; i < len(bytes); i += 1) {
|
||||
r <<= 6;
|
||||
r |= bytes[i] & 0x3F;
|
||||
};
|
||||
return r: rune;
|
||||
};
|
||||
|
||||
// Returns the previous rune from a decoder. If the slice starts with a complete UTF-8
|
||||
// sequence, void is returned. If an incomplete sequence is encountered, more is
|
||||
// returned. And if an invalid sequence is encountered, invalid is returned.
|
||||
export fn prev(d: *decoder) (rune | void | more | invalid) = {
|
||||
if (d.offs == 0) {
|
||||
return;
|
||||
};
|
||||
|
||||
let n = 0z;
|
||||
let r = 0u32;
|
||||
|
||||
for (let i = 0z; i < d.offs; i += 1) {
|
||||
if ((d.src[d.offs - i - 1] & 0xC0) == 0x80) {
|
||||
let tmp: u32 = d.src[d.offs - i - 1] & 0x3F;
|
||||
r |= tmp << (i * 6): u32;
|
||||
} else {
|
||||
n = i + 1;
|
||||
let tmp: u32 = d.src[d.offs - i - 1] & masks[i];
|
||||
r |= tmp << (i * 6): u32;
|
||||
break;
|
||||
};
|
||||
};
|
||||
if (n == 0) {
|
||||
return more;
|
||||
};
|
||||
d.offs -= n;
|
||||
if (n != utf8sz(d.src[d.offs])) {
|
||||
return invalid;
|
||||
};
|
||||
return r: rune;
|
||||
};
|
||||
|
||||
@test fn decode() void = {
|
||||
const input: [_]u8 = [
|
||||
0xE3, 0x81, 0x93, 0xE3, 0x82, 0x93, 0xE3, 0x81,
|
||||
0xAB, 0xE3, 0x81, 0xA1, 0xE3, 0x81, 0xAF, 0x00,
|
||||
];
|
||||
const expected = ['こ', 'ん', 'に', 'ち', 'は', '\0'];
|
||||
let decoder = decode(input);
|
||||
for (let i = 0z; i < len(expected); i += 1) {
|
||||
match (next(&decoder)) {
|
||||
(invalid | more | void) => abort(),
|
||||
r: rune => assert(r == expected[i]),
|
||||
};
|
||||
};
|
||||
assert(next(&decoder) is void);
|
||||
assert(decoder.offs == len(decoder.src));
|
||||
for (let i = 0z; i < len(expected); i += 1) {
|
||||
match (prev(&decoder)) {
|
||||
(invalid | more | void) => abort(),
|
||||
r: rune => assert(r == expected[len(expected) - i - 1]),
|
||||
};
|
||||
};
|
||||
assert(prev(&decoder) is void);
|
||||
|
||||
// TODO: Test more invalid sequences
|
||||
const invalid: [_]u8 = [0xA0, 0xA1];
|
||||
decoder = decode(invalid);
|
||||
assert(next(&decoder) is invalid);
|
||||
decoder.offs = 2;
|
||||
assert(prev(&decoder) is more);
|
||||
|
||||
const incomplete: [_]u8 = [0xE3, 0x81];
|
||||
decoder = decode(incomplete);
|
||||
assert(next(&decoder) is more);
|
||||
decoder.offs = 2;
|
||||
assert(prev(&decoder) is invalid);
|
||||
};
|
||||
|
||||
// Returns true if a given string or byte slice contains only valid UTF-8
|
||||
// sequences. Note that Hare strings (str) are always valid UTF-8 - if this
|
||||
// returns false for a str type, something funny is going on.
|
||||
export fn valid(src: (str | []u8)) bool = {
|
||||
let decoder = decode(src);
|
||||
for (true) {
|
||||
match (next(&decoder)) {
|
||||
void => return true,
|
||||
invalid => return false,
|
||||
more => return false,
|
||||
rune => void,
|
||||
};
|
||||
};
|
||||
abort();
|
||||
};
|
||||
|
||||
// Returns the expected length of a UTF-8 character in bytes.
|
||||
export fn utf8sz(c: u8) size = {
|
||||
for (let i = 0z; i < len(sizes); i += 1) {
|
||||
if (c & sizes[i].mask == sizes[i].result) {
|
||||
return sizes[i].octets;
|
||||
};
|
||||
};
|
||||
return types::SIZE_MAX;
|
||||
};
|
||||
@ -0,0 +1,399 @@
|
||||
use bytes;
|
||||
use errors;
|
||||
use encoding::utf8;
|
||||
use fs;
|
||||
use io;
|
||||
use path;
|
||||
use rt;
|
||||
use strings;
|
||||
use time;
|
||||
|
||||
// Controls how symlinks are followed (or not) in a dirfd filesystem. Support
|
||||
// for this feature varies, you should gate usage of this enum behind a build
|
||||
// tag.
|
||||
//
|
||||
// Note that on Linux, specifying BENEATH or IN_ROOT will also disable magic
|
||||
// symlinks.
|
||||
export type resolve_flags = enum {
|
||||
NORMAL,
|
||||
|
||||
// Does not allow symlink resolution to occur for any symlinks which
|
||||
// would refer to any anscestor of the fd directory. This disables all
|
||||
// absolute symlinks, and any call to open or create with an absolute
|
||||
// path.
|
||||
BENEATH,
|
||||
|
||||
// Treat the directory fd as the root directory. This affects
|
||||
// open/create for absolute paths, as well as absolute path resolution
|
||||
// of symlinks. The effects are similar to chroot.
|
||||
IN_ROOT,
|
||||
|
||||
// Disables symlink resolution entirely.
|
||||
NO_SYMLINKS,
|
||||
|
||||
// Disallows traversal of mountpoints during path resolution. This is
|
||||
// not recommended for general use, as bind mounts are extensively used
|
||||
// on many systems.
|
||||
NO_XDEV,
|
||||
};
|
||||
|
||||
type os_filesystem = struct {
|
||||
fs: fs::fs,
|
||||
dirfd: int,
|
||||
resolve: resolve_flags,
|
||||
};
|
||||
|
||||
fn static_dirfdopen(fd: int, filesystem: *os_filesystem) *fs::fs = {
|
||||
*filesystem = os_filesystem {
|
||||
fs = fs::fs {
|
||||
open = &fs_open,
|
||||
create = &fs_create,
|
||||
remove = &fs_remove,
|
||||
iter = &fs_iter,
|
||||
stat = &fs_stat,
|
||||
subdir = &fs_subdir,
|
||||
mkdir = &fs_mkdir,
|
||||
rmdir = &fs_rmdir,
|
||||
chmod = &fs_chmod,
|
||||
chown = &fs_chown,
|
||||
resolve = &fs_resolve,
|
||||
...
|
||||
},
|
||||
dirfd = fd,
|
||||
};
|
||||
return &filesystem.fs;
|
||||
};
|
||||
|
||||
// Opens a file descriptor as an [fs::fs]. This file descriptor must be a
|
||||
// directory file. The file will be closed when the fs is closed.
|
||||
//
|
||||
// If no other flags are provided to [fs::open] and [fs::create] when used with
|
||||
// a dirfdfs, [fs::flags::NOCTTY] and [fs::flags::CLOEXEC] are used when opening
|
||||
// the file. If you pass your own flags, it is recommended that you add these
|
||||
// unless you know that you do not want them.
|
||||
export fn dirfdopen(fd: int, resolve: resolve_flags...) *fs::fs = {
|
||||
let ofs = alloc(os_filesystem { ... });
|
||||
let fs = static_dirfdopen(fd, ofs);
|
||||
for (let i = 0z; i < len(resolve); i += 1) {
|
||||
ofs.resolve |= resolve[i];
|
||||
};
|
||||
fs.close = &fs_close;
|
||||
return fs;
|
||||
};
|
||||
|
||||
// Clones a dirfd filesystem, optionally adding additional [resolve_flags]
|
||||
// constraints.
|
||||
export fn dirfs_clone(fs: *fs::fs, resolve: resolve_flags...) *fs::fs = {
|
||||
assert(fs.open == &fs_open);
|
||||
let fs = fs: *os_filesystem;
|
||||
let new = alloc(*fs);
|
||||
for (let i = 0z; i < len(resolve); i += 1) {
|
||||
new.resolve |= resolve[i];
|
||||
};
|
||||
new.dirfd = rt::dup(new.dirfd) as int;
|
||||
return &new.fs;
|
||||
};
|
||||
|
||||
fn errno_to_fs(err: rt::errno) fs::error = switch (err) {
|
||||
rt::ENOENT => errors::noentry,
|
||||
rt::EEXIST => errors::exists,
|
||||
rt::EACCES => errors::noaccess,
|
||||
rt::EBUSY => errors::busy,
|
||||
rt::ENOTDIR => fs::wrongtype,
|
||||
rt::EOPNOTSUPP, rt::ENOSYS => errors::unsupported,
|
||||
* => errors::errno(err),
|
||||
};
|
||||
|
||||
fn _fs_open(
|
||||
fs: *fs::fs,
|
||||
path: str,
|
||||
mode: io::mode,
|
||||
oh: *rt::open_how,
|
||||
) (*io::stream | fs::error) = {
|
||||
let fs = fs: *os_filesystem;
|
||||
|
||||
oh.resolve = 0u64;
|
||||
if (fs.resolve & resolve_flags::BENEATH == resolve_flags::BENEATH) {
|
||||
oh.resolve |= rt::RESOLVE_BENEATH | rt::RESOLVE_NO_MAGICLINKS;
|
||||
};
|
||||
if (fs.resolve & resolve_flags::IN_ROOT == resolve_flags::IN_ROOT) {
|
||||
oh.resolve |= rt::RESOLVE_IN_ROOT | rt::RESOLVE_NO_MAGICLINKS;
|
||||
};
|
||||
if (fs.resolve & resolve_flags::NO_SYMLINKS == resolve_flags::NO_SYMLINKS) {
|
||||
oh.resolve |= rt::RESOLVE_NO_SYMLINKS;
|
||||
};
|
||||
if (fs.resolve & resolve_flags::NO_XDEV == resolve_flags::NO_XDEV) {
|
||||
oh.resolve |= rt::RESOLVE_NO_XDEV;
|
||||
};
|
||||
|
||||
let fd = match (rt::openat2(fs.dirfd, path, oh, size(rt::open_how))) {
|
||||
err: rt::errno => return errno_to_fs(err),
|
||||
fd: int => fd,
|
||||
};
|
||||
|
||||
return fdopen(fd, path, mode);
|
||||
};
|
||||
|
||||
fn fs_open(
|
||||
fs: *fs::fs,
|
||||
path: str,
|
||||
flags: fs::flags...
|
||||
) (*io::stream | fs::error) = {
|
||||
let oflags = 0;
|
||||
let iomode = io::mode::NONE;
|
||||
if (len(flags) == 0z) {
|
||||
oflags |= (fs::flags::NOCTTY
|
||||
| fs::flags::CLOEXEC
|
||||
| fs::flags::RDONLY): int;
|
||||
};
|
||||
for (let i = 0z; i < len(flags); i += 1z) {
|
||||
oflags |= flags[i]: int;
|
||||
};
|
||||
|
||||
if ((oflags: fs::flags & fs::flags::DIRECTORY) == fs::flags::DIRECTORY) {
|
||||
// This is arch-specific
|
||||
oflags &= ~fs::flags::DIRECTORY: int;
|
||||
oflags |= rt::O_DIRECTORY: int;
|
||||
};
|
||||
|
||||
if ((oflags: fs::flags & fs::flags::RDWR) == fs::flags::RDWR) {
|
||||
iomode = io::mode::RDWR;
|
||||
} else if ((oflags: fs::flags & fs::flags::WRONLY) == fs::flags::WRONLY) {
|
||||
iomode = io::mode::WRITE;
|
||||
} else if ((oflags: fs::flags & fs::flags::PATH) != fs::flags::PATH) {
|
||||
iomode = io::mode::READ;
|
||||
};
|
||||
|
||||
let oh = rt::open_how {
|
||||
flags = oflags: u64,
|
||||
...
|
||||
};
|
||||
return _fs_open(fs, path, iomode, &oh);
|
||||
};
|
||||
|
||||
fn fs_create(
|
||||
fs: *fs::fs,
|
||||
path: str,
|
||||
mode: fs::mode,
|
||||
flags: fs::flags...
|
||||
) (*io::stream | fs::error) = {
|
||||
let oflags = 0;
|
||||
let iomode = io::mode::NONE;
|
||||
if (len(flags) == 0z) {
|
||||
oflags |= (fs::flags::NOCTTY
|
||||
| fs::flags::CLOEXEC
|
||||
| fs::flags::WRONLY): int;
|
||||
};
|
||||
for (let i = 0z; i < len(flags); i += 1z) {
|
||||
oflags |= flags[i]: int;
|
||||
};
|
||||
oflags |= fs::flags::CREATE: int;
|
||||
|
||||
if ((oflags: fs::flags & fs::flags::RDWR) == fs::flags::RDWR) {
|
||||
iomode = io::mode::RDWR;
|
||||
} else if ((oflags: fs::flags & fs::flags::WRONLY) == fs::flags::WRONLY) {
|
||||
iomode = io::mode::WRITE;
|
||||
} else if ((oflags: fs::flags & fs::flags::PATH) != fs::flags::PATH) {
|
||||
iomode = io::mode::READ;
|
||||
};
|
||||
|
||||
let oh = rt::open_how {
|
||||
flags = oflags: u64,
|
||||
mode = mode: u64,
|
||||
...
|
||||
};
|
||||
return _fs_open(fs, path, iomode, &oh);
|
||||
};
|
||||
|
||||
fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {
|
||||
let fs = fs: *os_filesystem;
|
||||
match (rt::unlinkat(fs.dirfd, path, 0)) {
|
||||
err: rt::errno => return errno_to_fs(err),
|
||||
void => void,
|
||||
};
|
||||
};
|
||||
|
||||
fn fs_stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = {
|
||||
let fs = fs: *os_filesystem;
|
||||
let st = rt::st { ... };
|
||||
match (rt::fstatat(fs.dirfd, path, &st, rt::AT_SYMLINK_NOFOLLOW)) {
|
||||
err: rt::errno => return errno_to_fs(err),
|
||||
void => void,
|
||||
};
|
||||
return fs::filestat {
|
||||
mask = fs::stat_mask::UID
|
||||
| fs::stat_mask::GID
|
||||
| fs::stat_mask::SIZE
|
||||
| fs::stat_mask::INODE
|
||||
| fs::stat_mask::ATIME
|
||||
| fs::stat_mask::MTIME
|
||||
| fs::stat_mask::CTIME,
|
||||
mode = st.mode: fs::mode,
|
||||
uid = st.uid,
|
||||
uid = st.gid,
|
||||
sz = st.sz,
|
||||
inode = st.ino,
|
||||
atime = time::instant {
|
||||
sec = st.atime.tv_sec,
|
||||
nsec = st.atime.tv_nsec,
|
||||
},
|
||||
mtime = time::instant {
|
||||
sec = st.mtime.tv_sec,
|
||||
nsec = st.mtime.tv_nsec,
|
||||
},
|
||||
ctime = time::instant {
|
||||
sec = st.ctime.tv_sec,
|
||||
nsec = st.ctime.tv_nsec,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
fn fs_subdir(fs: *fs::fs, path: str) (*fs::fs | fs::error) = {
|
||||
let fs = fs: *os_filesystem;
|
||||
let oh = rt::open_how {
|
||||
flags = (rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY): u64,
|
||||
...
|
||||
};
|
||||
|
||||
let fd: int = match (rt::openat2(fs.dirfd, path,
|
||||
&oh, size(rt::open_how))) {
|
||||
err: rt::errno => return errno_to_fs(err),
|
||||
n: int => n,
|
||||
};
|
||||
|
||||
return dirfdopen(fd);
|
||||
};
|
||||
|
||||
fn fs_rmdir(fs: *fs::fs, path: str) (void | fs::error) = {
|
||||
let fs = fs: *os_filesystem;
|
||||
match (rt::unlinkat(fs.dirfd, path, rt::AT_REMOVEDIR)) {
|
||||
err: rt::errno => return errno_to_fs(err),
|
||||
void => void,
|
||||
};
|
||||
};
|
||||
|
||||
fn fs_mkdir(fs: *fs::fs, path: str) (void | fs::error) = {
|
||||
let fs = fs: *os_filesystem;
|
||||
return match (rt::mkdirat(fs.dirfd, path, 0o755)) {
|
||||
err: rt::errno => errno_to_fs(err),
|
||||
void => void,
|
||||
};
|
||||
};
|
||||
|
||||
fn fs_chmod(fs: *fs::fs, path: str, mode: fs::mode) (void | fs::error) = {
|
||||
let fs = fs: *os_filesystem;
|
||||
return match (rt::fchmodat(fs.dirfd, path, mode: uint)) {
|
||||
err: rt::errno => return errno_to_fs(err),
|
||||
void => void,
|
||||
};
|
||||
};
|
||||
|
||||
fn fs_chown(fs: *fs::fs, path: str, uid: uint, gid: uint) (void | fs::error) = {
|
||||
let fs = fs: *os_filesystem;
|
||||
return match (rt::fchownat(fs.dirfd, path, uid, gid)) {
|
||||
err: rt::errno => return errno_to_fs(err),
|
||||
void => void,
|
||||
};
|
||||
};
|
||||
|
||||
fn resolve_part(parts: *[]str, part: str) void = {
|
||||
if (part == ".") {
|
||||
// no-op
|
||||
void;
|
||||
} else if (part == "..") {
|
||||
// XXX: We should not have to dereference this
|
||||
if (len(*parts) != 0) {
|
||||
delete(parts[len(*parts) - 1]);
|
||||
};
|
||||
} else {
|
||||
append(*parts, part);
|
||||
};
|
||||
};
|
||||
|
||||
fn fs_resolve(fs: *fs::fs, path: str) str = {
|
||||
let parts: []str = [];
|
||||
if (!path::abs(path)) {
|
||||
let iter = path::iter(getcwd());
|
||||
for (true) match (path::next(&iter)) {
|
||||
void => break,
|
||||
p: str => resolve_part(&parts, p),
|
||||
};
|
||||
};
|
||||
let iter = path::iter(path);
|
||||
for (true) match (path::next(&iter)) {
|
||||
void => break,
|
||||
p: str => resolve_part(&parts, p),
|
||||
};
|
||||
return path::join(parts...);
|
||||
};
|
||||
|
||||
fn fs_close(fs: *fs::fs) void = {
|
||||
let fs = fs: *os_filesystem;
|
||||
rt::close(fs.dirfd);
|
||||
};
|
||||
|
||||
def BUFSIZ: size = 2048;
|
||||
|
||||
// Based on musl's readdir
|
||||
type os_iterator = struct {
|
||||
iter: fs::iterator,
|
||||
fd: int,
|
||||
buf_pos: size,
|
||||
buf_end: size,
|
||||
buf: [BUFSIZ]u8,
|
||||
};
|
||||
|
||||
fn fs_iter(fs: *fs::fs, path: str) (*fs::iterator | fs::error) = {
|
||||
let fs = fs: *os_filesystem;
|
||||
let oh = rt::open_how {
|
||||
flags = (rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY): u64,
|
||||
...
|
||||
};
|
||||
let fd: int = match (rt::openat2(fs.dirfd, path,
|
||||
&oh, size(rt::open_how))) {
|
||||
err: rt::errno => return errno_to_fs(err),
|
||||
n: int => n,
|
||||
};
|
||||
|
||||
let iter = alloc(os_iterator {
|
||||
iter = fs::iterator {
|
||||
next = &iter_next,
|
||||
},
|
||||
fd = fd,
|
||||
...
|
||||
});
|
||||
return &iter.iter;
|
||||
};
|
||||
|
||||
fn iter_next(iter: *fs::iterator) (fs::dirent | void) = {
|
||||
let iter = iter: *os_iterator;
|
||||
if (iter.buf_pos >= iter.buf_end) {
|
||||
let n = rt::getdents64(iter.fd, &iter.buf, BUFSIZ) as size;
|
||||
if (n == 0) {
|
||||
rt::close(iter.fd);
|
||||
free(iter);
|
||||
return;
|
||||
};
|
||||
iter.buf_end = n;
|
||||
iter.buf_pos = 0;
|
||||
};
|
||||
let de = &iter.buf[iter.buf_pos]: *rt::dirent64;
|
||||
iter.buf_pos += de.d_reclen;
|
||||
let name = strings::fromc(&de.d_name: *const char);
|
||||
|
||||
let ftype: fs::mode = switch (de.d_type) {
|
||||
rt::DT_UNKNOWN => fs::mode::UNKNOWN,
|
||||
rt::DT_FIFO => fs::mode::FIFO,
|
||||
rt::DT_CHR => fs::mode::CHR,
|
||||
rt::DT_DIR => fs::mode::DIR,
|
||||
rt::DT_BLK => fs::mode::BLK,
|
||||
rt::DT_REG => fs::mode::REG,
|
||||
rt::DT_LNK => fs::mode::LINK,
|
||||
rt::DT_SOCK => fs::mode::SOCK,
|
||||
* => fs::mode::UNKNOWN,
|
||||
};
|
||||
return fs::dirent {
|
||||
name = name,
|
||||
ftype = ftype,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
// The slice module provides some utility functions for working with slices. In
|
||||
// order to work with a user-supplied slice of an arbitrary type, the slice must
|
||||
// be cast to []void and the size of the member type passed alongside it. These
|
||||
// functions provide support code for common operations such as indexing and
|
||||
// appending, which are normally provided by language features, but which are
|
||||
// not available for []void slices.
|
||||
@ -0,0 +1,41 @@
|
||||
use bytes;
|
||||
use rt;
|
||||
use types;
|
||||
|
||||
// Duplicates a string. Aborts on allocation failure.
|
||||
export fn dup(s: const str) str = {
|
||||
const in = &s: *types::string;
|
||||
const id = match (in.data) {
|
||||
null => return "", // Empty string
|
||||
b: *[*]u8 => b,
|
||||
};
|
||||
let buf: *[*]u8 = match (rt::malloc(in.length + 1)) {
|
||||
null => abort("Out of memory"),
|
||||
v: *void => v,
|
||||
};
|
||||
bytes::copy(buf[..in.length + 1z], id[..in.length + 1]);
|
||||
let out = types::string {
|
||||
data = buf,
|
||||
length = in.length,
|
||||
capacity = in.length,
|
||||
};
|
||||
return *(&out: *str);
|
||||
};
|
||||
|
||||
// Duplicates every string of a slice in place, returning the same slice with
|
||||
// new strings.
|
||||
export fn dupall(s: []str) void = {
|
||||
for (let i = 0z; i < len(s); i += 1) {
|
||||
s[i] = strings::dup(s[i]);
|
||||
};
|
||||
};
|
||||
|
||||
@test fn dup() void = {
|
||||
let s = dup("");
|
||||
assert(s == "");
|
||||
free(s);
|
||||
|
||||
s = dup("hello");
|
||||
assert(s == "hello");
|
||||
free(s);
|
||||
};
|
||||
@ -0,0 +1,192 @@
|
||||
use bytes;
|
||||
use errors;
|
||||
use io;
|
||||
|
||||
type dynamic_stream = struct {
|
||||
stream: io::stream,
|
||||
buf: []u8,
|
||||
pos: size,
|
||||
};
|
||||
|
||||
// Creates an [io::stream] which dynamically allocates a buffer to store writes
|
||||
// into. Subsequent reads will consume the buffered data. Upon failure to
|
||||
// allocate sufficient memory to store writes, the program aborts.
|
||||
//
|
||||
// Calling [io::close] on this stream will free the buffer. Call [bufio::finish]
|
||||
// instead to free up resources associated with the stream, but transfer
|
||||
// ownership of the buffer to the caller.
|
||||
export fn dynamic(mode: io::mode) *io::stream = dynamic_from([], mode);
|
||||
|
||||
// Like [dynamic], but takes an existing slice as input. Writes are appended to
|
||||
// it and reads consume bytes from the initial buffer, plus any additional
|
||||
// writes. Like [dynamic], calling [io::close] will free the buffer, and
|
||||
// [bufio::finish] can be used to return ownership of the buffer to the caller.
|
||||
export fn dynamic_from(in: []u8, mode: io::mode) *io::stream = {
|
||||
let s = alloc(dynamic_stream {
|
||||
stream = io::stream {
|
||||
closer = &dynamic_close,
|
||||
seeker = &dynamic_seek,
|
||||
...
|
||||
},
|
||||
buf = in,
|
||||
pos = 0,
|
||||
}): *io::stream;
|
||||
if (mode & io::mode::READ == io::mode::READ) {
|
||||
s.reader = &dynamic_read;
|
||||
};
|
||||
if (mode & io::mode::WRITE == io::mode::WRITE) {
|
||||
s.writer = &dynamic_write;
|
||||
};
|
||||
return s;
|
||||
};
|
||||
|
||||
fn dynamic_write(s: *io::stream, buf: const []u8) (size | io::error) = {
|
||||
let s = s: *dynamic_stream;
|
||||
if (s.pos == len(s.buf)) {
|
||||
append(s.buf, ...buf);
|
||||
} else {
|
||||
// TODO: update this after we add insert
|
||||
let new: []u8 = alloc([], len(s.buf) + len(buf));
|
||||
append(new, ...s.buf[..s.pos]);
|
||||
append(new, ...buf[..]);
|
||||
append(new, ...s.buf[s.pos..]);
|
||||
free(s.buf);
|
||||
s.buf = new;
|
||||
};
|
||||
|
||||
s.pos += len(buf);
|
||||
return len(buf);
|
||||
};
|
||||
|
||||
fn dynamic_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
|
||||
let s = s: *dynamic_stream;
|
||||
if (len(s.buf) == s.pos && len(buf) != 0) {
|
||||
return io::EOF;
|
||||
};
|
||||
const n = if (len(s.buf) - s.pos < len(buf)) {
|
||||
len(s.buf) - s.pos;
|
||||
} else {
|
||||
len(buf);
|
||||
};
|
||||
buf[..n] = s.buf[s.pos..s.pos + n];
|
||||
s.pos += n;
|
||||
return n;
|
||||
};
|
||||
|
||||
fn dynamic_seek(
|
||||
s: *io::stream,
|
||||
off: io::off,
|
||||
w: io::whence
|
||||
) (io::off | io::error) = {
|
||||
let stream = s: *dynamic_stream;
|
||||
switch (w) {
|
||||
io::whence::SET => {
|
||||
if (len(stream.buf) < off: size) {
|
||||
abort("invalid offset");
|
||||
};
|
||||
stream.pos = off: size;
|
||||
},
|
||||
io::whence::CUR => {
|
||||
if (stream.pos + off: size > len(stream.buf)) {
|
||||
abort("invalid offset");
|
||||
};
|
||||
stream.pos += off: size;
|
||||
},
|
||||
io::whence::END => {
|
||||
if (len(stream.buf) - (-off): size < len(stream.buf)) {
|
||||
abort("invalid offset");
|
||||
};
|
||||
stream.pos = len(stream.buf) - (-off): size;
|
||||
},
|
||||
};
|
||||
return stream.pos: io::off;
|
||||
};
|
||||
|
||||
fn dynamic_close(s: *io::stream) void = {
|
||||
const s = s: *dynamic_stream;
|
||||
free(s.buf);
|
||||
free(s);
|
||||
};
|
||||
|
||||
// Closes the stream without freeing the buffer, instead transferring ownership
|
||||
// of it to the caller.
|
||||
export fn finish(s: *io::stream) []u8 = {
|
||||
if (s.closer != &dynamic_close) {
|
||||
abort("bufio::finish called on non-bufio stream");
|
||||
};
|
||||
let s = s: *dynamic_stream;
|
||||
let buf = s.buf;
|
||||
free(s);
|
||||
return buf;
|
||||
};
|
||||
|
||||
// Returns the current buffer.
|
||||
export fn buffer(s: *io::stream) []u8 = {
|
||||
if (s.closer != &dynamic_close) {
|
||||
abort("bufio::buffer called on non-bufio stream");
|
||||
};
|
||||
let s = s: *dynamic_stream;
|
||||
return s.buf;
|
||||
};
|
||||
|
||||
// Resets the buffer's length to zero, but keeps the allocated memory around for
|
||||
// future writes.
|
||||
export fn reset(s: *io::stream) void = {
|
||||
if (s.closer != &dynamic_close) {
|
||||
abort("bufio::reset called on non-bufio stream");
|
||||
};
|
||||
const s = s: *dynamic_stream;
|
||||
s.pos = 0;
|
||||
s.buf = s.buf[..0];
|
||||
};
|
||||
|
||||
// Truncates the buffer, freeing memory associated with it and setting its
|
||||
// length to zero.
|
||||
export fn truncate(s: *io::stream) (void | errors::unsupported) = {
|
||||
if (s.closer != &dynamic_close) {
|
||||
return errors::unsupported;
|
||||
};
|
||||
let s = s: *dynamic_stream;
|
||||
s.pos = 0;
|
||||
delete(s.buf[..]);
|
||||
};
|
||||
|
||||
@test fn dynamic() void = {
|
||||
// TODO: slice/array equality
|
||||
let s = dynamic(io::mode::RDWR);
|
||||
assert(io::write(s, [1, 2, 3]) as size == 3);
|
||||
assert(bytes::equal(buffer(s), [1, 2, 3]));
|
||||
assert(io::write(s, [4, 5]) as size == 2);
|
||||
assert(bytes::equal(buffer(s), [1, 2, 3, 4, 5]));
|
||||
let buf: [2]u8 = [0...];
|
||||
assert(io::seek(s, 0, io::whence::SET) as io::off == 0: io::off);
|
||||
assert(io::read(s, buf[..]) as size == 2 && bytes::equal(buf, [1, 2]));
|
||||
assert(io::read(s, buf[..]) as size == 2 && bytes::equal(buf, [3, 4]));
|
||||
assert(io::read(s, buf[..]) as size == 1 && buf[0] == 5);
|
||||
assert(io::read(s, buf[..]) is io::EOF);
|
||||
assert(io::write(s, [6, 7, 8]) as size == 3);
|
||||
assert(bytes::equal(buffer(s), [1, 2, 3, 4, 5, 6, 7, 8]));
|
||||
reset(s);
|
||||
assert(len(buffer(s)) == 0);
|
||||
assert(io::write(s, [1, 2, 3]) as size == 3);
|
||||
assert(truncate(s) is void);
|
||||
assert(len(buffer(s)) == 0);
|
||||
|
||||
let sl: []u8 = alloc([1, 2, 3]);
|
||||
let s = dynamic_from(sl, io::mode::WRITE);
|
||||
assert(io::write(s, [0, 0]) as size == 2);
|
||||
assert(io::seek(s, 0, io::whence::END) as io::off == 5: io::off);
|
||||
assert(io::write(s, [4, 5, 6]) as size == 3);
|
||||
assert(bytes::equal(buffer(s), [0, 0, 1, 2, 3, 4, 5, 6]));
|
||||
// TODO: this should check for errors::unsupported (harec bug prevents that)
|
||||
assert(io::read(s, buf[..]) is io::error);
|
||||
io::close(s);
|
||||
|
||||
sl = alloc([1, 2]);
|
||||
let s = dynamic_from(sl, io::mode::READ);
|
||||
assert(io::read(s, buf[..1]) as size == 1 && buf[0] == 1);
|
||||
assert(io::seek(s, 1, io::whence::CUR) as io::off == 2: io::off);
|
||||
assert(io::read(s, buf[..]) is io::EOF);
|
||||
// TODO: this should check for errors::unsupported (harec bug prevents that)
|
||||
assert(io::write(s, [1, 2]) is io::error);
|
||||
};
|
||||
@ -0,0 +1,41 @@
|
||||
// Encodes a rune as UTF-8 and returns the result as a slice. The result is
|
||||
// statically allocated; duplicate it if you aren't using it right away.
|
||||
export fn encoderune(r: rune) []u8 = {
|
||||
let ch = r: u32, n = 0z, first = 0u8;
|
||||
if (ch < 0x80) {
|
||||
first = 0;
|
||||
n = 1;
|
||||
} else if (ch < 0x800) {
|
||||
first = 0xC0;
|
||||
n = 2;
|
||||
} else if (ch < 0x10000) {
|
||||
first = 0xE0;
|
||||
n = 3;
|
||||
} else {
|
||||
first = 0xF0;
|
||||
n = 4;
|
||||
};
|
||||
|
||||
static let buf: [6]u8 = [0...];
|
||||
for (let i = n - 1; i > 0; i -= 1) {
|
||||
buf[i] = ch: u8 & 0x3F | 0x80;
|
||||
ch >>= 6;
|
||||
};
|
||||
buf[0] = ch: u8 | first;
|
||||
return buf[..n];
|
||||
};
|
||||
|
||||
@test fn encode() void = {
|
||||
const expected: [_][]u8 = [
|
||||
[0],
|
||||
[0x25],
|
||||
[0xE3, 0x81, 0x93],
|
||||
];
|
||||
const inputs = ['\0', '%', 'こ'];
|
||||
for (let i = 0z; i < len(inputs); i += 1) {
|
||||
const out = encoderune(inputs[i]);
|
||||
for (let j = 0z; j < len(expected[i]); j += 1) {
|
||||
assert(out[j] == expected[i][j]);
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
// The set of functions used for endian-aware encoding.
|
||||
export type endian = struct {
|
||||
getu16: *fn(buf: []u8) u16,
|
||||
putu16: *fn(buf: []u8, in: u16) void,
|
||||
getu32: *fn(buf: []u8) u32,
|
||||
putu32: *fn(buf: []u8, in: u32) void,
|
||||
getu64: *fn(buf: []u8) u64,
|
||||
putu64: *fn(buf: []u8, in: u64) void,
|
||||
};
|
||||
|
||||
// Big endian; MSB first.
|
||||
export const big: endian = endian {
|
||||
getu16 = &begetu16,
|
||||
putu16 = &beputu16,
|
||||
getu32 = &begetu32,
|
||||
putu32 = &beputu32,
|
||||
getu64 = &begetu64,
|
||||
putu64 = &beputu64,
|
||||
};
|
||||
|
||||
// Little endian; LSB first.
|
||||
export const little: endian = endian {
|
||||
getu16 = &legetu16,
|
||||
putu16 = &leputu16,
|
||||
getu32 = &legetu32,
|
||||
putu32 = &leputu32,
|
||||
getu64 = &legetu64,
|
||||
putu64 = &leputu64,
|
||||
};
|
||||
@ -0,0 +1,36 @@
|
||||
export type slice = struct {
|
||||
data: nullable *void,
|
||||
length: size,
|
||||
capacity: size,
|
||||
};
|
||||
|
||||
export fn ensure(s: *slice, membsz: size) void = {
|
||||
let cap = s.capacity;
|
||||
if (cap >= s.length) {
|
||||
return;
|
||||
};
|
||||
for (cap < s.length) {
|
||||
assert(cap >= s.capacity, "slice out of memory (overflow)");
|
||||
if (cap == 0) {
|
||||
cap = s.length;
|
||||
} else {
|
||||
cap *= 2;
|
||||
};
|
||||
};
|
||||
s.capacity = cap;
|
||||
const data = realloc(s.data, s.capacity * membsz);
|
||||
assert(data != null || s.capacity * membsz == 0);
|
||||
s.data = data;
|
||||
};
|
||||
|
||||
export fn unensure(s: *slice, membsz: size) void = {
|
||||
let cap = s.capacity;
|
||||
for (cap > s.length) {
|
||||
cap /= 2;
|
||||
};
|
||||
cap *= 2;
|
||||
s.capacity = cap;
|
||||
const data = realloc(s.data, s.capacity * membsz);
|
||||
assert(data != null || s.capacity * membsz == 0);
|
||||
s.data = data;
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
use format::elf;
|
||||
|
||||
// System V auxillary vector for the current process
|
||||
export let auxv: *[*]format::elf::auxv64 = null: *[*]format::elf::auxv64;
|
||||
@ -0,0 +1,112 @@
|
||||
use bytes;
|
||||
use rt;
|
||||
use strings;
|
||||
use types;
|
||||
|
||||
// The command line arguments provided to the program. By convention, the first
|
||||
// member is usually the name of the program.
|
||||
export let args: []str = [];
|
||||
|
||||
// Statically allocate arg strings if there are few enough arguments, saves a
|
||||
// syscall if we don't need it.
|
||||
let args_static: [32]str = [""...];
|
||||
|
||||
@init fn init_environ() void = {
|
||||
if (rt::argc < len(args_static)) {
|
||||
args = args_static[..rt::argc];
|
||||
for (let i = 0z; i < rt::argc; i += 1) {
|
||||
args[i] = strings::fromc(rt::argv[i]);
|
||||
};
|
||||
} else {
|
||||
args = alloc([], rt::argc);
|
||||
for (let i = 0z; i < rt::argc; i += 1) {
|
||||
append(args, strings::fromc(rt::argv[i]));
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@fini fn fini_environ() void = {
|
||||
if (rt::argc >= len(args_static)) {
|
||||
free(args);
|
||||
};
|
||||
};
|
||||
|
||||
// Looks up an environment variable and returns its value, or void if unset.
|
||||
export fn getenv(name: const str) (str | void) = {
|
||||
const name_b = strings::toutf8(name);
|
||||
for (let i = 0z; rt::envp[i] != null; i += 1) {
|
||||
const item = rt::envp[i]: *[*]u8;
|
||||
const eq: size = match (bytes::index(item[..], '=': u32: u8)) {
|
||||
void => abort("Environment violates System-V invariants"),
|
||||
i: size => i,
|
||||
};
|
||||
if (bytes::equal(name_b, item[..eq])) {
|
||||
const ln = strings::cstrlen(item: *const char);
|
||||
return strings::fromutf8(item[eq+1..ln]);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Looks up an environment variable and returns its value, or a default value if
|
||||
// unset.
|
||||
export fn tryenv(name: const str, default: str) str = match (getenv(name)) {
|
||||
s: str => s,
|
||||
void => default,
|
||||
};
|
||||
|
||||
let envp: []str = [];
|
||||
|
||||
// Returns a slice of the environment strings in the form KEY=VALUE.
|
||||
export fn getenvs() []str = {
|
||||
if (len(envp) != 0) {
|
||||
return envp;
|
||||
};
|
||||
for (let i = 0z; rt::envp[i] != null; i += 1) {
|
||||
append(envp, strings::fromc(rt::envp[i]: *const char));
|
||||
};
|
||||
return envp;
|
||||
};
|
||||
|
||||
let uts: rt::utsname = rt::utsname { ... };
|
||||
let uts_valid: bool = false;
|
||||
|
||||
// Returns the host kernel name
|
||||
export fn sysname() const str = {
|
||||
if (!uts_valid) {
|
||||
rt::uname(&uts) as void;
|
||||
};
|
||||
return strings::fromc(&uts.sysname: *const char);
|
||||
};
|
||||
|
||||
// Returns the host system hostname
|
||||
export fn hostname() const str = {
|
||||
if (!uts_valid) {
|
||||
rt::uname(&uts) as void;
|
||||
};
|
||||
return strings::fromc(&uts.nodename: *const char);
|
||||
};
|
||||
|
||||
// Returns the host kernel version
|
||||
export fn release() const str = {
|
||||
if (!uts_valid) {
|
||||
rt::uname(&uts) as void;
|
||||
};
|
||||
return strings::fromc(&uts.release: *const char);
|
||||
};
|
||||
|
||||
// Returns the host operating system version
|
||||
export fn version() const str = {
|
||||
if (!uts_valid) {
|
||||
rt::uname(&uts) as void;
|
||||
};
|
||||
return strings::fromc(&uts.version: *const char);
|
||||
};
|
||||
|
||||
// Returns the host CPU architecture
|
||||
export fn machine() const str = {
|
||||
if (!uts_valid) {
|
||||
rt::uname(&uts) as void;
|
||||
};
|
||||
return strings::fromc(&uts.machine: *const char);
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
// Returns true if the two byte sequences are identical.
|
||||
export fn equal(a: []u8, b: []u8) bool = {
|
||||
if (len(a) != len(b)) {
|
||||
return false;
|
||||
};
|
||||
for (let i = 0z; i < len(a); i += 1) {
|
||||
if (a[i] != b[i]) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
@test fn equal() void = {
|
||||
let a: []u8 = [1, 2, 3];
|
||||
let b: []u8 = [1, 2, 3];
|
||||
let c: []u8 = [1, 4, 5];
|
||||
let d: []u8 = [1, 2, 3, 4];
|
||||
let e: []u8 = [1, 2];
|
||||
assert(equal(a, b));
|
||||
assert(!equal(a, c));
|
||||
assert(!equal(a, d));
|
||||
assert(!equal(a, e));
|
||||
};
|
||||
@ -0,0 +1,423 @@
|
||||
// Represents an error returned from the Linux kernel.
|
||||
export type errno = int!;
|
||||
|
||||
// Given an integer error number, wraps it in an error type.
|
||||
export fn wrap_errno(err: int) errno = err: errno;
|
||||
|
||||
// Checks the return value from a Linux syscall and, if found to be in error,
|
||||
// returns the appropriate error. Otherwise, returns the original value.
|
||||
fn wrap_return(r: u64) (errno | u64) = {
|
||||
if (r > -4096u64) {
|
||||
return (-(r: i64)): int: errno;
|
||||
};
|
||||
return r;
|
||||
};
|
||||
|
||||
// Obtains a human-friendly reading of an [errno] (e.g. "Operation not
|
||||
// permitted").
|
||||
export fn strerror(err: errno) str = {
|
||||
return switch (err: int) {
|
||||
EPERM => "Operation not permitted",
|
||||
ENOENT => "No such file or directory",
|
||||
ESRCH => "No such process",
|
||||
EINTR => "Interrupted system call",
|
||||
EIO => "Input/output error",
|
||||
ENXIO => "No such device or address",
|
||||
E2BIG => "Argument list too long",
|
||||
ENOEXEC => "Exec format error",
|
||||
EBADF => "Bad file descriptor",
|
||||
ECHILD => "No child processes",
|
||||
EAGAIN => "Resource temporarily unavailable",
|
||||
ENOMEM => "Cannot allocate memory",
|
||||
EACCES => "Permission denied",
|
||||
EFAULT => "Bad address",
|
||||
ENOTBLK => "Block device required",
|
||||
EBUSY => "Device or resource busy",
|
||||
EEXIST => "File exists",
|
||||
EXDEV => "Invalid cross-device link",
|
||||
ENODEV => "No such device",
|
||||
ENOTDIR => "Not a directory",
|
||||
EISDIR => "Is a directory",
|
||||
EINVAL => "Invalid argument",
|
||||
ENFILE => "Too many open files in system",
|
||||
EMFILE => "Too many open files",
|
||||
ENOTTY => "Inappropriate ioctl for device",
|
||||
ETXTBSY => "Text file busy",
|
||||
EFBIG => "File too large",
|
||||
ENOSPC => "No space left on device",
|
||||
ESPIPE => "Illegal seek",
|
||||
EROFS => "Read-only file system",
|
||||
EMLINK => "Too many links",
|
||||
EPIPE => "Broken pipe",
|
||||
EDOM => "Numerical argument out of domain",
|
||||
ERANGE => "Numerical result out of range",
|
||||
EDEADLK => "Resource deadlock avoided",
|
||||
ENAMETOOLONG => "File name too long",
|
||||
ENOLCK => "No locks available",
|
||||
ENOSYS => "Function not implemented",
|
||||
ENOTEMPTY => "Directory not empty",
|
||||
ELOOP => "Too many levels of symbolic links",
|
||||
ENOMSG => "No message of desired type",
|
||||
EIDRM => "Identifier removed",
|
||||
ECHRNG => "Channel number out of range",
|
||||
EL2NSYNC => "Level 2 not synchronized",
|
||||
EL3HLT => "Level 3 halted",
|
||||
EL3RST => "Level 3 reset",
|
||||
ELNRNG => "Link number out of range",
|
||||
EUNATCH => "Protocol driver not attached",
|
||||
ENOCSI => "No CSI structure available",
|
||||
EL2HLT => "Level 2 halted",
|
||||
EBADE => "Invalid exchange",
|
||||
EBADR => "Invalid request descriptor",
|
||||
EXFULL => "Exchange full",
|
||||
ENOANO => "No anode",
|
||||
EBADRQC => "Invalid request code",
|
||||
EBADSLT => "Invalid slot",
|
||||
EBFONT => "Bad font file format",
|
||||
ENOSTR => "Device not a stream",
|
||||
ENODATA => "No data available",
|
||||
ETIME => "Timer expired",
|
||||
ENOSR => "Out of streams resources",
|
||||
ENONET => "Machine is not on the network",
|
||||
ENOPKG => "Package not installed",
|
||||
EREMOTE => "Object is remote",
|
||||
ENOLINK => "Link has been severed",
|
||||
EADV => "Advertise error",
|
||||
ESRMNT => "Srmount error",
|
||||
ECOMM => "Communication error on send",
|
||||
EPROTO => "Protocol error",
|
||||
EMULTIHOP => "Multihop attempted",
|
||||
EDOTDOT => "RFS specific error",
|
||||
EBADMSG => "Bad message",
|
||||
EOVERFLOW => "Value too large for defined data type",
|
||||
ENOTUNIQ => "Name not unique on network",
|
||||
EBADFD => "File descriptor in bad state",
|
||||
EREMCHG => "Remote address changed",
|
||||
ELIBACC => "Can not access a needed shared library",
|
||||
ELIBBAD => "Accessing a corrupted shared library",
|
||||
ELIBSCN => ".lib section in a.out corrupted",
|
||||
ELIBMAX => "Attempting to link in too many shared libraries",
|
||||
ELIBEXEC => "Cannot exec a shared library directly",
|
||||
EILSEQ => "Invalid or incomplete multibyte or wide character",
|
||||
ERESTART => "Interrupted system call should be restarted",
|
||||
ESTRPIPE => "Streams pipe error",
|
||||
EUSERS => "Too many users",
|
||||
ENOTSOCK => "Socket operation on non-socket",
|
||||
EDESTADDRREQ => "Destination address required",
|
||||
EMSGSIZE => "Message too long",
|
||||
EPROTOTYPE => "Protocol wrong type for socket",
|
||||
ENOPROTOOPT => "Protocol not available",
|
||||
EPROTONOSUPPORT => "Protocol not supported",
|
||||
ESOCKTNOSUPPORT => "Socket type not supported",
|
||||
EOPNOTSUPP => "Operation not supported",
|
||||
EPFNOSUPPORT => "Protocol family not supported",
|
||||
EAFNOSUPPORT => "Address family not supported by protocol",
|
||||
EADDRINUSE => "Address already in use",
|
||||
EADDRNOTAVAIL => "Cannot assign requested address",
|
||||
ENETDOWN => "Network is down",
|
||||
ENETUNREACH => "Network is unreachable",
|
||||
ENETRESET => "Network dropped connection on reset",
|
||||
ECONNABORTED => "Software caused connection abort",
|
||||
ECONNRESET => "Connection reset by peer",
|
||||
ENOBUFS => "No buffer space available",
|
||||
EISCONN => "Transport endpoint is already connected",
|
||||
ENOTCONN => "Transport endpoint is not connected",
|
||||
ESHUTDOWN => "Cannot send after transport endpoint shutdown",
|
||||
ETOOMANYREFS => "Too many references: cannot splice",
|
||||
ETIMEDOUT => "Connection timed out",
|
||||
ECONNREFUSED => "Connection refused",
|
||||
EHOSTDOWN => "Host is down",
|
||||
EHOSTUNREACH => "No route to host",
|
||||
EALREADY => "Operation already in progress",
|
||||
EINPROGRESS => "Operation now in progress",
|
||||
ESTALE => "Stale file handle",
|
||||
EUCLEAN => "Structure needs cleaning",
|
||||
ENOTNAM => "Not a XENIX named type file",
|
||||
ENAVAIL => "No XENIX semaphores available",
|
||||
EISNAM => "Is a named type file",
|
||||
EREMOTEIO => "Remote I/O error",
|
||||
EDQUOT => "Disk quota exceeded",
|
||||
ENOMEDIUM => "No medium found",
|
||||
EMEDIUMTYPE => "Wrong medium type",
|
||||
ECANCELED => "Operation canceled",
|
||||
ENOKEY => "Required key not available",
|
||||
EKEYEXPIRED => "Key has expired",
|
||||
EKEYREVOKED => "Key has been revoked",
|
||||
EKEYREJECTED => "Key was rejected by service",
|
||||
EOWNERDEAD => "Owner died",
|
||||
ENOTRECOVERABLE => "State not recoverable",
|
||||
ERFKILL => "Operation not possible due to RF-kill",
|
||||
EHWPOISON => "Memory page has hardware error",
|
||||
* => "Unknown Linux error code", // TODO: snprintf to add errno?
|
||||
};
|
||||
};
|
||||
|
||||
// Gets the programmer-friendly name for an [errno] (e.g. EPERM).
|
||||
export fn errname(err: errno) str = {
|
||||
return switch (err: int) {
|
||||
EPERM => "EPERM",
|
||||
ENOENT => "ENOENT",
|
||||
ESRCH => "ESRCH",
|
||||
EINTR => "EINTR",
|
||||
EIO => "EIO",
|
||||
ENXIO => "ENXIO",
|
||||
E2BIG => "E2BIG",
|
||||
ENOEXEC => "ENOEXEC",
|
||||
EBADF => "EBADF",
|
||||
ECHILD => "ECHILD",
|
||||
EAGAIN => "EAGAIN",
|
||||
ENOMEM => "ENOMEM",
|
||||
EACCES => "EACCES",
|
||||
EFAULT => "EFAULT",
|
||||
ENOTBLK => "ENOTBLK",
|
||||
EBUSY => "EBUSY",
|
||||
EEXIST => "EEXIST",
|
||||
EXDEV => "EXDEV",
|
||||
ENODEV => "ENODEV",
|
||||
ENOTDIR => "ENOTDIR",
|
||||
EISDIR => "EISDIR",
|
||||
EINVAL => "EINVAL",
|
||||
ENFILE => "ENFILE",
|
||||
EMFILE => "EMFILE",
|
||||
ENOTTY => "ENOTTY",
|
||||
ETXTBSY => "ETXTBSY",
|
||||
EFBIG => "EFBIG",
|
||||
ENOSPC => "ENOSPC",
|
||||
ESPIPE => "ESPIPE",
|
||||
EROFS => "EROFS",
|
||||
EMLINK => "EMLINK",
|
||||
EPIPE => "EPIPE",
|
||||
EDOM => "EDOM",
|
||||
ERANGE => "ERANGE",
|
||||
EDEADLK => "EDEADLK",
|
||||
ENAMETOOLONG => "ENAMETOOLONG",
|
||||
ENOLCK => "ENOLCK",
|
||||
ENOSYS => "ENOSYS",
|
||||
ENOTEMPTY => "ENOTEMPTY",
|
||||
ELOOP => "ELOOP",
|
||||
ENOMSG => "ENOMSG",
|
||||
EIDRM => "EIDRM",
|
||||
ECHRNG => "ECHRNG",
|
||||
EL2NSYNC => "EL2NSYNC",
|
||||
EL3HLT => "EL3HLT",
|
||||
EL3RST => "EL3RST",
|
||||
ELNRNG => "ELNRNG",
|
||||
EUNATCH => "EUNATCH",
|
||||
ENOCSI => "ENOCSI",
|
||||
EL2HLT => "EL2HLT",
|
||||
EBADE => "EBADE",
|
||||
EBADR => "EBADR",
|
||||
EXFULL => "EXFULL",
|
||||
ENOANO => "ENOANO",
|
||||
EBADRQC => "EBADRQC",
|
||||
EBADSLT => "EBADSLT",
|
||||
EBFONT => "EBFONT",
|
||||
ENOSTR => "ENOSTR",
|
||||
ENODATA => "ENODATA",
|
||||
ETIME => "ETIME",
|
||||
ENOSR => "ENOSR",
|
||||
ENONET => "ENONET",
|
||||
ENOPKG => "ENOPKG",
|
||||
EREMOTE => "EREMOTE",
|
||||
ENOLINK => "ENOLINK",
|
||||
EADV => "EADV",
|
||||
ESRMNT => "ESRMNT",
|
||||
ECOMM => "ECOMM",
|
||||
EPROTO => "EPROTO",
|
||||
EMULTIHOP => "EMULTIHOP",
|
||||
EDOTDOT => "EDOTDOT",
|
||||
EBADMSG => "EBADMSG",
|
||||
EOVERFLOW => "EOVERFLOW",
|
||||
ENOTUNIQ => "ENOTUNIQ",
|
||||
EBADFD => "EBADFD",
|
||||
EREMCHG => "EREMCHG",
|
||||
ELIBACC => "ELIBACC",
|
||||
ELIBBAD => "ELIBBAD",
|
||||
ELIBSCN => "ELIBSCN",
|
||||
ELIBMAX => "ELIBMAX",
|
||||
ELIBEXEC => "ELIBEXEC",
|
||||
EILSEQ => "EILSEQ",
|
||||
ERESTART => "ERESTART",
|
||||
ESTRPIPE => "ESTRPIPE",
|
||||
EUSERS => "EUSERS",
|
||||
ENOTSOCK => "ENOTSOCK",
|
||||
EDESTADDRREQ => "EDESTADDRREQ",
|
||||
EMSGSIZE => "EMSGSIZE",
|
||||
EPROTOTYPE => "EPROTOTYPE",
|
||||
ENOPROTOOPT => "ENOPROTOOPT",
|
||||
EPROTONOSUPPORT => "EPROTONOSUPPORT",
|
||||
ESOCKTNOSUPPORT => "ESOCKTNOSUPPORT",
|
||||
EOPNOTSUPP => "EOPNOTSUPP",
|
||||
EPFNOSUPPORT => "EPFNOSUPPORT",
|
||||
EAFNOSUPPORT => "EAFNOSUPPORT",
|
||||
EADDRINUSE => "EADDRINUSE",
|
||||
EADDRNOTAVAIL => "EADDRNOTAVAIL",
|
||||
ENETDOWN => "ENETDOWN",
|
||||
ENETUNREACH => "ENETUNREACH",
|
||||
ENETRESET => "ENETRESET",
|
||||
ECONNABORTED => "ECONNABORTED",
|
||||
ECONNRESET => "ECONNRESET",
|
||||
ENOBUFS => "ENOBUFS",
|
||||
EISCONN => "EISCONN",
|
||||
ENOTCONN => "ENOTCONN",
|
||||
ESHUTDOWN => "ESHUTDOWN",
|
||||
ETOOMANYREFS => "ETOOMANYREFS",
|
||||
ETIMEDOUT => "ETIMEDOUT",
|
||||
ECONNREFUSED => "ECONNREFUSED",
|
||||
EHOSTDOWN => "EHOSTDOWN",
|
||||
EHOSTUNREACH => "EHOSTUNREACH",
|
||||
EALREADY => "EALREADY",
|
||||
EINPROGRESS => "EINPROGRESS",
|
||||
ESTALE => "ESTALE",
|
||||
EUCLEAN => "EUCLEAN",
|
||||
ENOTNAM => "ENOTNAM",
|
||||
ENAVAIL => "ENAVAIL",
|
||||
EISNAM => "EISNAM",
|
||||
EREMOTEIO => "EREMOTEIO",
|
||||
EDQUOT => "EDQUOT",
|
||||
ENOMEDIUM => "ENOMEDIUM",
|
||||
EMEDIUMTYPE => "EMEDIUMTYPE",
|
||||
ECANCELED => "ECANCELED",
|
||||
ENOKEY => "ENOKEY",
|
||||
EKEYEXPIRED => "EKEYEXPIRED",
|
||||
EKEYREVOKED => "EKEYREVOKED",
|
||||
EKEYREJECTED => "EKEYREJECTED",
|
||||
EOWNERDEAD => "EOWNERDEAD",
|
||||
ENOTRECOVERABLE => "ENOTRECOVERABLE",
|
||||
ERFKILL => "ERFKILL",
|
||||
EHWPOISON => "EHWPOISON",
|
||||
* => "[unknown errno]", // TODO: snprintf to add errno?
|
||||
};
|
||||
};
|
||||
|
||||
export def EPERM: int = 1;
|
||||
export def ENOENT: int = 2;
|
||||
export def ESRCH: int = 3;
|
||||
export def EINTR: int = 4;
|
||||
export def EIO: int = 5;
|
||||
export def ENXIO: int = 6;
|
||||
export def E2BIG: int = 7;
|
||||
export def ENOEXEC: int = 8;
|
||||
export def EBADF: int = 9;
|
||||
export def ECHILD: int = 10;
|
||||
export def EAGAIN: int = 11;
|
||||
export def ENOMEM: int = 12;
|
||||
export def EACCES: int = 13;
|
||||
export def EFAULT: int = 14;
|
||||
export def ENOTBLK: int = 15;
|
||||
export def EBUSY: int = 16;
|
||||
export def EEXIST: int = 17;
|
||||
export def EXDEV: int = 18;
|
||||
export def ENODEV: int = 19;
|
||||
export def ENOTDIR: int = 20;
|
||||
export def EISDIR: int = 21;
|
||||
export def EINVAL: int = 22;
|
||||
export def ENFILE: int = 23;
|
||||
export def EMFILE: int = 24;
|
||||
export def ENOTTY: int = 25;
|
||||
export def ETXTBSY: int = 26;
|
||||
export def EFBIG: int = 27;
|
||||
export def ENOSPC: int = 28;
|
||||
export def ESPIPE: int = 29;
|
||||
export def EROFS: int = 30;
|
||||
export def EMLINK: int = 31;
|
||||
export def EPIPE: int = 32;
|
||||
export def EDOM: int = 33;
|
||||
export def ERANGE: int = 34;
|
||||
export def EDEADLK: int = 35;
|
||||
export def ENAMETOOLONG: int = 36;
|
||||
export def ENOLCK: int = 37;
|
||||
export def ENOSYS: int = 38;
|
||||
export def ENOTEMPTY: int = 39;
|
||||
export def ELOOP: int = 40;
|
||||
export def ENOMSG: int = 42;
|
||||
export def EIDRM: int = 43;
|
||||
export def ECHRNG: int = 44;
|
||||
export def EL2NSYNC: int = 45;
|
||||
export def EL3HLT: int = 46;
|
||||
export def EL3RST: int = 47;
|
||||
export def ELNRNG: int = 48;
|
||||
export def EUNATCH: int = 49;
|
||||
export def ENOCSI: int = 50;
|
||||
export def EL2HLT: int = 51;
|
||||
export def EBADE: int = 52;
|
||||
export def EBADR: int = 53;
|
||||
export def EXFULL: int = 54;
|
||||
export def ENOANO: int = 55;
|
||||
export def EBADRQC: int = 56;
|
||||
export def EBADSLT: int = 57;
|
||||
export def EBFONT: int = 59;
|
||||
export def ENOSTR: int = 60;
|
||||
export def ENODATA: int = 61;
|
||||
export def ETIME: int = 62;
|
||||
export def ENOSR: int = 63;
|
||||
export def ENONET: int = 64;
|
||||
export def ENOPKG: int = 65;
|
||||
export def EREMOTE: int = 66;
|
||||
export def ENOLINK: int = 67;
|
||||
export def EADV: int = 68;
|
||||
export def ESRMNT: int = 69;
|
||||
export def ECOMM: int = 70;
|
||||
export def EPROTO: int = 71;
|
||||
export def EMULTIHOP: int = 72;
|
||||
export def EDOTDOT: int = 73;
|
||||
export def EBADMSG: int = 74;
|
||||
export def EOVERFLOW: int = 75;
|
||||
export def ENOTUNIQ: int = 76;
|
||||
export def EBADFD: int = 77;
|
||||
export def EREMCHG: int = 78;
|
||||
export def ELIBACC: int = 79;
|
||||
export def ELIBBAD: int = 80;
|
||||
export def ELIBSCN: int = 81;
|
||||
export def ELIBMAX: int = 82;
|
||||
export def ELIBEXEC: int = 83;
|
||||
export def EILSEQ: int = 84;
|
||||
export def ERESTART: int = 85;
|
||||
export def ESTRPIPE: int = 86;
|
||||
export def EUSERS: int = 87;
|
||||
export def ENOTSOCK: int = 88;
|
||||
export def EDESTADDRREQ: int = 89;
|
||||
export def EMSGSIZE: int = 90;
|
||||
export def EPROTOTYPE: int = 91;
|
||||
export def ENOPROTOOPT: int = 92;
|
||||
export def EPROTONOSUPPORT: int = 93;
|
||||
export def ESOCKTNOSUPPORT: int = 94;
|
||||
export def EOPNOTSUPP: int = 95;
|
||||
export def EPFNOSUPPORT: int = 96;
|
||||
export def EAFNOSUPPORT: int = 97;
|
||||
export def EADDRINUSE: int = 98;
|
||||
export def EADDRNOTAVAIL: int = 99;
|
||||
export def ENETDOWN: int = 100;
|
||||
export def ENETUNREACH: int = 101;
|
||||
export def ENETRESET: int = 102;
|
||||
export def ECONNABORTED: int = 103;
|
||||
export def ECONNRESET: int = 104;
|
||||
export def ENOBUFS: int = 105;
|
||||
export def EISCONN: int = 106;
|
||||
export def ENOTCONN: int = 107;
|
||||
export def ESHUTDOWN: int = 108;
|
||||
export def ETOOMANYREFS: int = 109;
|
||||
export def ETIMEDOUT: int = 110;
|
||||
export def ECONNREFUSED: int = 111;
|
||||
export def EHOSTDOWN: int = 112;
|
||||
export def EHOSTUNREACH: int = 113;
|
||||
export def EALREADY: int = 114;
|
||||
export def EINPROGRESS: int = 115;
|
||||
export def ESTALE: int = 116;
|
||||
export def EUCLEAN: int = 117;
|
||||
export def ENOTNAM: int = 118;
|
||||
export def ENAVAIL: int = 119;
|
||||
export def EISNAM: int = 120;
|
||||
export def EREMOTEIO: int = 121;
|
||||
export def EDQUOT: int = 122;
|
||||
export def ENOMEDIUM: int = 123;
|
||||
export def EMEDIUMTYPE: int = 124;
|
||||
export def ECANCELED: int = 125;
|
||||
export def ENOKEY: int = 126;
|
||||
export def EKEYEXPIRED: int = 127;
|
||||
export def EKEYREVOKED: int = 128;
|
||||
export def EKEYREJECTED: int = 129;
|
||||
export def EOWNERDEAD: int = 130;
|
||||
export def ENOTRECOVERABLE: int = 131;
|
||||
export def ERFKILL: int = 132;
|
||||
export def EHWPOISON: int = 133;
|
||||
@ -0,0 +1,40 @@
|
||||
use bufio;
|
||||
use fmt;
|
||||
use hare::lex;
|
||||
use hare::parse;
|
||||
use io;
|
||||
use os;
|
||||
use strings;
|
||||
|
||||
// TODO: Expand to more kinds of errors
|
||||
fn printerr(err: parse::error) void = {
|
||||
match (err) {
|
||||
err: lex::syntax => printerr_syntax(err),
|
||||
err: io::error => fmt::errorln(io::strerror(err)),
|
||||
};
|
||||
};
|
||||
|
||||
fn printerr_syntax(err: lex::syntax) void = {
|
||||
let location = err.0, details = err.1;
|
||||
let file = os::open(location.path) as *io::stream;
|
||||
defer io::close(file);
|
||||
|
||||
let line = 1u;
|
||||
for (line < location.line) {
|
||||
let r = bufio::scanrune(file) as rune;
|
||||
if (r == '\n') {
|
||||
line += 1u;
|
||||
};
|
||||
};
|
||||
|
||||
let line = bufio::scanline(file) as []u8;
|
||||
defer free(line);
|
||||
let line = strings::fromutf8(line);
|
||||
fmt::errorfln("{}:{},{}: Syntax error: {}",
|
||||
location.path, location.line, location.col, details);
|
||||
fmt::errorln(line);
|
||||
for (let i = 0u; i < location.col - 2; i += 1) {
|
||||
fmt::error(" ");
|
||||
};
|
||||
fmt::errorln("^--- here");
|
||||
};
|
||||
@ -0,0 +1,83 @@
|
||||
use errors;
|
||||
use rt;
|
||||
use strings;
|
||||
use os;
|
||||
|
||||
// Forks the current process, returning the pid of the child (to the parent) and
|
||||
// void (to the child), or an error.
|
||||
export fn fork() (int | void | error) = match (rt::fork()) {
|
||||
err: rt::errno => errors::errno(err),
|
||||
i: (int | void) => i,
|
||||
};
|
||||
|
||||
fn open(path: str) (platform_cmd | errors::opaque) = {
|
||||
match (rt::access(path, rt::X_OK)) {
|
||||
err: rt::errno => errors::errno(err),
|
||||
b: bool => if (!b) {
|
||||
return errors::errno(rt::EACCES);
|
||||
},
|
||||
};
|
||||
// O_PATH is used because it allows us to use an executable for which we
|
||||
// have execute permissions, but not read permissions.
|
||||
return match (rt::open(path, rt::O_PATH, 0u)) {
|
||||
fd: int => fd,
|
||||
err: rt::errno => errors::errno(err),
|
||||
};
|
||||
};
|
||||
|
||||
fn platform_finish(cmd: *command) void = rt::close(cmd.platform);
|
||||
|
||||
fn platform_exec(cmd: *command) errors::opaque = {
|
||||
// We don't worry about freeing the return values from strings::to_c
|
||||
// because once we exec(2) our heap is fried anyway
|
||||
let argv: []nullable *const char = alloc([], len(cmd.argv) + 1z);
|
||||
for (let i = 0z; i < len(cmd.argv); i += 1z) {
|
||||
append(argv, strings::to_c(cmd.argv[i]));
|
||||
};
|
||||
append(argv, null);
|
||||
|
||||
let envp: nullable *[*]nullable *const char = null;
|
||||
if (len(cmd.env) != 0) {
|
||||
let env: []nullable *const char = alloc([], len(cmd.env) + 1);
|
||||
for (let i = 0z; i < len(cmd.env); i += 1) {
|
||||
append(env, strings::to_c(cmd.env[i]));
|
||||
};
|
||||
append(env, null);
|
||||
envp = env: *[*]nullable *const char;
|
||||
};
|
||||
|
||||
return errors::errno(rt::execveat(cmd.platform, strings::c_empty,
|
||||
argv: *[*]nullable *const char, envp, rt::AT_EMPTY_PATH));
|
||||
};
|
||||
|
||||
fn platform_start(cmd: *command) (errors::opaque | process) = {
|
||||
// TODO: Let the user configure clone more to their taste (e.g. SIGCHLD)
|
||||
let pipe: [2]int = [0...];
|
||||
match (rt::pipe2(&pipe, rt::O_CLOEXEC)) {
|
||||
err: rt::errno => return errors::errno(err),
|
||||
void => void,
|
||||
};
|
||||
|
||||
match (rt::clone(null, 0, null, null, 0)) {
|
||||
err: rt::errno => return errors::errno(err),
|
||||
pid: int => {
|
||||
rt::close(pipe[1]);
|
||||
let errno: int = 0;
|
||||
return match (rt::read(pipe[0], &errno, size(int))) {
|
||||
err: rt::errno => errors::errno(err),
|
||||
n: size => switch (n) {
|
||||
size(int) => errors::errno(errno),
|
||||
* => abort("Unexpected rt::read result"),
|
||||
0 => pid,
|
||||
},
|
||||
};
|
||||
},
|
||||
void => {
|
||||
rt::close(pipe[0]);
|
||||
let err = platform_exec(cmd);
|
||||
let err = &err.data: *rt::errno;
|
||||
rt::write(pipe[1], &err, size(int));
|
||||
rt::exit(1);
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
use rt;
|
||||
|
||||
// Exit the program with the provided status code.
|
||||
export @noreturn fn exit(status: int) void = rt::exit(status);
|
||||
@ -0,0 +1,89 @@
|
||||
use io;
|
||||
use fmt;
|
||||
use hare::ast;
|
||||
|
||||
// TODO
|
||||
export fn expr(
|
||||
out: *io::stream,
|
||||
indent: size,
|
||||
t: ast::expr
|
||||
) (size | io::error) = {
|
||||
return match (t) {
|
||||
e: ast::access_expr => abort(),
|
||||
e: ast::alloc_expr => abort(),
|
||||
e: ast::append_expr => abort(),
|
||||
e: ast::assert_expr => abort(),
|
||||
e: ast::assign_expr => abort(),
|
||||
e: ast::binarithm_expr => {
|
||||
let z = expr(out, indent, *e.lvalue)?;
|
||||
z += fmt::fprintf(out, " {} ", switch (e.op) {
|
||||
ast::binarithm_op::BAND => "&",
|
||||
ast::binarithm_op::BOR => "|",
|
||||
ast::binarithm_op::DIV => "/",
|
||||
ast::binarithm_op::GT => ">",
|
||||
ast::binarithm_op::GTEQ => ">=",
|
||||
ast::binarithm_op::LAND => "&&",
|
||||
ast::binarithm_op::LEQUAL => "==",
|
||||
ast::binarithm_op::LESS => "<",
|
||||
ast::binarithm_op::LESSEQ => "<=",
|
||||
ast::binarithm_op::LOR => "||",
|
||||
ast::binarithm_op::LSHIFT => "<<",
|
||||
ast::binarithm_op::LXOR => "^^",
|
||||
ast::binarithm_op::MINUS => "-",
|
||||
ast::binarithm_op::MODULO => "%",
|
||||
ast::binarithm_op::NEQUAL => "!=",
|
||||
ast::binarithm_op::PLUS => "+",
|
||||
ast::binarithm_op::RSHIFT => ">>",
|
||||
ast::binarithm_op::TIMES => "*",
|
||||
ast::binarithm_op::BXOR => "^",
|
||||
})?;
|
||||
z += expr(out, indent, *e.rvalue)?;
|
||||
z;
|
||||
},
|
||||
e: []ast::binding_expr => abort(),
|
||||
e: ast::break_expr => abort(),
|
||||
e: ast::call_expr => abort(),
|
||||
e: ast::cast_expr => {
|
||||
let z = expr(out, indent, *e.value)?;
|
||||
const op = switch (e.kind) {
|
||||
ast::cast_kind::CAST => ": ",
|
||||
ast::cast_kind::ASSERTION => " as ",
|
||||
ast::cast_kind::TEST => " is ",
|
||||
};
|
||||
z += fmt::fprintf(out, "{}", op)?;
|
||||
z += _type(out, indent, *e._type)?;
|
||||
z;
|
||||
},
|
||||
e: ast::constant_expr => {
|
||||
assert(e is void);
|
||||
fmt::fprint(out, "void")?;
|
||||
},
|
||||
e: ast::continue_expr => abort(),
|
||||
e: ast::defer_expr => abort(),
|
||||
e: ast::delete_expr => abort(),
|
||||
e: ast::for_expr => abort(),
|
||||
e: ast::free_expr => abort(),
|
||||
e: ast::if_expr => abort(),
|
||||
e: ast::list_expr => abort(),
|
||||
e: ast::match_expr => abort(),
|
||||
e: ast::len_expr => abort(),
|
||||
e: ast::size_expr => abort(),
|
||||
e: ast::offset_expr => abort(),
|
||||
e: ast::propagate_expr => abort(),
|
||||
e: ast::return_expr => abort(),
|
||||
e: ast::slice_expr => abort(),
|
||||
e: ast::switch_expr => abort(),
|
||||
e: ast::unarithm_expr => {
|
||||
let z = fmt::fprintf(out, "{}", switch (e.op) {
|
||||
ast::unarithm_op::ADDR => "&",
|
||||
ast::unarithm_op::BNOT => "~",
|
||||
ast::unarithm_op::DEREF => "*",
|
||||
ast::unarithm_op::LNOT => "!",
|
||||
ast::unarithm_op::MINUS => "-",
|
||||
ast::unarithm_op::PLUS => "+",
|
||||
})?;
|
||||
z += expr(out, indent, *e.operand)?;
|
||||
z;
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
use hare::ast;
|
||||
use hare::lex::{btoken};
|
||||
use hare::lex;
|
||||
use io;
|
||||
|
||||
// Parses a complex-expression.
|
||||
export fn complex_expression(lexer: *lex::lexer) (ast::expr | error) = {
|
||||
// TODO: if, for, switch, match
|
||||
return simple_expression(lexer);
|
||||
};
|
||||
|
||||
// Parses a compound-expression.
|
||||
export fn compound_expression(lexer: *lex::lexer) (ast::expr | error) = {
|
||||
let tok = match (lex::lex(lexer)?) {
|
||||
io::EOF => return syntaxerr(mkloc(lexer),
|
||||
"Unexpected EOF, expected compound expression"),
|
||||
t: (lex::token, lex::location) => t,
|
||||
};
|
||||
|
||||
lex::unlex(lexer, tok);
|
||||
let tok = match (tok.0) {
|
||||
tok: btoken => tok,
|
||||
* => return complex_expression(lexer),
|
||||
};
|
||||
|
||||
return switch (tok) {
|
||||
btoken::LBRACE => expression_list(lexer),
|
||||
btoken::BREAK, btoken::CONTINUE, btoken::RETURN =>
|
||||
control_statement(lexer),
|
||||
* => complex_expression(lexer),
|
||||
};
|
||||
};
|
||||
|
||||
fn scope_expression(lexer: *lex::lexer) (ast::expr | error) = {
|
||||
return simple_expression(lexer); // TODO
|
||||
};
|
||||
|
||||
// Parses a simple-expression.
|
||||
export fn simple_expression(lexer: *lex::lexer) (ast::expr | error) =
|
||||
binarithm(lexer, void, 0);
|
||||
@ -0,0 +1,135 @@
|
||||
use errors;
|
||||
use io;
|
||||
use rt;
|
||||
use strings;
|
||||
|
||||
type fd_stream = struct {
|
||||
stream: io::stream,
|
||||
fd: int,
|
||||
};
|
||||
|
||||
fn static_fdopen(
|
||||
fd: int, name: str, mode: io::mode, stream: *fd_stream,
|
||||
) *io::stream = {
|
||||
*stream = fd_stream {
|
||||
stream = io::stream {
|
||||
name = name,
|
||||
closer = &fd_close_static,
|
||||
copier = &fd_copy,
|
||||
seeker = &fd_seek,
|
||||
...
|
||||
},
|
||||
fd = fd,
|
||||
};
|
||||
if (mode & io::mode::READ == io::mode::READ) {
|
||||
stream.stream.reader = &fd_read;
|
||||
};
|
||||
if (mode & io::mode::WRITE == io::mode::WRITE) {
|
||||
stream.stream.writer = &fd_write;
|
||||
};
|
||||
return &stream.stream;
|
||||
};
|
||||
|
||||
// Opens a Unix file descriptor as an io::stream.
|
||||
export fn fdopen(fd: int, name: str, mode: io::mode) *io::stream = {
|
||||
let stream = alloc(fd_stream { ... });
|
||||
static_fdopen(fd, strings::dup(name), mode, stream);
|
||||
stream.stream.closer = &fd_close;
|
||||
return &stream.stream;
|
||||
};
|
||||
|
||||
fn is_fdstream(s: *io::stream) bool = {
|
||||
return s.reader == &fd_read
|
||||
|| s.writer == &fd_write
|
||||
|| s.closer == &fd_close
|
||||
|| s.closer == &fd_close_static
|
||||
|| s.copier == &fd_copy;
|
||||
};
|
||||
|
||||
// Returns the file descriptor for a given [io::stream]. If there is no fd
|
||||
// associated with this stream, void is returned.
|
||||
export fn streamfd(s: *io::stream) (int | void) = {
|
||||
for (!is_fdstream(s)) {
|
||||
s = match (io::source(s)) {
|
||||
errors::unsupported => return,
|
||||
s: *io::stream => s,
|
||||
};
|
||||
};
|
||||
let stream = s: *fd_stream;
|
||||
return stream.fd;
|
||||
};
|
||||
|
||||
fn fd_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
|
||||
let stream = s: *fd_stream;
|
||||
return match (rt::read(stream.fd, buf: *[*]u8, len(buf))) {
|
||||
err: rt::errno => errors::errno(err),
|
||||
n: size => switch (n) {
|
||||
0 => io::EOF,
|
||||
* => n,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
fn fd_write(s: *io::stream, buf: const []u8) (size | io::error) = {
|
||||
let stream = s: *fd_stream;
|
||||
return match (rt::write(stream.fd, buf: *const [*]u8, len(buf))) {
|
||||
err: rt::errno => errors::errno(err),
|
||||
n: size => n,
|
||||
};
|
||||
};
|
||||
|
||||
fn fd_close(s: *io::stream) void = {
|
||||
let stream = s: *fd_stream;
|
||||
rt::close(stream.fd);
|
||||
free(s.name);
|
||||
free(stream);
|
||||
};
|
||||
|
||||
fn fd_close_static(s: *io::stream) void = {
|
||||
let stream = s: *fd_stream;
|
||||
rt::close(stream.fd);
|
||||
free(stream);
|
||||
};
|
||||
|
||||
def SENDFILE_MAX: size = 2147479552z;
|
||||
|
||||
fn fd_copy(to: *io::stream, from: *io::stream) (size | io::error) = {
|
||||
if (!is_fdstream(from)) {
|
||||
return errors::unsupported;
|
||||
};
|
||||
|
||||
let to = to: *fd_stream, from = from: *fd_stream;
|
||||
let sum = 0z;
|
||||
for (true) {
|
||||
let n = match (rt::sendfile(to.fd, from.fd,
|
||||
null, SENDFILE_MAX)) {
|
||||
err: rt::errno => switch (err) {
|
||||
rt::EINVAL => {
|
||||
if (sum == 0) {
|
||||
return errors::unsupported;
|
||||
};
|
||||
return errors::errno(err);
|
||||
},
|
||||
* => return errors::errno(err),
|
||||
},
|
||||
n: size => switch (n) {
|
||||
0 => return sum,
|
||||
* => n,
|
||||
},
|
||||
};
|
||||
sum += n;
|
||||
};
|
||||
return sum;
|
||||
};
|
||||
|
||||
fn fd_seek(
|
||||
s: *io::stream,
|
||||
off: io::off,
|
||||
whence: io::whence,
|
||||
) (io::off | io::error) = {
|
||||
let stream = s: *fd_stream;
|
||||
return match (rt::lseek(stream.fd, off: i64, whence: uint)) {
|
||||
err: rt::errno => errors::errno(err),
|
||||
n: i64 => n: io::off,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,62 @@
|
||||
use bytes;
|
||||
use io;
|
||||
use strings;
|
||||
|
||||
type fixed_stream = struct {
|
||||
stream: io::stream,
|
||||
buf: []u8,
|
||||
};
|
||||
|
||||
// Creates an [io::stream] for a fixed, caller-supplied buffer. Supports either
|
||||
// read or write, but not both. The program aborts if writes would exceed the
|
||||
// buffer's capacity.
|
||||
export fn fixed(in: []u8, mode: io::mode) *io::stream = {
|
||||
let s = alloc(fixed_stream {
|
||||
stream = io::stream {
|
||||
name = "<bufio::fixed>",
|
||||
...
|
||||
},
|
||||
buf = in,
|
||||
});
|
||||
if (mode & io::mode::READ == io::mode::READ) {
|
||||
assert(mode & io::mode::WRITE != io::mode::WRITE);
|
||||
s.stream.reader = &fixed_read;
|
||||
};
|
||||
if (mode & io::mode::WRITE == io::mode::WRITE) {
|
||||
assert(mode & io::mode::READ != io::mode::READ);
|
||||
s.stream.writer = &fixed_write;
|
||||
};
|
||||
return &s.stream;
|
||||
};
|
||||
|
||||
fn fixed_read(s: *io::stream, buf: []u8) (size | io::error | io::EOF) = {
|
||||
let stream = s: *fixed_stream;
|
||||
if (len(stream.buf) == 0) {
|
||||
return io::EOF;
|
||||
};
|
||||
const n = if (len(buf) > len(stream.buf)) len(stream.buf) else len(buf);
|
||||
buf[..n] = stream.buf[..n];
|
||||
stream.buf = stream.buf[n..];
|
||||
return n;
|
||||
};
|
||||
|
||||
fn fixed_write(s: *io::stream, buf: const []u8) (size | io::error) = {
|
||||
let stream = s: *fixed_stream;
|
||||
if (len(stream.buf) == 0) {
|
||||
abort("bufio::fixed buffer exceeded");
|
||||
};
|
||||
const n = if (len(buf) > len(stream.buf)) len(stream.buf) else len(buf);
|
||||
stream.buf[..n] = buf[..n];
|
||||
stream.buf = stream.buf[n..];
|
||||
return n;
|
||||
};
|
||||
|
||||
@test fn fixed() void = {
|
||||
// TODO: add a read test too
|
||||
static let buf: [1024]u8 = [0...];
|
||||
let stream = fixed(buf, io::mode::WRITE);
|
||||
let n = 0z;
|
||||
n += io::write(stream, strings::toutf8("hello ")) as size;
|
||||
n += io::write(stream, strings::toutf8("world")) as size;
|
||||
assert(bytes::equal(buf[..n], strings::toutf8("hello world")));
|
||||
};
|
||||
@ -0,0 +1,432 @@
|
||||
// A format string consists of a string of literal characters, to be printed
|
||||
// verbatim, and format sequences, which describe how to format arguments from
|
||||
// a set of variadic parameters for printing.
|
||||
//
|
||||
// A format sequence is enclosed in curly braces '{}'. An empty sequence takes
|
||||
// the next argument from the parameter list, in order. A specific parameter may
|
||||
// be selected by indexing it from zero: '{0}', '{1}', and so on. To print '{',
|
||||
// use '{{', and for '}', use '}}'.
|
||||
//
|
||||
// You may use a colon to add format modifiers; for example, '{:x}' will format
|
||||
// an argument in hexadecimal, and '{3:-10}' will left-align the 3rd argument to
|
||||
// at least 10 characters.
|
||||
//
|
||||
// The format modifiers takes the form of an optional flag character:
|
||||
//
|
||||
// 0: Numeric values are zero-padded up to the required width.
|
||||
// -: The value shall be left-aligned, and spaces inserted on the right to meet
|
||||
// the required width. '-' takes precedence over '0' if both are used.
|
||||
// : (a space) insert a space before positive numbers, where '-' would be if it
|
||||
// were negative.
|
||||
// +: insert a '+' before positive numbers, where '-' would be if it were
|
||||
// negative. '+' takes precedence over ' ' if both are used.
|
||||
//
|
||||
// Following the flag, an optional decimal number shall specify the minimum
|
||||
// width of this field. If '0' or '-' were not given, the default behavior shall
|
||||
// be to pad with spaces to achieve the necessary width.
|
||||
//
|
||||
// Following the width, an optional precision may be given as a decimal number
|
||||
// following a '.' character. For integer types, this gives the minimum number
|
||||
// of digits to include. For floating types, this gives the number of digits
|
||||
// following the radix to include.
|
||||
//
|
||||
// Following the precision, an optional character controls the output format:
|
||||
//
|
||||
// x, X: print in lowercase or uppercase hexadecimal
|
||||
// o, b: print in octal or binary
|
||||
//
|
||||
// TODO: Expand this with more format modifiers
|
||||
use ascii;
|
||||
use bufio;
|
||||
use encoding::utf8;
|
||||
use io;
|
||||
use os;
|
||||
use strconv;
|
||||
use strings;
|
||||
use types;
|
||||
|
||||
// Tagged union of all types which are formattable.
|
||||
export type formattable =
|
||||
(...types::numeric | uintptr | str | rune | bool | nullable *void);
|
||||
|
||||
// Formats text for printing and writes it to [os::stdout].
|
||||
export fn printf(fmt: str, args: formattable...) (io::error | size) =
|
||||
fprintf(os::stdout, fmt, args...);
|
||||
|
||||
// Formats text for printing and writes it to [os::stdout], followed by a line
|
||||
// feed.
|
||||
export fn printfln(fmt: str, args: formattable...) (io::error | size) =
|
||||
fprintfln(os::stdout, fmt, args...);
|
||||
|
||||
// Formats text for printing and writes it to [os::stderr].
|
||||
export fn errorf(fmt: str, args: formattable...) (io::error | size) =
|
||||
fprintf(os::stderr, fmt, args...);
|
||||
|
||||
// Formats text for printing and writes it to [os::stderr], followed by a line
|
||||
// feed.
|
||||
export fn errorfln(fmt: str, args: formattable...) (io::error | size) =
|
||||
fprintfln(os::stderr, fmt, args...);
|
||||
|
||||
// Formats text for printing and writes it into a heap-allocated string. The
|
||||
// caller must free the return value.
|
||||
export fn asprintf(fmt: str, args: formattable...) str = {
|
||||
let buf = bufio::dynamic(io::mode::WRITE);
|
||||
assert(fprintf(buf, fmt, args...) is size);
|
||||
return strings::fromutf8_unsafe(bufio::finish(buf));
|
||||
};
|
||||
|
||||
// Formats text for printing and writes it into a caller supplied buffer. The
|
||||
// returned string is borrowed from this buffer.
|
||||
export fn bsprintf(buf: []u8, fmt: str, args: formattable...) str = {
|
||||
let sink = bufio::fixed(buf, io::mode::WRITE);
|
||||
let l = fprintf(sink, fmt, args...) as size;
|
||||
return strings::fromutf8_unsafe(buf[..l]);
|
||||
};
|
||||
|
||||
// Formats text for printing and writes it to [os::stderr], followed by a line
|
||||
// feed, then exits the program with an error status.
|
||||
export @noreturn fn fatal(fmt: str, args: formattable...) void = {
|
||||
fprintfln(os::stderr, fmt, args...);
|
||||
os::exit(1);
|
||||
};
|
||||
|
||||
// Formats text for printing and writes it to an [io::stream], followed by a
|
||||
// line feed.
|
||||
export fn fprintfln(
|
||||
s: *io::stream,
|
||||
fmt: str,
|
||||
args: formattable...
|
||||
) (io::error | size) = {
|
||||
return fprintf(s, fmt, args...)? + io::write(s, ['\n': u32: u8])?;
|
||||
};
|
||||
|
||||
// Formats values for printing using the default format modifiers and writes
|
||||
// them to [os::stdout] separated by spaces
|
||||
export fn print(args: formattable...) (io::error | size) =
|
||||
fprint(os::stdout, args...);
|
||||
|
||||
// Formats values for printing using the default format modifiers and writes
|
||||
// them to [os::stdout] separated by spaces and followed by a line feed
|
||||
export fn println(args: formattable...) (io::error | size) =
|
||||
fprintln(os::stdout, args...);
|
||||
|
||||
// Formats values for printing using the default format modifiers and writes
|
||||
// them to [os::stderr] separated by spaces
|
||||
export fn error(args: formattable...) (io::error | size) =
|
||||
fprint(os::stderr, args...);
|
||||
|
||||
// Formats values for printing using the default format modifiers and writes
|
||||
// them to [os::stderr] separated by spaces and followed by a line feed
|
||||
export fn errorln(args: formattable...) (io::error | size) =
|
||||
fprintln(os::stderr, args...);
|
||||
|
||||
// Formats values for printing using the default format modifiers and writes
|
||||
// them into a heap-allocated string separated by spaces. The caller must free
|
||||
// the return value.
|
||||
export fn asprint(args: formattable...) str = {
|
||||
let buf = bufio::dynamic(io::mode::WRITE);
|
||||
assert(fprint(buf, args...) is size);
|
||||
return strings::fromutf8_unsafe(bufio::finish(buf));
|
||||
};
|
||||
|
||||
// Formats values for printing using the default format modifiers and writes
|
||||
// them into a caller supplied buffer separated by spaces. The returned string
|
||||
// is borrowed from this buffer.
|
||||
export fn bsprint(buf: []u8, args: formattable...) str = {
|
||||
let sink = bufio::fixed(buf, io::mode::WRITE);
|
||||
assert(fprint(sink, args...) is size);
|
||||
return strings::fromutf8_unsafe(buf);
|
||||
};
|
||||
|
||||
// Formats values for printing using the default format modifiers and writes
|
||||
// them to an [io::stream] separated by spaces and followed by a line feed
|
||||
export fn fprintln(s: *io::stream, args: formattable...) (io::error | size) = {
|
||||
return fprint(s, args...)? + io::write(s, ['\n': u32: u8])?;
|
||||
};
|
||||
|
||||
// Formats values for printing using the default format modifiers and writes
|
||||
// them to an [io::stream] separated by spaces
|
||||
export fn fprint(s: *io::stream, args: formattable...) (io::error | size) = {
|
||||
let mod = modifiers { base = strconv::base::DEC, ... };
|
||||
let n = 0z;
|
||||
for (let i = 0z; i < len(args); i += 1) {
|
||||
n += format(s, args[i], &mod)?;
|
||||
if (i != len(args) - 1) {
|
||||
n += io::write(s, [' ': u32: u8])?;
|
||||
};
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
type negation = enum {
|
||||
NONE,
|
||||
SPACE,
|
||||
PLUS,
|
||||
};
|
||||
|
||||
type padding = enum {
|
||||
ALIGN_RIGHT,
|
||||
ALIGN_LEFT,
|
||||
ZEROES,
|
||||
};
|
||||
|
||||
type modifiers = struct {
|
||||
padding: padding,
|
||||
negation: negation,
|
||||
width: uint,
|
||||
precision: uint,
|
||||
base: strconv::base,
|
||||
};
|
||||
|
||||
type modflags = enum uint {
|
||||
NONE = 0,
|
||||
ZERO = 1 << 0,
|
||||
MINUS = 1 << 1,
|
||||
SPACE = 1 << 2,
|
||||
PLUS = 1 << 3,
|
||||
};
|
||||
|
||||
// Formats text for printing and writes it to an [io::stream].
|
||||
export fn fprintf(
|
||||
s: *io::stream,
|
||||
fmt: str,
|
||||
args: formattable...
|
||||
) (io::error | size) = {
|
||||
let n = 0z, i = 0z;
|
||||
let iter = strings::iter(fmt);
|
||||
for (true) {
|
||||
let r: rune = match (strings::next(&iter)) {
|
||||
void => break,
|
||||
r: rune => r,
|
||||
};
|
||||
|
||||
if (r == '{') {
|
||||
r = match (strings::next(&iter)) {
|
||||
void => abort("Invalid format string (unterminated '{')"),
|
||||
r: rune => r,
|
||||
};
|
||||
|
||||
const arg = if (r == '{') {
|
||||
n += io::write(s, utf8::encoderune('{'))?;
|
||||
continue;
|
||||
} else if (ascii::isdigit(r)) {
|
||||
strings::push(&iter, r);
|
||||
args[scan_uint(&iter)];
|
||||
} else {
|
||||
strings::push(&iter, r);
|
||||
i += 1;
|
||||
args[i - 1];
|
||||
};
|
||||
|
||||
let mod = modifiers { base = strconv::base::DEC, ... };
|
||||
r = match (strings::next(&iter)) {
|
||||
void => abort("Invalid format string (unterminated '{')"),
|
||||
r: rune => r,
|
||||
};
|
||||
switch (r) {
|
||||
':' => scan_modifiers(&iter, &mod),
|
||||
'}' => void,
|
||||
* => abort("Invalid format string"),
|
||||
};
|
||||
|
||||
n += format(s, arg, &mod)?;
|
||||
} else if (r == '}') {
|
||||
match (strings::next(&iter)) {
|
||||
void => abort("Invalid format string (hanging '}')"),
|
||||
r: rune => assert(r == '}', "Invalid format string (hanging '}')"),
|
||||
};
|
||||
|
||||
n += io::write(s, utf8::encoderune('}'))?;
|
||||
} else {
|
||||
n += io::write(s, utf8::encoderune(r))?;
|
||||
};
|
||||
};
|
||||
|
||||
return n;
|
||||
};
|
||||
|
||||
fn format(out: *io::stream, arg: formattable, mod: *modifiers) (size | io::error) = {
|
||||
let z = format_raw(io::empty, arg, mod)?;
|
||||
|
||||
let pad: []u8 = [];
|
||||
if (z < mod.width: size) {
|
||||
pad = utf8::encoderune(switch (mod.padding) {
|
||||
padding::ZEROES => '0',
|
||||
* => ' ',
|
||||
});
|
||||
};
|
||||
|
||||
if (mod.padding == padding::ALIGN_LEFT) {
|
||||
format_raw(out, arg, mod);
|
||||
};
|
||||
|
||||
for (z < mod.width: size) {
|
||||
z += io::write(out, pad)?;
|
||||
};
|
||||
|
||||
if (mod.padding != padding::ALIGN_LEFT) {
|
||||
format_raw(out, arg, mod);
|
||||
};
|
||||
|
||||
return z;
|
||||
};
|
||||
|
||||
fn format_raw(
|
||||
out: *io::stream,
|
||||
arg: formattable,
|
||||
mod: *modifiers,
|
||||
) (size | io::error) = match (arg) {
|
||||
s: str => io::write(out, strings::toutf8(s)),
|
||||
r: rune => io::write(out, utf8::encoderune(r)),
|
||||
b: bool => io::write(out, strings::toutf8(if (b) "true" else "false")),
|
||||
n: types::numeric => {
|
||||
let s = strconv::numerictosb(n, mod.base);
|
||||
io::write(out, strings::toutf8(s));
|
||||
},
|
||||
p: uintptr => {
|
||||
let s = strconv::uptrtosb(p, mod.base);
|
||||
io::write(out, strings::toutf8(s));
|
||||
},
|
||||
v: nullable *void => match (v) {
|
||||
v: *void => {
|
||||
let s = strconv::uptrtosb(v: uintptr,
|
||||
strconv::base::HEX_LOWER);
|
||||
let n = io::write(out, strings::toutf8("0x"))?;
|
||||
n += io::write(out, strings::toutf8(s))?;
|
||||
n;
|
||||
},
|
||||
null => format(out, "(null)", mod),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
fn scan_uint(iter: *strings::iterator) uint = {
|
||||
let num: []u8 = [];
|
||||
defer free(num);
|
||||
for (true) {
|
||||
let r = match (strings::next(iter)) {
|
||||
void => abort("Invalid format string (unterminated '{')"),
|
||||
r: rune => r,
|
||||
};
|
||||
|
||||
if (ascii::isdigit(r)) {
|
||||
append(num, r: u32: u8);
|
||||
} else {
|
||||
strings::push(iter, r);
|
||||
match (strconv::stou(strings::fromutf8(num))) {
|
||||
(strconv::invalid | strconv::overflow) =>
|
||||
abort("Invalid format string (invalid index)"),
|
||||
u: uint => return u,
|
||||
};
|
||||
};
|
||||
};
|
||||
abort("unreachable");
|
||||
};
|
||||
|
||||
fn scan_modifier_flags(iter: *strings::iterator, mod: *modifiers) void = {
|
||||
let flags = modflags::NONE;
|
||||
|
||||
for (true) {
|
||||
let r = match (strings::next(iter)) {
|
||||
void => abort("Invalid format string (unterminated '{')"),
|
||||
r: rune => r,
|
||||
};
|
||||
|
||||
switch (r) {
|
||||
'0' => flags |= modflags::ZERO,
|
||||
'-' => flags |= modflags::MINUS,
|
||||
' ' => flags |= modflags::SPACE,
|
||||
'+' => flags |= modflags::PLUS,
|
||||
* => {
|
||||
strings::push(iter, r);
|
||||
break;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
mod.padding = if (flags & modflags::MINUS != 0)
|
||||
padding::ALIGN_LEFT
|
||||
else if (flags & modflags::ZERO != 0)
|
||||
padding::ZEROES
|
||||
else
|
||||
padding::ALIGN_RIGHT;
|
||||
|
||||
mod.negation = if (flags & modflags::PLUS != 0)
|
||||
negation::PLUS
|
||||
else if (flags & modflags::SPACE != 0)
|
||||
negation::SPACE
|
||||
else
|
||||
negation::NONE;
|
||||
};
|
||||
|
||||
fn scan_modifier_width(iter: *strings::iterator, mod: *modifiers) void = {
|
||||
let r = match (strings::next(iter)) {
|
||||
void => abort("Invalid format string (unterminated '{')"),
|
||||
r: rune => r,
|
||||
};
|
||||
|
||||
let is_digit = ascii::isdigit(r);
|
||||
strings::push(iter, r);
|
||||
|
||||
if (is_digit) {
|
||||
mod.width = scan_uint(iter);
|
||||
};
|
||||
};
|
||||
|
||||
fn scan_modifier_precision(iter: *strings::iterator, mod: *modifiers) void = {
|
||||
let r = match (strings::next(iter)) {
|
||||
void => abort("Invalid format string (unterminated '{')"),
|
||||
r: rune => r,
|
||||
};
|
||||
|
||||
if (r == '.') {
|
||||
mod.precision = scan_uint(iter);
|
||||
} else {
|
||||
strings::push(iter, r);
|
||||
};
|
||||
};
|
||||
|
||||
fn scan_modifier_base(iter: *strings::iterator, mod: *modifiers) void = {
|
||||
let r = match (strings::next(iter)) {
|
||||
void => abort("Invalid format string (unterminated '{')"),
|
||||
r: rune => r,
|
||||
};
|
||||
|
||||
switch (r) {
|
||||
'x' => mod.base = strconv::base::HEX_LOWER,
|
||||
'X' => mod.base = strconv::base::HEX_UPPER,
|
||||
'o' => mod.base = strconv::base::OCT,
|
||||
'b' => mod.base = strconv::base::BIN,
|
||||
* => strings::push(iter, r),
|
||||
};
|
||||
};
|
||||
|
||||
fn scan_modifiers(iter: *strings::iterator, mod: *modifiers) void = {
|
||||
scan_modifier_flags(iter, mod);
|
||||
scan_modifier_width(iter, mod);
|
||||
scan_modifier_precision(iter, mod);
|
||||
scan_modifier_base(iter, mod);
|
||||
|
||||
// eat '}'
|
||||
let terminated = match (strings::next(iter)) {
|
||||
void => false,
|
||||
r: rune => r == '}',
|
||||
};
|
||||
assert(terminated, "Invalid format string (unterminated '{')");
|
||||
};
|
||||
|
||||
@test fn fmt() void = {
|
||||
let buf: [1024]u8 = [0...];
|
||||
assert(bsprintf(buf, "hello world") == "hello world");
|
||||
assert(bsprintf(buf, "{} {}", "hello", "world") == "hello world");
|
||||
assert(bsprintf(buf, "{0} {1}", "hello", "world") == "hello world");
|
||||
assert(bsprintf(buf, "{0} {0}", "hello", "world") == "hello hello");
|
||||
assert(bsprintf(buf, "{1} {0} {1}", "hello", "world") == "world hello world");
|
||||
assert(bsprintf(buf, "x: {:08x}", 0xBEEF) == "x: 0000beef");
|
||||
assert(bsprintf(buf, "x: {:8X}", 0xBEEF) == "x: BEEF");
|
||||
assert(bsprintf(buf, "x: {:-8X}", 0xBEEF) == "x: BEEF ");
|
||||
assert(bsprintf(buf, "x: {:o}", 0o755) == "x: 755");
|
||||
assert(bsprintf(buf, "x: {:b}", 0b11011) == "x: 11011");
|
||||
assert(bsprintf(buf, "{} {} {} {}", true, false, null, 'x')
|
||||
== "true false (null) x");
|
||||
};
|
||||
@ -0,0 +1,177 @@
|
||||
use errors;
|
||||
use io;
|
||||
use path;
|
||||
|
||||
// Closes a filesystem. The fs cannot be used after this function is called.
|
||||
export fn close(fs: *fs) void = {
|
||||
match (fs.close) {
|
||||
null => void,
|
||||
f: *closefunc => f(fs),
|
||||
};
|
||||
};
|
||||
|
||||
// Opens a file. If no flags are provided, the default read/write mode is
|
||||
// RDONLY.
|
||||
export fn open(fs: *fs, path: str, flags: flags...) (*io::stream | error) = {
|
||||
return match (fs.open) {
|
||||
null => errors::unsupported,
|
||||
f: *openfunc => f(fs, path, flags...),
|
||||
};
|
||||
};
|
||||
|
||||
// Creates a new file and opens it for writing. If no flags are provided, the
|
||||
// default read/write mode is WRONLY.
|
||||
//
|
||||
// Only the permission bits of the mode are used. If other bits are set, they
|
||||
// are discarded.
|
||||
export fn create(
|
||||
fs: *fs,
|
||||
path: str,
|
||||
mode: mode,
|
||||
flags: flags...
|
||||
) (*io::stream | error) = {
|
||||
mode = mode & 0o777;
|
||||
return match (fs.create) {
|
||||
null => errors::unsupported,
|
||||
f: *createfunc => f(fs, path, mode, flags...),
|
||||
};
|
||||
};
|
||||
|
||||
// Removes a file.
|
||||
export fn remove(fs: *fs, path: str) (void | error) = {
|
||||
return match (fs.remove) {
|
||||
null => errors::unsupported,
|
||||
f: *removefunc => f(fs, path),
|
||||
};
|
||||
};
|
||||
|
||||
// Returns an iterator for a path, which yields the contents of a directory.
|
||||
// Pass empty string to yield from the root. The order in which entries are
|
||||
// returned is undefined.
|
||||
export fn iter(fs: *fs, path: str) (*iterator | error) = {
|
||||
return match (fs.iter) {
|
||||
null => errors::unsupported,
|
||||
f: *iterfunc => f(fs, path),
|
||||
};
|
||||
};
|
||||
|
||||
// Obtains information about a file or directory. If the target is a symlink,
|
||||
// information is returned about the link, not its target.
|
||||
export fn stat(fs: *fs, path: str) (filestat | error) = {
|
||||
return match (fs.stat) {
|
||||
null => errors::unsupported,
|
||||
f: *statfunc => f(fs, path),
|
||||
};
|
||||
};
|
||||
|
||||
// Opens a new filesystem for a subdirectory. The subdirectory must be closed
|
||||
// separately from the parent filesystem, and its lifetime can outlive that of
|
||||
// its parent.
|
||||
export fn subdir(fs: *fs, path: str) (*fs | error) = {
|
||||
return match (fs.subdir) {
|
||||
null => errors::unsupported,
|
||||
f: *subdirfunc => f(fs, path),
|
||||
};
|
||||
};
|
||||
|
||||
// Creates a directory.
|
||||
export fn mkdir(fs: *fs, path: str) (void | error) = {
|
||||
return match (fs.mkdir) {
|
||||
null => errors::unsupported,
|
||||
f: *mkdirfunc => f(fs, path),
|
||||
};
|
||||
};
|
||||
|
||||
// Makes a directory, and all non-extant directories in its path.
|
||||
export fn mkdirs(fs: *fs, path: str) (void | error) = {
|
||||
let parent = path::dirname(path);
|
||||
if (path != parent) {
|
||||
match (mkdirs(fs, parent)) {
|
||||
errors::exists => void,
|
||||
err: error => return err,
|
||||
void => void,
|
||||
};
|
||||
};
|
||||
return mkdir(fs, path);
|
||||
};
|
||||
|
||||
// Removes a directory. The target directory must be empty; see [rmdirall] to
|
||||
// remove its contents as well.
|
||||
export fn rmdir(fs: *fs, path: str) (void | error) = {
|
||||
if (path == "") {
|
||||
return errors::invalid;
|
||||
};
|
||||
return match (fs.rmdir) {
|
||||
null => errors::unsupported,
|
||||
f: *rmdirfunc => f(fs, path),
|
||||
};
|
||||
};
|
||||
|
||||
// Removes a directory, and anything in it.
|
||||
export fn rmdirall(fs: *fs, path: str) (void | error) = {
|
||||
let it = iter(fs, path)?;
|
||||
for (true) match (next(it)) {
|
||||
ent: dirent => {
|
||||
if (ent.name == "." || ent.name == "..") {
|
||||
continue;
|
||||
};
|
||||
let p = path::join(path, ent.name);
|
||||
defer free(p);
|
||||
switch (ent.ftype) {
|
||||
mode::DIR => rmdirall(fs, p)?,
|
||||
* => remove(fs, p)?,
|
||||
};
|
||||
},
|
||||
void => break,
|
||||
};
|
||||
if (path != "") {
|
||||
return rmdir(fs, path);
|
||||
};
|
||||
};
|
||||
|
||||
// Creates a directory and returns a subdir for it. Some filesystems support
|
||||
// doing this operation atomically, but if not, a fallback is used.
|
||||
export fn mksubdir(fs: *fs, path: str) (*fs | error) = {
|
||||
return match (fs.mksubdir) {
|
||||
null => {
|
||||
mkdir(fs, path)?;
|
||||
subdir(fs, path);
|
||||
},
|
||||
f: *mksubdirfunc => f(fs, path),
|
||||
};
|
||||
};
|
||||
|
||||
// Changes mode flags on a file or directory. Type bits are discared.
|
||||
export fn chmod(fs: *fs, path: str, mode: mode) (void | error) = {
|
||||
mode &= 0o755;
|
||||
|
||||
return match (fs.chmod) {
|
||||
f: *chmodfunc => f(fs, path, mode),
|
||||
null => abort(),
|
||||
};
|
||||
};
|
||||
|
||||
// Changes ownership of a file.
|
||||
export fn chown(fs: *fs, path: str, uid: uint, gid: uint) (void | error) = {
|
||||
return match (fs.chown) {
|
||||
f: *chownfunc => f(fs, path, uid, gid),
|
||||
null => abort(),
|
||||
};
|
||||
};
|
||||
|
||||
// Resolves a path to its absolute, normalized value. This consoldates ./ and
|
||||
// ../ sequences, roots the path, and returns a new path. The caller must free
|
||||
// the return value.
|
||||
export fn resolve(fs: *fs, path: str) str = {
|
||||
match (fs.resolve) {
|
||||
f: *resolvefunc => return f(fs, path),
|
||||
null => void,
|
||||
};
|
||||
abort(); // TODO
|
||||
};
|
||||
|
||||
// Returns the next directory entry from an interator, or void if none remain.
|
||||
// It is a programming error to call this again after it has returned void. The
|
||||
// file stat returned may only have the type bits set on the file mode; callers
|
||||
// should call [fs::stat] to obtain the detailed file mode.
|
||||
export fn next(iter: *iterator) (dirent | void) = iter.next(iter);
|
||||
@ -0,0 +1,506 @@
|
||||
use types;
|
||||
|
||||
fn f64bits(a: f64) u64 = *(&a: *u64);
|
||||
|
||||
type r128 = struct {
|
||||
hi: u64,
|
||||
lo: u64,
|
||||
};
|
||||
|
||||
// TODO: use 128-bit integers when implemented
|
||||
fn u128mul(a: u64, b: u64) r128 = {
|
||||
const a0 = a: u32: u64, a1 = a >> 32;
|
||||
const b0 = b: u32: u64, b1 = b >> 32;
|
||||
const p00 = a0 * b0, p01 = a0 * b1, p10 = a1 * b0, p11 = a1 * b1;
|
||||
const p00_lo = p00: u32: u64, p00_hi = p00 >> 32;
|
||||
const mid1 = p10 + p00_hi;
|
||||
const mid1_lo = mid1: u32: u64, mid1_hi = mid1 >> 32;
|
||||
const mid2 = p01 + mid1_lo;
|
||||
const mid2_lo = mid2: u32: u64, mid2_hi = mid2 >> 32;
|
||||
const r_hi = p11 + mid1_hi + mid2_hi;
|
||||
const r_lo = (mid2_lo << 32) | p00_lo;
|
||||
return r128 { hi = r_hi, lo = r_lo };
|
||||
};
|
||||
|
||||
// TODO: Same as above
|
||||
fn u128rshift(lo: u64, hi: u64, s: u32) u64 = {
|
||||
assert(0 <= s);
|
||||
assert(s <= 64);
|
||||
return (hi << (64 - s)) | (lo >> s);
|
||||
};
|
||||
|
||||
fn pow5fac(value: u64) u32 = {
|
||||
const m_inv_5: u64 = 14757395258967641293; // 5 * m_inv_5 = 1 (mod 2^64)
|
||||
const n_div_5: u64 = 3689348814741910323;
|
||||
let count: u32 = 0;
|
||||
for (true) {
|
||||
assert(value != 0);
|
||||
value *= m_inv_5;
|
||||
if (value > n_div_5) break;
|
||||
count += 1;
|
||||
};
|
||||
return count;
|
||||
};
|
||||
|
||||
fn ibool(b: bool) u8 = if (b) 1 else 0;
|
||||
|
||||
fn pow5multiple(v: u64, p: u32) bool = pow5fac(v) >= p;
|
||||
|
||||
fn pow2multiple(v: u64, p: u32) bool = {
|
||||
assert(v > 0);
|
||||
assert(p < 64);
|
||||
return (v & ((1u64 << p) - 1)) == 0;
|
||||
};
|
||||
|
||||
fn mulshift64(m: u64, mul: (u64, u64), j: u32) u64 = {
|
||||
// m is maximum 55 bits
|
||||
let r0 = u128mul(m, mul.0), r1 = u128mul(m, mul.1);
|
||||
const sum = r1.lo + r0.hi;
|
||||
r1.hi += ibool(sum < r0.hi);
|
||||
return u128rshift(sum, r1.hi, j - 64);
|
||||
};
|
||||
|
||||
fn mulshiftall64(m: u64, mul: (u64, u64), j: i32, mm_shift: u32) (u64, u64, u64) = {
|
||||
m <<= 1;
|
||||
const r0 = u128mul(m, mul.0), r1 = u128mul(m, mul.1);
|
||||
let lo = r0.lo, tmp = r0.hi, mid = r1.lo, hi = r1.hi;
|
||||
hi += ibool(mid < tmp);
|
||||
const lo2 = lo + mul.0;
|
||||
const mid2 = mid + mul.1 + ibool(lo2 < lo);
|
||||
const hi2 = hi + ibool(mid2 < mid);
|
||||
const v_plus = u128rshift(mid2, hi2, (j - 64 - 1): u32);
|
||||
const v_minus = if (mm_shift == 1) {
|
||||
const lo3 = lo - mul.0;
|
||||
const mid3 = mid - mul.1 - ibool(lo3 > lo);
|
||||
const hi3 = hi - ibool(mid3 > mid);
|
||||
u128rshift(mid3, hi3, (j - 64 - 1): u32);
|
||||
} else {
|
||||
const lo3 = lo + lo;
|
||||
const mid3 = mid + mid + ibool(lo3 < lo);
|
||||
const hi3 = hi + hi + ibool(mid3 < mid);
|
||||
const lo4 = lo3 - mul.0;
|
||||
const mid4 = mid3 - mul.1 - ibool(lo4 > lo3);
|
||||
const hi4 = hi3 - ibool(mid4 > mid3);
|
||||
u128rshift(mid4, hi4, (j - 64): u32);
|
||||
};
|
||||
const v_rounded = u128rshift(mid, hi, (j - 64 - 1): u32);
|
||||
return (v_plus, v_rounded, v_minus);
|
||||
};
|
||||
|
||||
fn log2pow5(e: u32) i32 = {
|
||||
assert(e <= 3528);
|
||||
return ((e * 1217359) >> 19): i32;
|
||||
};
|
||||
|
||||
fn ceil_log2pow5(e: u32) i32 = log2pow5(e) + 1;
|
||||
|
||||
fn pow5bits(e: u32) i32 = ceil_log2pow5(e);
|
||||
|
||||
fn log10pow2(e: u32) u32 = {
|
||||
assert(e <= 1650);
|
||||
return ((e * 78913) >> 18);
|
||||
};
|
||||
|
||||
fn log10pow5(e: u32) u32 = {
|
||||
assert(e <= 2620);
|
||||
return ((e * 732923) >> 20);
|
||||
};
|
||||
|
||||
def F64_POW5_INV_BITCOUNT: u8 = 125;
|
||||
def F64_POW5_BITCOUNT: u8 = 125;
|
||||
|
||||
const F64_POW5_INV_SPLIT2: [15][2]u64 = [
|
||||
[1, 2305843009213693952],
|
||||
[5955668970331000884, 1784059615882449851],
|
||||
[8982663654677661702, 1380349269358112757],
|
||||
[7286864317269821294, 2135987035920910082],
|
||||
[7005857020398200553, 1652639921975621497],
|
||||
[17965325103354776697, 1278668206209430417],
|
||||
[8928596168509315048, 1978643211784836272],
|
||||
[10075671573058298858, 1530901034580419511],
|
||||
[597001226353042382, 1184477304306571148],
|
||||
[1527430471115325346, 1832889850782397517],
|
||||
[12533209867169019542, 1418129833677084982],
|
||||
[5577825024675947042, 2194449627517475473],
|
||||
[11006974540203867551, 1697873161311732311],
|
||||
[10313493231639821582, 1313665730009899186],
|
||||
[12701016819766672773, 2032799256770390445],
|
||||
];
|
||||
|
||||
const POW5_INV_OFFSETS: [19]u32 = [
|
||||
0x54544554, 0x04055545, 0x10041000, 0x00400414, 0x40010000, 0x41155555,
|
||||
0x00000454, 0x00010044, 0x40000000, 0x44000041, 0x50454450, 0x55550054,
|
||||
0x51655554, 0x40004000, 0x01000001, 0x00010500, 0x51515411, 0x05555554,
|
||||
0x00000000
|
||||
];
|
||||
|
||||
const F64_POW5_SPLIT2: [13][2]u64 = [
|
||||
[0, 1152921504606846976],
|
||||
[0, 1490116119384765625],
|
||||
[1032610780636961552, 1925929944387235853],
|
||||
[7910200175544436838, 1244603055572228341],
|
||||
[16941905809032713930, 1608611746708759036],
|
||||
[13024893955298202172, 2079081953128979843],
|
||||
[6607496772837067824, 1343575221513417750],
|
||||
[17332926989895652603, 1736530273035216783],
|
||||
[13037379183483547984, 2244412773384604712],
|
||||
[1605989338741628675, 1450417759929778918],
|
||||
[9630225068416591280, 1874621017369538693],
|
||||
[665883850346957067, 1211445438634777304],
|
||||
[14931890668723713708, 1565756531257009982]
|
||||
];
|
||||
|
||||
const POW5_OFFSETS: [21]u32 = [
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x40000000, 0x59695995,
|
||||
0x55545555, 0x56555515, 0x41150504, 0x40555410, 0x44555145, 0x44504540,
|
||||
0x45555550, 0x40004000, 0x96440440, 0x55565565, 0x54454045, 0x40154151,
|
||||
0x55559155, 0x51405555, 0x00000105
|
||||
];
|
||||
|
||||
def POW5_TABLE_SIZE: u8 = 26;
|
||||
|
||||
const POW5_TABLE: [POW5_TABLE_SIZE]u64 = [
|
||||
1u64, 5u64, 25u64, 125u64, 625u64, 3125u64, 15625u64, 78125u64,
|
||||
390625u64, 1953125u64, 9765625u64, 48828125u64, 244140625u64,
|
||||
1220703125u64, 6103515625u64, 30517578125u64, 152587890625u64,
|
||||
762939453125u64, 3814697265625u64, 19073486328125u64, 95367431640625u64,
|
||||
476837158203125u64, 2384185791015625u64, 11920928955078125u64,
|
||||
59604644775390625u64, 298023223876953125u64 //, 1490116119384765625u64
|
||||
];
|
||||
|
||||
fn f64computeinvpow5(i: u32) (u64, u64) = {
|
||||
const base = ((i + POW5_TABLE_SIZE - 1) / POW5_TABLE_SIZE): u32;
|
||||
const base2 = base * POW5_TABLE_SIZE;
|
||||
const mul = F64_POW5_INV_SPLIT2[base];
|
||||
const off = base2 - i;
|
||||
if (off == 0) {
|
||||
return (mul[0], mul[1]);
|
||||
};
|
||||
const m = POW5_TABLE[off];
|
||||
const r1 = u128mul(m, mul[1]), r0 = u128mul(m, mul[0] - 1);
|
||||
let high1 = r1.hi, low1 = r1.lo, high0 = r0.hi, low0 = r0.lo;
|
||||
const sum = high0 + low1;
|
||||
if (sum < high0) {
|
||||
high1 += 1;
|
||||
};
|
||||
const delta = (pow5bits(base2) - pow5bits(i)): u32;
|
||||
const res0 = u128rshift(low0, sum, delta) + 1 +
|
||||
((POW5_INV_OFFSETS[i / 16] >> ((i % 16) << 1)) & 3);
|
||||
const res1 = u128rshift(sum, high1, delta);
|
||||
return (res0, res1);
|
||||
};
|
||||
|
||||
fn f64computepow5(i: u32) (u64, u64) = {
|
||||
const base = i / POW5_TABLE_SIZE, base2 = base * POW5_TABLE_SIZE;
|
||||
const mul = F64_POW5_SPLIT2[base];
|
||||
const off = i - base2;
|
||||
if (off == 0) {
|
||||
return (mul[0], mul[1]);
|
||||
};
|
||||
const m = POW5_TABLE[off];
|
||||
const r1 = u128mul(m, mul[1]), r0 = u128mul(m, mul[0]);
|
||||
let high1 = r1.hi, low1 = r1.lo, high0 = r0.hi, low0 = r0.lo;
|
||||
const sum = high0 + low1;
|
||||
if (sum < high0) {
|
||||
high1 += 1;
|
||||
};
|
||||
const delta = (pow5bits(i) - pow5bits(base2)): u32;
|
||||
const res0 = u128rshift(low0, sum, delta) +
|
||||
((POW5_OFFSETS[i / 16] >> ((i % 16) << 1)) & 3);
|
||||
const res1 = u128rshift(sum, high1, delta);
|
||||
return (res0, res1);
|
||||
};
|
||||
|
||||
type decf64 = struct {
|
||||
mantissa: u64,
|
||||
exponent: i32,
|
||||
};
|
||||
|
||||
def F64_MANTISSA_BITS: u64 = 52;
|
||||
def F64_EXPONENT_BITS: u64 = 11;
|
||||
def F64_EXPONENT_BIAS: u16 = 1023;
|
||||
|
||||
fn declen(n: u64) u8 = {
|
||||
assert(n <= 1e17);
|
||||
return if (n >= 1e17) 18
|
||||
else if (n >= 1e16) 17
|
||||
else if (n >= 1e15) 16
|
||||
else if (n >= 1e14) 15
|
||||
else if (n >= 1e13) 14
|
||||
else if (n >= 1e12) 13
|
||||
else if (n >= 1e11) 12
|
||||
else if (n >= 1e10) 11
|
||||
else if (n >= 1e9) 10
|
||||
else if (n >= 1e8) 9
|
||||
else if (n >= 1e7) 8
|
||||
else if (n >= 1e6) 7
|
||||
else if (n >= 1e5) 6
|
||||
else if (n >= 1e4) 5
|
||||
else if (n >= 1e3) 4
|
||||
else if (n >= 100) 3
|
||||
else if (n >= 10) 2
|
||||
else 1;
|
||||
};
|
||||
|
||||
fn bintodec(mantissa: u64, exponent: u32) decf64 = {
|
||||
let e2: i32 = 0, m2: u64 = 0;
|
||||
if (exponent == 0) {
|
||||
e2 = 1 - (F64_EXPONENT_BIAS + F64_MANTISSA_BITS): i32 - 2;
|
||||
m2 = mantissa;
|
||||
} else {
|
||||
e2 = (exponent: i32) - (F64_EXPONENT_BIAS + F64_MANTISSA_BITS): i32 - 2;
|
||||
m2 = (1u64 << F64_MANTISSA_BITS) | mantissa;
|
||||
};
|
||||
const even = (m2 & 1) == 0, accept_bounds = even;
|
||||
const mv = 4 * m2;
|
||||
const mm_shift: u32 = ibool(mantissa != 0 || exponent <= 1);
|
||||
let vp: u64 = 0, vr: u64 = 0, vm: u64 = 0;
|
||||
let e10: i32 = 0;
|
||||
let vm_trailing_zeros = false, vr_trailing_zeros = false;
|
||||
if (e2 >= 0) {
|
||||
const q = log10pow2(e2: u32) - ibool(e2 > 3);
|
||||
e10 = q: i32;
|
||||
const k = F64_POW5_INV_BITCOUNT: i32 + pow5bits(q) - 1;
|
||||
const i = -e2 + (q: i32) + k;
|
||||
let pow5 = f64computeinvpow5(q);
|
||||
const res = mulshiftall64(m2, pow5, i, mm_shift);
|
||||
vp = res.0; vr = res.1; vm = res.2;
|
||||
if (q <= 21) {
|
||||
if ((mv - 5 * (mv / 5)) == 0) {
|
||||
vr_trailing_zeros = pow5multiple(mv, q);
|
||||
} else if (accept_bounds) {
|
||||
vm_trailing_zeros = pow5multiple(mv - 1 - mm_shift, q);
|
||||
} else {
|
||||
vp -= ibool(pow5multiple(mv + 2, q));
|
||||
};
|
||||
};
|
||||
} else {
|
||||
const q = log10pow5((-e2): u32) - ibool(-e2 > 1);
|
||||
e10 = e2 + (q: i32);
|
||||
const i = -e2 - (q: i32);
|
||||
const k = pow5bits(i: u32) - F64_POW5_BITCOUNT: i32;
|
||||
const j = (q: i32) - k;
|
||||
let pow5 = f64computepow5(i: u32);
|
||||
const res = mulshiftall64(m2, pow5, j, mm_shift);
|
||||
vp = res.0; vr = res.1; vm = res.2;
|
||||
if (q <= 1) {
|
||||
vr_trailing_zeros = true;
|
||||
if (accept_bounds) {
|
||||
vm_trailing_zeros = mm_shift == 1;
|
||||
} else {
|
||||
vp -= 1;
|
||||
};
|
||||
} else if (q < 63) {
|
||||
vr_trailing_zeros = pow2multiple(mv, q);
|
||||
};
|
||||
};
|
||||
let removed: i32 = 0, last_removed_digit: u8 = 0;
|
||||
let output: u64 = 0;
|
||||
if (vm_trailing_zeros || vr_trailing_zeros) {
|
||||
for (true) {
|
||||
const vpby10 = vp / 10, vmby10 = vm / 10;
|
||||
if (vpby10 <= vmby10) break;
|
||||
const vmmod10 = (vm: u32) - 10 * (vmby10: u32);
|
||||
const vrby10 = vr / 10;
|
||||
const vrmod10 = (vr: u32) - 10 * (vrby10: u32);
|
||||
vm_trailing_zeros = vm_trailing_zeros && (vmmod10 == 0);
|
||||
vr_trailing_zeros = vr_trailing_zeros &&
|
||||
(last_removed_digit == 0);
|
||||
last_removed_digit = vrmod10: u8;
|
||||
vr = vrby10; vp = vpby10; vm = vmby10;
|
||||
removed += 1;
|
||||
};
|
||||
if (vm_trailing_zeros) {
|
||||
for (true) {
|
||||
const vmby10 = vm / 10;
|
||||
const vmmod10 = (vm: u32) - 10 * (vmby10: u32);
|
||||
if (vmmod10 != 0) break;
|
||||
const vpby10 = vp / 10, vrby10 = vr / 10;
|
||||
const vrmod10 = (vr: u32) - 10 * (vrby10: u32);
|
||||
vr_trailing_zeros = vr_trailing_zeros &&
|
||||
(last_removed_digit == 0);
|
||||
last_removed_digit = vrmod10: u8;
|
||||
vr = vrby10; vp = vpby10; vm = vmby10;
|
||||
removed += 1;
|
||||
};
|
||||
};
|
||||
if (vr_trailing_zeros && last_removed_digit == 5 && (vr & 1 == 0)) {
|
||||
// round to even
|
||||
last_removed_digit = 4;
|
||||
};
|
||||
output = vr + ibool((vr == vm &&
|
||||
(!accept_bounds || !vm_trailing_zeros)) || last_removed_digit >= 5);
|
||||
} else {
|
||||
let round_up = false;
|
||||
const vpby100 = vp / 100, vmby100 = vm / 100;
|
||||
if (vpby100 > vmby100) {
|
||||
const vrby100 = vr / 100;
|
||||
const vrmod100 = (vr: u32) - 100 * (vrby100: u32);
|
||||
round_up = vrmod100 >= 50;
|
||||
vr = vrby100; vp = vpby100; vm = vmby100;
|
||||
removed += 2;
|
||||
};
|
||||
for (true) {
|
||||
const vmby10 = vm / 10, vpby10 = vp / 10;
|
||||
if (vpby10 <= vmby10) break;
|
||||
const vrby10 = vr / 10;
|
||||
const vrmod10 = (vr: u32) - 10 * (vrby10: u32);
|
||||
round_up = vrmod10 >= 5;
|
||||
vr = vrby10; vp = vpby10; vm = vmby10;
|
||||
removed += 1;
|
||||
};
|
||||
output = vr + ibool(vr == vm || round_up);
|
||||
};
|
||||
const exp = e10 + removed;
|
||||
return decf64 { exponent = exp, mantissa = output };
|
||||
};
|
||||
|
||||
fn encode(buf: []u8, v: decf64) size = {
|
||||
const zch = '0': u32: u8;
|
||||
const n = v.mantissa, e = v.exponent, olen = declen(n);
|
||||
const exp = e + olen: i32 - 1;
|
||||
// use scientific notation for numbers whose exponent is beyond the
|
||||
// precision available for f64 type
|
||||
if (exp > -17 && exp < 17) {
|
||||
if (e >= 0) {
|
||||
let k = exp;
|
||||
for (let a = e; a > 0; a -= 1) {
|
||||
buf[k] = zch;
|
||||
k -= 1;
|
||||
};
|
||||
let m = n;
|
||||
for (k >= 0; k -= 1) {
|
||||
const mby10 = m / 10;
|
||||
const mmod10 = (m: u32) - 10 * (mby10: u32);
|
||||
buf[k] = zch + mmod10: u8;
|
||||
m = mby10;
|
||||
};
|
||||
return (e + olen: i32): size;
|
||||
} else if (exp < 0) {
|
||||
buf[0] = zch;
|
||||
buf[1] = '.': u32: u8;
|
||||
let k = -e + 1;
|
||||
let m = n;
|
||||
for (let a = olen: i32; a > 0; a -= 1) {
|
||||
const mby10 = m / 10;
|
||||
const mmod10 = (m: u32) - 10 * (mby10: u32);
|
||||
buf[k] = zch + mmod10: u8;
|
||||
k -= 1;
|
||||
m = mby10;
|
||||
};
|
||||
for (k > 1; k -= 1) {
|
||||
buf[k] = zch;
|
||||
};
|
||||
return (-e + 2): size;
|
||||
} else {
|
||||
let k = olen: i32;
|
||||
let m = n;
|
||||
for (let a = -e; a > 0; a -= 1) {
|
||||
const mby10 = m / 10;
|
||||
const mmod10 = (m: u32) - 10 * (mby10: u32);
|
||||
buf[k] = zch + mmod10: u8;
|
||||
k -= 1;
|
||||
m = mby10;
|
||||
};
|
||||
buf[k] = '.': u32: u8;
|
||||
k -= 1;
|
||||
for (k >= 0; k -= 1) {
|
||||
const mby10 = m / 10;
|
||||
const mmod10 = (m: u32) - 10 * (mby10: u32);
|
||||
buf[k] = zch + mmod10: u8;
|
||||
m = mby10;
|
||||
};
|
||||
return (olen + 1): size;
|
||||
};
|
||||
} else {
|
||||
let h: i32 = 0;
|
||||
let m = n;
|
||||
if (olen == 1) {
|
||||
buf[0] = zch + m: u8;
|
||||
h = 1;
|
||||
} else {
|
||||
let k = olen: i32;
|
||||
for (let a = k; a > 1; a -= 1) {
|
||||
const mby10 = m / 10;
|
||||
const mmod10 = (m: u32) - 10 * (mby10: u32);
|
||||
buf[k] = zch + mmod10: u8;
|
||||
k -= 1;
|
||||
m = mby10;
|
||||
};
|
||||
buf[k] = '.': u32: u8;
|
||||
buf[0] = zch + m: u8;
|
||||
h = olen: i32 + 1;
|
||||
};
|
||||
buf[h] = 'e': u32: u8;
|
||||
h += 1;
|
||||
let ex = if (exp < 0) {
|
||||
buf[h] = '-': u32: u8;
|
||||
h += 1;
|
||||
-exp;
|
||||
} else exp;
|
||||
const elen = declen(ex: u64);
|
||||
let k = h + elen: i32 - 1;
|
||||
for (let a = elen: i32; a > 0; a -= 1) {
|
||||
const eby10 = ex / 10;
|
||||
const emod10 = (ex: u32) - 10 * (eby10: u32);
|
||||
buf[k] = zch + emod10: u8;
|
||||
k -= 1;
|
||||
ex = eby10;
|
||||
};
|
||||
h += elen: i32;
|
||||
return h: size;
|
||||
};
|
||||
};
|
||||
|
||||
// Converts a f64 to a string in base 10. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn f64tos(n: f64) const str = {
|
||||
// The biggest string produced by a f64 number in base 10 would have the
|
||||
// negative sign, followed by a digit and decimal point, and then
|
||||
// fifteen more decimal digits, followed by 'e' and another negative
|
||||
// sign and the maximum of three digits for exponent.
|
||||
// (1 + 1 + 1 + 15 + 1 + 1 + 3) = 23
|
||||
static let buf: [24]u8 = [0...];
|
||||
const bits = f64bits(n);
|
||||
const sign = (bits >> (F64_MANTISSA_BITS + F64_EXPONENT_BITS)): size;
|
||||
const mantissa = bits & ((1u64 << F64_MANTISSA_BITS) - 1);
|
||||
const exponent = ((bits >> F64_MANTISSA_BITS) &
|
||||
(1u64 << F64_EXPONENT_BITS) - 1): u32;
|
||||
if (mantissa == 0 && exponent == 0) {
|
||||
return "0";
|
||||
} else if (exponent == ((1 << F64_EXPONENT_BITS) - 1)) {
|
||||
if (mantissa != 0) {
|
||||
return "NaN";
|
||||
};
|
||||
return if (sign == 0) "Infinity" else "-Infinity";
|
||||
};
|
||||
const v = bintodec(mantissa, exponent);
|
||||
if (sign != 0) {
|
||||
buf[0] = '-': u32: u8;
|
||||
};
|
||||
let z = encode(buf[sign..], v) + sign;
|
||||
let s = types::string { data = &buf, length = z, ... };
|
||||
return *(&s: *str);
|
||||
};
|
||||
|
||||
@test fn f64tos() void = {
|
||||
assert(f64tos(0.0) == "0");
|
||||
assert(f64tos(1.0 / 0.0) == "Infinity");
|
||||
assert(f64tos(-1.0 / 0.0) == "-Infinity");
|
||||
assert(f64tos(0.0 / 0.0) == "NaN");
|
||||
assert(f64tos(1.0) == "1");
|
||||
assert(f64tos(0.3) == "0.3");
|
||||
assert(f64tos(0.0031415) == "0.0031415");
|
||||
assert(f64tos(0.0000012345678) == "0.0000012345678");
|
||||
assert(f64tos(1.414) == "1.414");
|
||||
assert(f64tos(1e234f64) == "1e234");
|
||||
// TODO: Negative exponents for floating point literals not implemented yet!
|
||||
//assert(f64tos(1.2e-34) == "1.2e-34");
|
||||
assert(f64tos(-6345.9721) == "-6345.9721");
|
||||
assert(f64tos(1.23456789e67) == "1.23456789e67");
|
||||
assert(f64tos(11.2233445566778899e20) == "1.122334455667789e21");
|
||||
assert(f64tos(1000000.0e9) == "1000000000000000");
|
||||
assert(f64tos(9007199254740991.0) == "9007199254740991");
|
||||
};
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
use rt;
|
||||
use linux::vdso;
|
||||
|
||||
|
||||
fn duration_to_timespec(n: duration, ts: *rt::timespec) void = {
|
||||
ts.tv_sec = n / SECOND;
|
||||
ts.tv_nsec = n % SECOND;
|
||||
};
|
||||
|
||||
fn instant_to_timespec(t: instant, ts: *rt::timespec) void = {
|
||||
ts.tv_sec = t.sec;
|
||||
ts.tv_nsec = t.nsec;
|
||||
};
|
||||
|
||||
fn timespec_to_instant(ts: rt::timespec) instant = instant {
|
||||
sec = ts.tv_sec,
|
||||
nsec = ts.tv_nsec,
|
||||
};
|
||||
|
||||
// Yields the process to the kernel and returns after the requested duration.
|
||||
export fn sleep(n: duration) void = {
|
||||
let in = rt::timespec { ... };
|
||||
duration_to_timespec(n, &in);
|
||||
let req = ∈
|
||||
|
||||
for (true) {
|
||||
let res = rt::timespec { ... };
|
||||
match (rt::nanosleep(req, &res)) {
|
||||
void => return,
|
||||
err: rt::errno => switch (err) {
|
||||
rt::EINTR => {
|
||||
req = &res;
|
||||
},
|
||||
* => abort("Unexpected error from nanosleep"),
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type clock = enum {
|
||||
// The current wall-clock time. This may jump forwards or backwards in
|
||||
// time to account for leap seconds, NTP adjustments, etc.
|
||||
REALTIME = 0,
|
||||
|
||||
// The current monotonic time. This clock measures from some undefined
|
||||
// epoch and is not affected by leap seconds, NTP adjustments, and
|
||||
// changes to the system time: it always increases by one second per
|
||||
// second.
|
||||
MONOTONIC = 1,
|
||||
|
||||
// Measures CPU time consumed by the calling process.
|
||||
PROCESS_CPU = 2,
|
||||
|
||||
// Time since the system was booted. Increases monotonically and,
|
||||
// unlike [MONOTONIC], continues to tick while the system is suspended.
|
||||
BOOT = 7,
|
||||
};
|
||||
|
||||
let cgt_vdso: nullable *fn(_: int, _: *rt::timespec) int = null;
|
||||
|
||||
fn get_cgt_vdso() nullable *fn(_: int, _: *rt::timespec) int = {
|
||||
static let vdso_checked: bool = false;
|
||||
if (vdso_checked)
|
||||
return cgt_vdso;
|
||||
vdso_checked = true;
|
||||
|
||||
cgt_vdso = vdso::getsym(VDSO_CGT_SYM, VDSO_CGT_VER)
|
||||
: nullable *fn(_: int, _: *rt::timespec) int;
|
||||
return cgt_vdso;
|
||||
};
|
||||
|
||||
fn now_vdso(clock: clock, tp: *rt::timespec) (void | rt::errno) = {
|
||||
let vfn = match (get_cgt_vdso()) {
|
||||
null => return rt::wrap_errno(rt::ENOSYS),
|
||||
vfn: *fn(_: int, _: *rt::timespec) int => vfn,
|
||||
};
|
||||
let ret = vfn(clock, tp);
|
||||
if (ret == 0) {
|
||||
return;
|
||||
};
|
||||
return rt::wrap_errno(ret);
|
||||
};
|
||||
|
||||
// Returns the current time for a given clock.
|
||||
export fn now(clock: clock) instant = {
|
||||
let tp = rt::timespec { ... };
|
||||
let err = match (now_vdso(clock, &tp)) {
|
||||
void => return timespec_to_instant(tp),
|
||||
err: rt::errno => err
|
||||
};
|
||||
if (err != rt::wrap_errno(rt::ENOSYS)) {
|
||||
abort("Unexpected error from clock_gettime");
|
||||
};
|
||||
return match (rt::clock_gettime(clock, &tp)) {
|
||||
void => timespec_to_instant(tp),
|
||||
err: rt::errno => abort("Unexpected error from clock_gettime"),
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,307 @@
|
||||
// getopt provides an interface for parsing command line arguments and
|
||||
// automatically generates a brief help message explaining the command usage.
|
||||
// See [parse] for the main entry point.
|
||||
//
|
||||
// The help text is brief and should serve only as a reminder. It is recommended
|
||||
// that your command line program be accompanied by a man page to provide
|
||||
// detailed usage information.
|
||||
use encoding::utf8;
|
||||
use fmt;
|
||||
use io;
|
||||
use os;
|
||||
use strings;
|
||||
|
||||
// A flag which does not take a parameter, e.g. "-a".
|
||||
export type flag = rune;
|
||||
|
||||
// An option with an included parameter, e.g. "-a foo".
|
||||
export type parameter = str;
|
||||
|
||||
// A command line option.
|
||||
export type option = (flag, parameter);
|
||||
|
||||
// The result of parsing the set of command line arguments, including any
|
||||
// options specified and the list of non-option arguments.
|
||||
export type command = struct {
|
||||
opts: []option,
|
||||
args: []str,
|
||||
};
|
||||
|
||||
// Help text providing a short, one-line summary of the command; or providing
|
||||
// the name of an argument.
|
||||
export type cmd_help = str;
|
||||
|
||||
// Help text for a flag, formatted as "-a: help text".
|
||||
export type flag_help = (flag, str);
|
||||
|
||||
// Help text for a parameter, formatted as "-a param: help text" where "param"
|
||||
// is the first string and "help text" is the second string.
|
||||
export type parameter_help = (flag, str, str);
|
||||
|
||||
// Help text for a command or option.
|
||||
//
|
||||
// cmd_help, flag_help, and parameter_help compose such that the help output for
|
||||
//
|
||||
// [
|
||||
// "foo bars in order",
|
||||
// ('a', "a help text"),
|
||||
// ('b', "b help text"),
|
||||
// ('c', "cflag", "c help text"),
|
||||
// ('d', "dflag", "d help text"),
|
||||
// "files...",
|
||||
// ]
|
||||
//
|
||||
// is:
|
||||
//
|
||||
// foo: foo bars in order
|
||||
//
|
||||
// Usage: foo [-ab] [-c <cflag>] [-d <dflag>] files...
|
||||
//
|
||||
// -a: a help text
|
||||
// -b: b help text
|
||||
// -c <cflag>: c help text
|
||||
// -d <dflag>: d help text
|
||||
export type help = (cmd_help | flag_help | parameter_help);
|
||||
|
||||
// Parses command line arguments and returns a tuple of the options specified,
|
||||
// and the remaining arguments. If an error occurs, details are printed to
|
||||
// [os::stderr] and [os::exit] is called with a nonzero exit status. The
|
||||
// argument list must include the command name as the first item; [os::args]
|
||||
// fulfills this criteria.
|
||||
//
|
||||
// The caller provides [help] arguments to specify which command line flags and
|
||||
// parameters are supported, and to provide some brief help text which describes
|
||||
// their use. Provide [flag_help] to add a flag which does not take a parameter,
|
||||
// and [parameter_help] to add a flag with a required parameter. The first
|
||||
// [cmd_help] is used as a short, one-line summary of the command's purpose, and
|
||||
// any later [cmd_help] arguments are used to provide the name of any arguments
|
||||
// which follow the options list.
|
||||
//
|
||||
// By convention, the caller should sort the list of options, first providing
|
||||
// all flags, then all parameters, alpha-sorted within each group by the flag
|
||||
// rune.
|
||||
//
|
||||
// // Usage for sed
|
||||
// let cmd = getopt::parse(os::args
|
||||
// "stream editor",
|
||||
// ('E', "use extended regular expressions"),
|
||||
// ('s', "treat files as separate, rather than one continuous stream"),
|
||||
// ('i', "edit files in place"),
|
||||
// ('z', "separate lines by NUL characeters"),
|
||||
// ('e', "script", "execute commands from script"),
|
||||
// ('f', "file", "execute commands from a file"),
|
||||
// "files...",
|
||||
// );
|
||||
// defer getopt::finish(&cmd);
|
||||
//
|
||||
// for (let i = 0z; i < len(cmd.opts); i += 1) {
|
||||
// let opt = cmd.opts[i];
|
||||
// switch (opt.0) {
|
||||
// 'E' => extended = true,
|
||||
// 's' => continuous = false,
|
||||
// // ...
|
||||
// 'e' => script = opt.1,
|
||||
// 'f' => file = opt.1,
|
||||
// };
|
||||
// };
|
||||
//
|
||||
// for (let i = 0z; i < len(cmd.args); i += 1) {
|
||||
// let arg = cmd.args[i];
|
||||
// // ...
|
||||
// };
|
||||
//
|
||||
// If "-h" is not among the options defined by the caller, the "-h" option will
|
||||
// will cause a summary of the command usage to be printed to stderr, and
|
||||
// [os::exit] will be called with a successful exit status.
|
||||
export fn parse(args: []str, help: help...) command = {
|
||||
let opts: []option = [];
|
||||
let i = 1z;
|
||||
:arg for (i < len(args); i += 1) {
|
||||
const arg = args[i];
|
||||
if (len(arg) == 0 || arg == "-"
|
||||
|| !strings::has_prefix(arg, "-")) {
|
||||
break;
|
||||
};
|
||||
if (arg == "--") {
|
||||
i += 1;
|
||||
break;
|
||||
};
|
||||
|
||||
let d = utf8::decode(arg);
|
||||
assert(utf8::next(&d) as rune == '-');
|
||||
let next = utf8::next(&d);
|
||||
:flag for (next is rune; next = utf8::next(&d)) {
|
||||
const r = next as rune;
|
||||
:help for (let j = 0z; j < len(help); j += 1) {
|
||||
let p: parameter_help = match (help[j]) {
|
||||
cmd_help => continue :help,
|
||||
f: flag_help => if (r == f.0) {
|
||||
append(opts, (r, ""));
|
||||
continue :flag;
|
||||
} else continue :help,
|
||||
p: parameter_help => if (r == p.0) p
|
||||
else continue :help,
|
||||
};
|
||||
if (len(d.src) == d.offs) {
|
||||
if (i + 1 >= len(args)) {
|
||||
errmsg(args[0], "option requires an argument: ",
|
||||
r, help);
|
||||
os::exit(1);
|
||||
};
|
||||
i += 1;
|
||||
append(opts, (r, args[i]));
|
||||
} else {
|
||||
let s = strings::fromutf8(d.src[d.offs..]);
|
||||
append(opts, (r, s));
|
||||
};
|
||||
continue :arg;
|
||||
};
|
||||
if (r =='h') {
|
||||
printhelp(os::stderr, args[0], help);
|
||||
os::exit(0);
|
||||
};
|
||||
errmsg(args[0], "unrecognized option: ", r, help);
|
||||
os::exit(1);
|
||||
};
|
||||
match (next) {
|
||||
rune => abort(), // Unreachable
|
||||
void => void,
|
||||
(utf8::more | utf8::invalid) => {
|
||||
errmsg(args[9], "invalid UTF-8 in arguments",
|
||||
void, help);
|
||||
os::exit(1);
|
||||
},
|
||||
};
|
||||
};
|
||||
return command {
|
||||
opts = opts,
|
||||
args = args[i..],
|
||||
};
|
||||
};
|
||||
|
||||
// Frees resources associated with the return value of [parse].
|
||||
export fn finish(cmd: *command) void = {
|
||||
if (cmd == null) return;
|
||||
free(cmd.opts);
|
||||
};
|
||||
|
||||
fn _printusage(s: *io::stream, name: str, indent: bool, help: []help) size = {
|
||||
let z = fmt::fprint(s, "Usage:", name) as size;
|
||||
|
||||
let started_flags = false;
|
||||
for (let i = 0z; i < len(help); i += 1) if (help[i] is flag_help) {
|
||||
if (!started_flags) {
|
||||
z += fmt::fprint(s, " [-") as size;
|
||||
started_flags = true;
|
||||
};
|
||||
const help = help[i] as flag_help;
|
||||
z += fmt::fprint(s, help.0: rune) as size;
|
||||
};
|
||||
if (started_flags) {
|
||||
z += fmt::fprint(s, "]") as size;
|
||||
};
|
||||
|
||||
for (let i = 0z; i < len(help); i += 1) if (help[i] is parameter_help) {
|
||||
const help = help[i] as parameter_help;
|
||||
if (indent) {
|
||||
z += fmt::fprintf(s, "\n\t") as size;
|
||||
};
|
||||
z += fmt::fprintf(s, " [-{} <{}>]", help.0: rune, help.1) as size;
|
||||
};
|
||||
if (indent) {
|
||||
z += fmt::fprintf(s, "\n\t") as size;
|
||||
};
|
||||
for (let i = 1z; i < len(help); i += 1) if (help[i] is cmd_help) {
|
||||
z += fmt::fprintf(s, " {}", help[i] as cmd_help: str) as size;
|
||||
};
|
||||
|
||||
return z + fmt::fprint(s, "\n") as size;
|
||||
};
|
||||
|
||||
// Prints command usage to the provided stream.
|
||||
export fn printusage(s: *io::stream, name: str, help: []help) void = {
|
||||
let z = _printusage(io::empty, name, false, help);
|
||||
_printusage(s, name, if (z > 72) true else false, help);
|
||||
};
|
||||
|
||||
// Prints command help to the provided stream.
|
||||
export fn printhelp(s: *io::stream, name: str, help: []help) void = {
|
||||
if (help[0] is cmd_help) {
|
||||
fmt::fprintfln(s, "{}: {}\n", name, help[0] as cmd_help: str);
|
||||
};
|
||||
|
||||
printusage(s, name, help);
|
||||
|
||||
for (let i = 0z; i < len(help); i += 1) match (help[i]) {
|
||||
cmd_help => void,
|
||||
(flag_help | parameter_help) => {
|
||||
// Only print this if there are flags to show
|
||||
fmt::fprint(s, "\n");
|
||||
break;
|
||||
},
|
||||
};
|
||||
|
||||
for (let i = 0z; i < len(help); i += 1) match (help[i]) {
|
||||
cmd_help => void,
|
||||
f: flag_help => {
|
||||
fmt::fprintfln(s, "-{}: {}", f.0: rune, f.1);
|
||||
},
|
||||
p: parameter_help => {
|
||||
fmt::fprintfln(s, "-{} <{}>: {}", p.0: rune, p.1, p.2);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
fn errmsg(name: str, err: str, opt: (rune | void), help: []help) void = {
|
||||
fmt::errorfln("{}: {}{}", name, err, match (opt) {
|
||||
r: rune => r,
|
||||
void => "",
|
||||
});
|
||||
printusage(os::stderr, name, help);
|
||||
};
|
||||
|
||||
@test fn parse() void = {
|
||||
let args: []str = ["cat", "-v", "a.out"];
|
||||
let cat = parse(args,
|
||||
"concatenate files",
|
||||
('v', "cause Rob Pike to make a USENIX presentation"),
|
||||
"files...",
|
||||
);
|
||||
defer finish(&cat);
|
||||
assert(len(cat.args) == 1 && cat.args[0] == "a.out");
|
||||
assert(len(cat.opts) == 1 && cat.opts[0].0 == 'v' && cat.opts[0].1 == "");
|
||||
|
||||
args = ["ls", "-Fahs", "--", "-j"];
|
||||
let ls = parse(args,
|
||||
"list files",
|
||||
('F', "Do some stuff"),
|
||||
('h', "Do some other stuff"),
|
||||
('s', "Do a third type of stuff"),
|
||||
('a', "Do a fourth type of stuff"),
|
||||
"files...",
|
||||
);
|
||||
defer finish(&ls);
|
||||
assert(len(ls.args) == 1 && ls.args[0] == "-j");
|
||||
assert(len(ls.opts) == 4);
|
||||
assert(ls.opts[0].0 == 'F' && ls.opts[0].1 == "");
|
||||
assert(ls.opts[1].0 == 'a' && ls.opts[1].1 == "");
|
||||
assert(ls.opts[2].0 == 'h' && ls.opts[2].1 == "");
|
||||
assert(ls.opts[3].0 == 's' && ls.opts[3].1 == "");
|
||||
|
||||
args = ["sed", "-e", "s/C++//g", "-f/tmp/turing.sed", "-"];
|
||||
let sed = parse(args,
|
||||
"edit streams",
|
||||
('e', "script", "Add the editing commands specified by the "
|
||||
"script option to the end of the script of editing "
|
||||
"commands"),
|
||||
('f', "script_file", "Add the editing commands in the file "
|
||||
"script_file to the end of the script of editing "
|
||||
"commands"),
|
||||
"files...",
|
||||
);
|
||||
defer finish(&sed);
|
||||
assert(len(sed.args) == 1 && sed.args[0] == "-");
|
||||
assert(len(sed.opts) == 2);
|
||||
assert(sed.opts[0].0 == 'e' && sed.opts[0].1 == "s/C++//g");
|
||||
assert(sed.opts[1].0 == 'f' && sed.opts[1].1 == "/tmp/turing.sed");
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
use rt;
|
||||
|
||||
// Returns the current process user ID.
|
||||
export fn getuid() uint = {
|
||||
let uid = 0u, euid = 0u, suid = 0u;
|
||||
rt::getresuid(&uid, &euid, &suid) as void;
|
||||
return uid;
|
||||
};
|
||||
|
||||
// Returns the current process effective user ID.
|
||||
export fn geteuid() uint = {
|
||||
let uid = 0u, euid = 0u, suid = 0u;
|
||||
rt::getresuid(&uid, &euid, &suid) as void;
|
||||
return euid;
|
||||
};
|
||||
|
||||
// Returns the current process group ID.
|
||||
export fn getgid() uint = {
|
||||
let gid = 0u, egid = 0u, sgid = 0u;
|
||||
rt::getresgid(&gid, &egid, &sgid) as void;
|
||||
return gid;
|
||||
};
|
||||
|
||||
// Returns the current process effective group ID.
|
||||
export fn getegid() uint = {
|
||||
let gid = 0u, egid = 0u, sgid = 0u;
|
||||
rt::getresgid(&gid, &egid, &sgid) as void;
|
||||
return egid;
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
use io;
|
||||
// TODO: Let caller supply the output buffer, to avoid the slice allocation
|
||||
|
||||
// The general purpose interface for a hashing function.
|
||||
export type hash = struct {
|
||||
// A stream which only supports writes and never returns errors.
|
||||
stream: io::stream,
|
||||
|
||||
// Returns the current hash.
|
||||
sum: *fn(hash: *hash) []u8,
|
||||
|
||||
// Resets the hash function to its initial state.
|
||||
reset: *fn(hash: *hash) void,
|
||||
|
||||
// Size of the hash in bytes.
|
||||
sz: size,
|
||||
};
|
||||
|
||||
// Returns a writable [io::stream] for a given hash.
|
||||
export fn writer(h: *hash) *io::stream = &h.stream;
|
||||
|
||||
// Writes an input to the hash function.
|
||||
export fn write(h: *hash, buf: const []u8) size =
|
||||
io::write(&h.stream, buf) as size;
|
||||
|
||||
// Finalizes the hash, frees resources associated with the hash, and returns the
|
||||
// sum. The return value is heap allocated, the caller needs to free it.
|
||||
export fn finish(h: *hash) []u8 = {
|
||||
let sum = sum(h);
|
||||
io::close(&h.stream);
|
||||
return sum;
|
||||
};
|
||||
|
||||
// Closes a hash, freeing its resources and discarding the checksum.
|
||||
export fn close(h: *hash) void = io::close(&h.stream);
|
||||
|
||||
// Returns the current sum. The return value is heap allocated, the caller
|
||||
// needs to free it.
|
||||
export fn sum(h: *hash) []u8 = h.sum(h);
|
||||
|
||||
// Resets the hash function to its initial state.
|
||||
export fn reset(h: *hash) void = h.reset(h);
|
||||
|
||||
// Returns the size of the hash in bytes. This is consistent regardless
|
||||
// of the hash state.
|
||||
export fn sz(h: *hash) size = h.sz;
|
||||
@ -0,0 +1,117 @@
|
||||
use ascii;
|
||||
use bytes;
|
||||
use fmt;
|
||||
use io;
|
||||
use strconv;
|
||||
use strings;
|
||||
use strio;
|
||||
|
||||
// Error returned when attempting to decode an invalid hex string.
|
||||
export type invalid = void!;
|
||||
|
||||
// Encodes a byte slice as a hexadecimal string and writes it to a stream.
|
||||
export fn encode(sink: *io::stream, b: []u8) (size | io::error) = {
|
||||
let z = 0z;
|
||||
for (let i = 0z; i < len(b); i += 1) {
|
||||
let s = strconv::u8tosb(b[i], strconv::base::HEX_LOWER);
|
||||
if (len(s) == 1) {
|
||||
z += io::write(sink, ['0': u32: u8])?;
|
||||
};
|
||||
z += io::write(sink, strings::toutf8(s))?;
|
||||
};
|
||||
return z;
|
||||
};
|
||||
|
||||
// Encodes a byte slice as a hexadecimal string and returns it. The caller must
|
||||
// free the return value.
|
||||
export fn encodestr(b: []u8) str = {
|
||||
let sink = strio::dynamic();
|
||||
encode(sink, b) as size;
|
||||
return strio::finish(sink);
|
||||
};
|
||||
|
||||
@test fn encode() void = {
|
||||
let in: [_]u8 = [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xF0, 0x0D];
|
||||
let s = encodestr(in);
|
||||
defer free(s);
|
||||
assert(s == "cafebabedeadf00d");
|
||||
};
|
||||
|
||||
// Decodes a string of hexadecimal bytes into a byte slice. The caller must free
|
||||
// the return value.
|
||||
export fn decode(s: str) ([]u8 | invalid) = {
|
||||
if (len(s) % 2 != 0) {
|
||||
return invalid;
|
||||
};
|
||||
let buf: []u8 = alloc([], len(s) / 2);
|
||||
let s = strings::toutf8(s);
|
||||
for (let i = 0z; i < len(s) / 2; i += 1) {
|
||||
let oct = strings::fromutf8_unsafe(s[i * 2..i * 2 + 2]);
|
||||
let u = match (strconv::stou8b(oct, 16)) {
|
||||
(strconv::invalid | strconv::overflow) => return invalid,
|
||||
u: u8 => u,
|
||||
};
|
||||
append(buf, u);
|
||||
};
|
||||
return buf;
|
||||
};
|
||||
|
||||
@test fn decode() void = {
|
||||
let s = decode("cafebabedeadf00d") as []u8;
|
||||
defer free(s);
|
||||
assert(bytes::equal(s, [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xF0, 0x0D]));
|
||||
|
||||
decode("this is not hex") as invalid;
|
||||
};
|
||||
|
||||
// Outputs a dump of hex data to a stream alongside the offset and an ASCII
|
||||
// representation (if applicable).
|
||||
//
|
||||
// Example output:
|
||||
//
|
||||
// 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
|
||||
// 00000010 03 00 3e 00 01 00 00 00 80 70 01 00 00 00 00 00 |..>......p......|
|
||||
export fn dump(out: *io::stream, data: []u8) (void | io::error) = {
|
||||
let datalen = len(data): u32;
|
||||
|
||||
for (let off = 0u32; off < datalen; off += 16) {
|
||||
fmt::fprintf(out, "{:08x} ", off)?;
|
||||
|
||||
let toff = 0z;
|
||||
for (let i = 0z; i < 16 && off + i < datalen; i += 1) {
|
||||
let val = data[off + i];
|
||||
toff += fmt::fprintf(out, "{}{:02x} ",
|
||||
if (i == 8) " " else "", val)?;
|
||||
};
|
||||
|
||||
// Align ASCII representation, max width of hex part (48) +
|
||||
// spacing around it
|
||||
for (toff < 50; toff += 1) {
|
||||
fmt::fprint(out, " ")?;
|
||||
};
|
||||
|
||||
fmt::fprint(out, "|")?;
|
||||
for (let i = 0z; i < 16 && off + i < datalen; i += 1) {
|
||||
let r = data[off + i]: u32: rune;
|
||||
|
||||
fmt::fprint(out, if (ascii::isprint(r)) r else '.')?;
|
||||
};
|
||||
fmt::fprint(out, "|\n")?;
|
||||
};
|
||||
};
|
||||
|
||||
@test fn dump() void = {
|
||||
let in: [_]u8 = [
|
||||
0x7F, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0xCA, 0xFE,
|
||||
0xBA, 0xBE, 0xDE, 0xAD, 0xF0, 0x0D, 0xCE, 0xFE, 0xBA, 0xBE,
|
||||
0xDE, 0xAD, 0xF0, 0x0D
|
||||
];
|
||||
|
||||
let sink = strio::dynamic();
|
||||
dump(sink, in) as void;
|
||||
|
||||
let s = strio::finish(sink);
|
||||
assert(s ==
|
||||
"00000000 7f 45 4c 46 02 01 01 00 ca fe ba be de ad f0 0d |.ELF............|\n"
|
||||
"00000010 ce fe ba be de ad f0 0d |........|\n");
|
||||
};
|
||||
@ -0,0 +1,2 @@
|
||||
// The [endian] functions which map to the host architecture.
|
||||
export const host: *endian = &little;
|
||||
@ -0,0 +1,2 @@
|
||||
// The [endian] functions which map to the host architecture.
|
||||
export const host: *endian = &little;
|
||||
@ -0,0 +1,31 @@
|
||||
use fmt;
|
||||
use hare::ast;
|
||||
use io;
|
||||
use strio;
|
||||
|
||||
// Unparses an identifier.
|
||||
export fn ident(out: *io::stream, id: ast::ident) (size | io::error) = {
|
||||
let n = 0z;
|
||||
for (let i = 0z; i < len(id); i += 1) {
|
||||
n += fmt::fprintf(out, "{}{}", id[i],
|
||||
if (i + 1 < len(id)) "::"
|
||||
else "")?;
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
// Unparses an identifier into a string. The caller must free the return value.
|
||||
export fn identstr(id: ast::ident) str = {
|
||||
let buf = strio::dynamic();
|
||||
ident(buf, id);
|
||||
return strio::finish(buf);
|
||||
};
|
||||
|
||||
@test fn ident() void = {
|
||||
let s = identstr(["foo", "bar", "baz"]);
|
||||
assert(s == "foo::bar::baz");
|
||||
free(s);
|
||||
s = identstr(["foo"]);
|
||||
assert(s == "foo");
|
||||
free(s);
|
||||
};
|
||||
@ -0,0 +1,52 @@
|
||||
use fmt;
|
||||
use io;
|
||||
use hare::ast;
|
||||
use strio;
|
||||
|
||||
export fn import(out: *io::stream, i: ast::import) (size | io::error) = {
|
||||
let n = 0z;
|
||||
n += fmt::fprint(out, "use ")?;
|
||||
match (i) {
|
||||
m: ast::import_module => n += ident(out, m)?,
|
||||
a: ast::import_alias => {
|
||||
n += fmt::fprint(out, a.alias, "= ")?;
|
||||
n += ident(out, a.ident)?;
|
||||
},
|
||||
o: ast::import_objects => {
|
||||
n += ident(out, o.ident)?;
|
||||
n += fmt::fprint(out, "::{")?;
|
||||
for (let i = 0z; i < len(o.objects); i += 1) {
|
||||
n += fmt::fprintf(out, "{}{}", o.objects[i],
|
||||
if (i + 1 < len(o.objects)) ", "
|
||||
else "")?;
|
||||
};
|
||||
n += fmt::fprint(out, "}")?;
|
||||
},
|
||||
};
|
||||
n += fmt::fprint(out, ";")?;
|
||||
return n;
|
||||
};
|
||||
|
||||
@test fn import() void = {
|
||||
let buf = strio::dynamic();
|
||||
import(buf, ["foo", "bar", "baz"]) as size;
|
||||
let s = strio::finish(buf);
|
||||
assert(s == "use foo::bar::baz;");
|
||||
free(s);
|
||||
buf = strio::dynamic();
|
||||
import(buf, ast::import_alias {
|
||||
ident = ["foo"],
|
||||
alias = "bar",
|
||||
}) as size;
|
||||
s = strio::finish(buf);
|
||||
assert(s == "use bar = foo;");
|
||||
free(s);
|
||||
buf = strio::dynamic();
|
||||
import(buf, ast::import_objects {
|
||||
ident = ["foo"],
|
||||
objects = ["bar", "baz"],
|
||||
}) as size;
|
||||
s = strio::finish(buf);
|
||||
assert(s == "use foo::{bar, baz};");
|
||||
free(s);
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
use bytes;
|
||||
|
||||
// Returns the index of the first occurance of 'needle' in the 'haystack', or
|
||||
// void if not present. The index returned is the rune-wise index, not the
|
||||
// byte-wise index.
|
||||
export fn index(haystack: str, needle: (str | rune)) (size | void) = {
|
||||
return match (needle) {
|
||||
r: rune => index_rune(haystack, r),
|
||||
s: str => abort(), // TODO
|
||||
};
|
||||
};
|
||||
|
||||
fn index_rune(s: str, r: rune) (size | void) = {
|
||||
let iter = iter(s);
|
||||
for (let i = 0z; true; i += 1) match (next(&iter)) {
|
||||
n: rune => if (r == n) return i,
|
||||
void => break,
|
||||
};
|
||||
};
|
||||
|
||||
@test fn index() void = {
|
||||
assert(index("hello world", 'w') as size == 6);
|
||||
assert(index("こんにちは", 'ち') as size == 3);
|
||||
assert(index("こんにちは", 'q') is void);
|
||||
};
|
||||
@ -0,0 +1,327 @@
|
||||
use io;
|
||||
use strio;
|
||||
use fmt;
|
||||
use bytes;
|
||||
use rt;
|
||||
use strconv;
|
||||
use strings;
|
||||
|
||||
// An IPv4 address.
|
||||
export type addr4 = [4]u8;
|
||||
|
||||
// An IPv6 address.
|
||||
export type addr6 = [16]u8;
|
||||
|
||||
// An IP address.
|
||||
export type addr = (addr4 | addr6);
|
||||
|
||||
// An IP subnet.
|
||||
export type subnet = struct {
|
||||
addr: addr,
|
||||
mask: addr,
|
||||
};
|
||||
|
||||
// An IPv4 address which represents "any" address, i.e. "0.0.0.0". Binding to
|
||||
// this address will listen on all available IPv4 interfaces on most systems.
|
||||
export const ANY_V4: addr4 = [0, 0, 0, 0];
|
||||
|
||||
// An IPv6 address which represents "any" address, i.e. "::". Binding to this
|
||||
// address will listen on all available IPv6 interfaces on most systems.
|
||||
export const ANY_V6: addr6 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
// Invalid parse result.
|
||||
export type invalid = void!;
|
||||
|
||||
// Test if two [addr]s are equal.
|
||||
export fn equal(l: addr, r: addr) bool = {
|
||||
return match (l) {
|
||||
l: addr4 => {
|
||||
if (!(r is addr4)) {
|
||||
return false;
|
||||
};
|
||||
let r = r as addr4;
|
||||
bytes::equal(l, r);
|
||||
},
|
||||
l: addr6 => {
|
||||
if (!(r is addr6)) {
|
||||
return false;
|
||||
};
|
||||
let r = r as addr6;
|
||||
bytes::equal(l, r);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
fn parsev4(st: str) (addr4 | invalid) = {
|
||||
let ret: addr4 = [0...];
|
||||
let tok = strings::tokenize(st, ".");
|
||||
let i = 0z;
|
||||
for (i < 4; i += 1) {
|
||||
let s = wanttoken(&tok)?;
|
||||
ret[i] = match (strconv::stou8(s)) {
|
||||
term: u8 => term,
|
||||
* => return invalid
|
||||
};
|
||||
};
|
||||
if (i < 4 || !(strings::next_token(&tok) is void)) {
|
||||
return invalid;
|
||||
};
|
||||
return ret;
|
||||
};
|
||||
|
||||
fn parsev6(st: str) (addr6 | invalid) = {
|
||||
let ret: addr6 = [0...];
|
||||
let tok = strings::tokenize(st, ":");
|
||||
if (st == "::") {
|
||||
return ret;
|
||||
};
|
||||
let ells = -1;
|
||||
if (strings::has_prefix(st, "::")) {
|
||||
wanttoken(&tok)?;
|
||||
wanttoken(&tok)?;
|
||||
ells = 0;
|
||||
} else if (strings::has_prefix(st, ":")) {
|
||||
return invalid;
|
||||
};
|
||||
let i = 0;
|
||||
for (i < 16) {
|
||||
let s = match (strings::next_token(&tok)) {
|
||||
s: str => s,
|
||||
void => break,
|
||||
};
|
||||
if (s == "") {
|
||||
if (ells != -1) {
|
||||
return invalid;
|
||||
};
|
||||
ells = i;
|
||||
continue;
|
||||
};
|
||||
let val = strconv::stou16b(s, 16);
|
||||
if (val is u16) {
|
||||
let v = val as u16;
|
||||
ret[i] = (v >> 8): u8;
|
||||
i += 1;
|
||||
ret[i] = v: u8;
|
||||
i += 1;
|
||||
continue;
|
||||
} else {
|
||||
let v4 = parsev4(s)?;
|
||||
rt::memcpy(&ret[i], &v4, 4);
|
||||
i += 4;
|
||||
break;
|
||||
};
|
||||
return invalid;
|
||||
};
|
||||
if (!(strings::next_token(&tok) is void)) {
|
||||
return invalid;
|
||||
};
|
||||
if (ells >= 0) {
|
||||
if (i >= 15) {
|
||||
return invalid;
|
||||
};
|
||||
rt::memcpy(
|
||||
&ret[16 - (i - ells)],
|
||||
&ret[ells], (i - ells): size);
|
||||
rt::memset(&ret[ells], 0, (i - ells): size);
|
||||
} else {
|
||||
if (i != 16)
|
||||
return invalid;
|
||||
};
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
// Parses an IP address.
|
||||
export fn parse(s: str) (addr | invalid) = {
|
||||
match(parsev4(s)) {
|
||||
v4: addr4 => return v4,
|
||||
};
|
||||
match(parsev6(s)) {
|
||||
v6: addr6 => return v6,
|
||||
};
|
||||
return invalid;
|
||||
};
|
||||
|
||||
fn fmtv4(s: *io::stream, a: addr4) (io::error | size) = {
|
||||
let ret = 0z;
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
if (i > 0) {
|
||||
ret += fmt::fprintf(s, ".")?;
|
||||
};
|
||||
ret += fmt::fprintf(s, "{}", a[i])?;
|
||||
};
|
||||
return ret;
|
||||
};
|
||||
|
||||
fn fmtv6(s: *io::stream, a: addr6) (io::error | size) = {
|
||||
let ret = 0z;
|
||||
let zstart: int = -1;
|
||||
let zend: int = -1;
|
||||
for (let i = 0; i < 16; i += 2) {
|
||||
let j = i;
|
||||
for (j < 16 && a[j] == 0 && a[j + 1] == 0) {
|
||||
j += 2;
|
||||
};
|
||||
|
||||
if (j > i && j - i > zend - zstart) {
|
||||
zstart = i;
|
||||
zend = j;
|
||||
i = j;
|
||||
};
|
||||
};
|
||||
|
||||
if (zend - zstart <= 2) {
|
||||
zstart = -1;
|
||||
zend = -1;
|
||||
};
|
||||
|
||||
for (let i = 0; i < 16; i += 2) {
|
||||
if (i == zstart) {
|
||||
ret += fmt::fprintf(s, "::")?;
|
||||
i = zend;
|
||||
if (i >= 16)
|
||||
break;
|
||||
} else if (i > 0) {
|
||||
ret += fmt::fprintf(s, ":")?;
|
||||
};
|
||||
let term = (a[i]: u16) << 8 | a[i + 1];
|
||||
ret += fmt::fprintf(s, "{:x}", term)?;
|
||||
};
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Fills a netmask according to the CIDR value
|
||||
// e.g. 23 -> [0xFF, 0xFF, 0xFD, 0x00]
|
||||
fn fillmask(mask: []u8, val: u8) void = {
|
||||
rt::memset(&mask[0], 0xFF, len(mask));
|
||||
let i: int = len(mask): int - 1;
|
||||
val = 32 - val;
|
||||
for (val >= 8) {
|
||||
mask[i] = 0x00;
|
||||
val -= 8;
|
||||
i -= 1;
|
||||
};
|
||||
if (i >= 0) {
|
||||
mask[i] = ~((1 << val) - 1);
|
||||
};
|
||||
};
|
||||
|
||||
// Returns an addr representing a netmask
|
||||
fn cidrmask(addr: addr, val: u8) (addr | invalid) = {
|
||||
let a_len: u8 = match (addr) {
|
||||
addr4 => 4,
|
||||
addr6 => 16,
|
||||
};
|
||||
|
||||
if (val > 8 * a_len)
|
||||
return invalid;
|
||||
if (a_len == 4) {
|
||||
let ret: addr4 = [0...];
|
||||
fillmask(ret[..], val);
|
||||
return ret;
|
||||
};
|
||||
if (a_len == 16) {
|
||||
let ret: addr6 = [0...];
|
||||
fillmask(ret[..], val);
|
||||
return ret;
|
||||
};
|
||||
return invalid;
|
||||
};
|
||||
|
||||
// Parse an IP subnet in CIDR notation e.g. 192.168.1.0/24
|
||||
export fn parsecidr(st: str) (subnet | invalid) = {
|
||||
let tok = strings::tokenize(st, "/");
|
||||
let ips = wanttoken(&tok)?;
|
||||
let addr = parse(ips)?;
|
||||
let masks = wanttoken(&tok)?;
|
||||
let val = match (strconv::stou8(masks)) {
|
||||
x: u8 => x,
|
||||
* => return invalid,
|
||||
};
|
||||
if (!(strings::next_token(&tok) is void)) {
|
||||
return invalid;
|
||||
};
|
||||
return subnet {
|
||||
addr = addr,
|
||||
mask = cidrmask(addr, val)?
|
||||
};
|
||||
};
|
||||
|
||||
fn masklen(addr: []u8) (void | size) = {
|
||||
let n = 0z;
|
||||
for (let i = 0z; i < len(addr); i += 1) {
|
||||
if (addr[i] == 0xff) {
|
||||
n += 8;
|
||||
continue;
|
||||
};
|
||||
let val = addr[i];
|
||||
for (val & 0x80 != 0) {
|
||||
n += 1;
|
||||
val <<= 1;
|
||||
};
|
||||
if (val != 0)
|
||||
return;
|
||||
for (let j = i + 1; j < len(addr); j += 1) {
|
||||
if (addr[j] != 0)
|
||||
return;
|
||||
};
|
||||
break;
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
fn fmtmask(s: *io::stream, mask: addr) (io::error | size) = {
|
||||
let ret = 0z;
|
||||
let slice = match (mask) {
|
||||
v4: addr4 => v4[..],
|
||||
v6: addr6 => v6[..],
|
||||
};
|
||||
match (masklen(slice)) {
|
||||
// format as hex, if zero runs are not contiguous
|
||||
// (like golang does)
|
||||
void => {
|
||||
for (let i = 0z; i < len(slice); i += 1) {
|
||||
ret += fmt::fprintf(s, "{:x}", slice[i])?;
|
||||
};
|
||||
},
|
||||
// standard CIDR integer
|
||||
n: size => ret += fmt::fprintf(s, "{}", n)?,
|
||||
};
|
||||
return ret;
|
||||
};
|
||||
|
||||
fn fmtsubnet(s: *io::stream, subnet: subnet) (io::error | size) = {
|
||||
let ret = 0z;
|
||||
ret += fmt(s, subnet.addr)?;
|
||||
ret += fmt::fprintf(s, "/")?;
|
||||
ret += fmtmask(s, subnet.mask)?;
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Formats an [addr] or [subnet] and prints it to a stream.
|
||||
export fn fmt(s: *io::stream, item: (...addr | subnet)) (io::error | size) = {
|
||||
return match (item) {
|
||||
v4: addr4 => fmtv4(s, v4)?,
|
||||
v6: addr6 => fmtv6(s, v6)?,
|
||||
sub: subnet => fmtsubnet(s, sub),
|
||||
};
|
||||
};
|
||||
|
||||
// Formats an [addr] or [subnet] as a string. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// extend its lifetime.
|
||||
export fn string(item: (...addr | subnet)) str = {
|
||||
// Maximum length of an IPv6 address plus its netmask in hexadecimal
|
||||
static let buf: [64]u8 = [0...];
|
||||
let stream = strio::fixed(buf);
|
||||
fmt(stream, item) as size;
|
||||
return strio::string(stream);
|
||||
};
|
||||
|
||||
fn wanttoken(tok: *strings::tokenizer) (str | invalid) = {
|
||||
return match (strings::next_token(tok)) {
|
||||
s: str => s,
|
||||
void => invalid
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,111 @@
|
||||
use encoding::utf8;
|
||||
|
||||
// An iterator which yields each rune from a string.
|
||||
export type iterator = struct {
|
||||
dec: utf8::decoder,
|
||||
push: (rune | void),
|
||||
};
|
||||
|
||||
// Initializes a string iterator, starting at the beginning of the string.
|
||||
export fn iter(src: str) iterator = iterator {
|
||||
dec = utf8::decode(src),
|
||||
push = void,
|
||||
};
|
||||
|
||||
// Initializes a string iterator, starting at the end of the string.
|
||||
export fn riter(src: str) iterator = {
|
||||
let ret = iterator {
|
||||
dec = utf8::decode(src),
|
||||
push = void,
|
||||
};
|
||||
ret.dec.offs = len(src);
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Get the next rune from an iterator, or void if there are none left.
|
||||
//
|
||||
// Be aware that a rune is not the minimum lexographical unit of language in
|
||||
// Unicode strings. If you use these runes to construct a new string,
|
||||
// reordering, editing, or omitting any of the runes without careful discretion
|
||||
// may cause linguistic errors to arise. To avoid this, you may need to use
|
||||
// [unicode::graphiter] instead.
|
||||
export fn next(iter: *iterator) (rune | void) = {
|
||||
match (iter.push) {
|
||||
r: rune => {
|
||||
iter.push = void;
|
||||
return r;
|
||||
},
|
||||
void => void,
|
||||
};
|
||||
return match (utf8::next(&iter.dec)) {
|
||||
r: rune => r,
|
||||
void => void,
|
||||
(utf8::more | utf8::invalid) =>
|
||||
abort("Invalid UTF-8 string (this should not happen)"),
|
||||
};
|
||||
};
|
||||
|
||||
// Get the previous rune from an iterator, or void when at the start of the
|
||||
// string.
|
||||
export fn prev(iter: *iterator) (rune | void) = {
|
||||
assert(iter.push is void);
|
||||
return match (utf8::prev(&iter.dec)) {
|
||||
r: rune => r,
|
||||
void => void,
|
||||
(utf8::more | utf8::invalid) =>
|
||||
abort("Invalid UTF-8 string (this should not happen)"),
|
||||
};
|
||||
};
|
||||
|
||||
// Causes the next call to [next] to return the provided rune, effectively
|
||||
// un-reading it. The next call using this iterator *must* be [next]; all other
|
||||
// functions will cause the program to abort until the pushed rune is consumed.
|
||||
// This does not modify the underlying string, and as such, subsequent calls to
|
||||
// functions like [prev] or [iter_str] will behave as if push were never called.
|
||||
export fn push(iter: *iterator, r: rune) void = {
|
||||
assert(iter.push is void);
|
||||
iter.push = r;
|
||||
};
|
||||
|
||||
// Return a substring from the next rune to the end of the string.
|
||||
export fn iter_str(iter: *iterator) str = {
|
||||
assert(iter.push is void);
|
||||
return fromutf8(iter.dec.src[iter.dec.offs..]);
|
||||
};
|
||||
|
||||
@test fn iter() void = {
|
||||
let s = iter("こんにちは");
|
||||
assert(prev(&s) is void);
|
||||
const expected1 = ['こ', 'ん'];
|
||||
for (let i = 0z; i < len(expected1); i += 1) {
|
||||
match (next(&s)) {
|
||||
r: rune => assert(r == expected1[i]),
|
||||
void => abort(),
|
||||
};
|
||||
};
|
||||
assert(iter_str(&s) == "にちは");
|
||||
assert(prev(&s) as rune == 'ん');
|
||||
const expected2 = ['ん', 'に', 'ち', 'は'];
|
||||
for (let i = 0z; i < len(expected2); i += 1) {
|
||||
match (next(&s)) {
|
||||
r: rune => assert(r == expected2[i]),
|
||||
void => abort(),
|
||||
};
|
||||
};
|
||||
assert(next(&s) is void);
|
||||
assert(next(&s) is void);
|
||||
push(&s, 'q');
|
||||
assert(next(&s) as rune == 'q');
|
||||
assert(prev(&s) as rune == 'は');
|
||||
|
||||
s = riter("にちは");
|
||||
const expected3 = ['は', 'ち', 'に'];
|
||||
for (let i = 0z; i< len(expected3); i += 1) {
|
||||
match (prev(&s)) {
|
||||
r: rune => assert(r == expected3[i]),
|
||||
void => abort(),
|
||||
};
|
||||
};
|
||||
assert(prev(&s) is void);
|
||||
assert(next(&s) as rune == 'に');
|
||||
};
|
||||
@ -0,0 +1,106 @@
|
||||
use bytes;
|
||||
use types;
|
||||
use strings;
|
||||
|
||||
// Converts an i64 to a string in the given base. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn i64tosb(i: i64, b: base) const str = {
|
||||
static assert(types::I64_MAX == 9223372036854775807);
|
||||
if (i >= 0) return u64tosb(i: u64, b);
|
||||
|
||||
static let buf: [65]u8 = [0...]; // 64 binary digits plus -
|
||||
|
||||
let s = types::string { data = &buf, ... };
|
||||
|
||||
buf[0] = '-': u32: u8;
|
||||
s.length = 1;
|
||||
|
||||
let u = strings::toutf8(u64tosb((-i): u64, b));
|
||||
assert(len(u) < len(buf));
|
||||
|
||||
bytes::copy(buf[1..len(u) + 1], u);
|
||||
s.length += len(u);
|
||||
|
||||
return *(&s: *str);
|
||||
};
|
||||
|
||||
// Converts a i32 to a string in the given base. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn i32tosb(i: i32, b: base) const str = i64tosb(i, b);
|
||||
|
||||
// Converts a i16 to a string in the given base. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn i16tosb(i: i16, b: base) const str = i64tosb(i, b);
|
||||
|
||||
// Converts a i8 to a string in the given base. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn i8tosb(i: i8, b: base) const str = i64tosb(i, b);
|
||||
|
||||
// Converts an int to a string in the given base. The return value is
|
||||
// statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn itosb(i: int, b: base) const str = i64tosb(i, b);
|
||||
|
||||
// Converts a i64 to a string in base 10. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn i64tos(i: i64) const str = i64tosb(i, base::DEC);
|
||||
|
||||
// Converts a i32 to a string in base 10. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn i32tos(i: i32) const str = i64tos(i);
|
||||
|
||||
// Converts a i16 to a string in base 10. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn i16tos(i: i16) const str = i64tos(i);
|
||||
|
||||
// Converts a i8 to a string in base 10. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn i8tos(i: i8) const str = i64tos(i);
|
||||
|
||||
// Converts a int to a string in base 10. The return value is statically
|
||||
// allocated and will be overwritten on subsequent calls; see [strings::dup] to
|
||||
// duplicate the result.
|
||||
export fn itos(i: int) const str = i64tos(i);
|
||||
|
||||
@test fn itosb() void = {
|
||||
assert("11010" == i64tosb(0b11010, base::BIN));
|
||||
assert("1234567" == i64tosb(0o1234567, base::OCT));
|
||||
assert("123456789" == i64tosb(123456789, base::DEC));
|
||||
assert("123456789ABCDEF" == i64tosb(0x123456789ABCDEF, base::HEX));
|
||||
assert("123456789ABCDEF" == i64tosb(0x123456789ABCDEF, base::HEX_UPPER));
|
||||
assert("123456789abcdef" == i64tosb(0x123456789ABCDEF, base::HEX_LOWER));
|
||||
assert("-1000000000000000000000000000000000000000000000000000000000000000"
|
||||
== i64tosb(types::I64_MIN, base::BIN));
|
||||
};
|
||||
|
||||
@test fn itos() void = {
|
||||
const samples: [_]i64 = [
|
||||
1234,
|
||||
4321,
|
||||
-1337,
|
||||
0,
|
||||
types::I64_MAX,
|
||||
types::I64_MIN,
|
||||
];
|
||||
const expected = [
|
||||
"1234",
|
||||
"4321",
|
||||
"-1337",
|
||||
"0",
|
||||
"9223372036854775807",
|
||||
"-9223372036854775808",
|
||||
];
|
||||
|
||||
for (let i = 0z; i < len(samples); i += 1) {
|
||||
const s = i64tos(samples[i]);
|
||||
assert(s == expected[i]);
|
||||
};
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
type arch_jmpbuf = [22]u64;
|
||||
@ -0,0 +1,65 @@
|
||||
use bytes;
|
||||
use bufio;
|
||||
use strings;
|
||||
use io;
|
||||
|
||||
// Joins together several path components with the path separator. The caller
|
||||
// must free the return value.
|
||||
export fn join(paths: str...) str = {
|
||||
// TODO: Normalize inputs so that if they end with a / we don't double
|
||||
// up on delimiters
|
||||
let sink = bufio::dynamic(io::mode::WRITE);
|
||||
let utf8 = true;
|
||||
for (let i = 0z; i < len(paths); i += 1) {
|
||||
let buf = strings::toutf8(paths[i]);
|
||||
let l = len(buf);
|
||||
if (l == 0) continue;
|
||||
for (l > 0 && buf[l - 1] == PATHSEP) {
|
||||
l -= 1;
|
||||
};
|
||||
for (let q = 0z; q < l) {
|
||||
let w = io::write(sink, buf[q..l]) as size;
|
||||
q += w;
|
||||
};
|
||||
if (i + 1 < len(paths)) {
|
||||
assert(io::write(sink, [PATHSEP]) as size == 1);
|
||||
};
|
||||
};
|
||||
|
||||
return strings::fromutf8_unsafe(bufio::finish(sink));
|
||||
};
|
||||
|
||||
@test fn join() void = {
|
||||
assert(PATHSEP == '/': u32: u8); // TODO: meh
|
||||
let i = join("foo");
|
||||
defer free(i);
|
||||
assert(i == "foo");
|
||||
|
||||
let p = join(i, "bar", "baz");
|
||||
defer free(p);
|
||||
assert(p == "foo/bar/baz");
|
||||
|
||||
let q = join(p, "bat", "bad");
|
||||
defer free(q);
|
||||
assert(q == "foo/bar/baz/bat/bad");
|
||||
|
||||
let r = join(p, q);
|
||||
defer free(r);
|
||||
assert(r == "foo/bar/baz/foo/bar/baz/bat/bad");
|
||||
|
||||
let p = join("foo/", "bar");
|
||||
defer free(p);
|
||||
assert(p == "foo/bar");
|
||||
|
||||
let p = join("foo///", "bar");
|
||||
defer free(p);
|
||||
assert(p == "foo/bar");
|
||||
|
||||
let p = join("foo", "", "bar");
|
||||
defer free(p);
|
||||
assert(p == "foo/bar");
|
||||
|
||||
let p = join("/", "foo", "bar", "baz");
|
||||
defer free(p);
|
||||
assert(p == "/foo/bar/baz");
|
||||
};
|
||||
@ -0,0 +1,524 @@
|
||||
// hare::lex provides a lexer for Hare source code.
|
||||
use ascii;
|
||||
use bufio;
|
||||
use encoding::utf8;
|
||||
use fmt;
|
||||
use io;
|
||||
use sort;
|
||||
use strconv;
|
||||
use strings;
|
||||
|
||||
// State associated with a lexer.
|
||||
export type lexer = struct {
|
||||
in: *io::stream,
|
||||
path: str,
|
||||
loc: (uint, uint),
|
||||
un: ((token, location) | void),
|
||||
rb: [2](rune | io::EOF | void),
|
||||
};
|
||||
|
||||
// A syntax error
|
||||
export type syntax = (location, str)!;
|
||||
|
||||
// All possible lexer errors
|
||||
export type error = (io::error | syntax)!;
|
||||
|
||||
// Returns a human-friendly string for a given error
|
||||
export fn strerror(err: error) const str = {
|
||||
static let buf: [2048]u8 = [0...];
|
||||
return match (err) {
|
||||
err: io::error => io::strerror(err),
|
||||
s: syntax => fmt::bsprintf(buf, "{}:{},{}: Syntax error: {}",
|
||||
s.0.path, s.0.line, s.0.col, s.1),
|
||||
};
|
||||
};
|
||||
|
||||
// Initializes a new lexer for the given input stream. The path is borrowed.
|
||||
export fn init(in: *io::stream, path: str) lexer = lexer {
|
||||
in = in,
|
||||
path = path,
|
||||
loc = (1, 1),
|
||||
un = void,
|
||||
rb = [void...],
|
||||
};
|
||||
|
||||
// Returns the next token from the lexer.
|
||||
export fn lex(lex: *lexer) ((token, location) | io::EOF | error) = {
|
||||
match (lex.un) {
|
||||
tok: (token, location) => {
|
||||
lex.un = void;
|
||||
return tok;
|
||||
},
|
||||
void => void,
|
||||
};
|
||||
|
||||
let loc = location { ... };
|
||||
let r: rune = match (nextw(lex)?) {
|
||||
io::EOF => return io::EOF,
|
||||
r: (rune, location) => {
|
||||
loc = r.1;
|
||||
r.0;
|
||||
},
|
||||
};
|
||||
|
||||
if (is_name(r, false)) {
|
||||
unget(lex, r);
|
||||
return lex_name(lex, loc);
|
||||
};
|
||||
if (ascii::isdigit(r)) {
|
||||
unget(lex, r);
|
||||
abort(); // TODO: Literals
|
||||
};
|
||||
|
||||
let tok: token = switch (r) {
|
||||
* => return syntaxerr(loc, "invalid character"),
|
||||
'"', '\'' => {
|
||||
unget(lex, r);
|
||||
return lex_rn_str(lex, loc);
|
||||
},
|
||||
'.', '<', '>' => return lex3(lex, loc, r),
|
||||
'^', '*', '%', '/', '+', '-', ':', '!', '&', '|', '=' => {
|
||||
return lex2(lex, loc, r);
|
||||
},
|
||||
'~' => btoken::BNOT,
|
||||
',' => btoken::COMMA,
|
||||
'{' => btoken::LBRACE,
|
||||
'[' => btoken::LBRACKET,
|
||||
'(' => btoken::LPAREN,
|
||||
'}' => btoken::RBRACE,
|
||||
']' => btoken::RBRACKET,
|
||||
')' => btoken::RPAREN,
|
||||
';' => btoken::SEMICOLON,
|
||||
'?' => btoken::QUESTION,
|
||||
};
|
||||
return (tok, loc);
|
||||
};
|
||||
|
||||
fn is_name(r: rune, num: bool) bool =
|
||||
ascii::isalpha(r) || r == '_' || r == '@' || (num && ascii::isdigit(r));
|
||||
|
||||
fn ncmp(a: const *void, b: const *void) int = {
|
||||
let a = a: const *str, b = b: const *str;
|
||||
return match (ascii::strcmp(*a, *b)) {
|
||||
void => abort("non-ascii name"), // TODO: Bubble me up
|
||||
i: int => i,
|
||||
};
|
||||
};
|
||||
|
||||
fn lex_unicode(lex: *lexer, loc: location, n: size) (rune | error) = {
|
||||
assert(n < 9);
|
||||
let buf: [9]u8 = [0...];
|
||||
for (let i = 0z; i < n; i += 1z) {
|
||||
let r = match (next(lex)?) {
|
||||
io::EOF => return syntaxerr(loc,
|
||||
"unexpected EOF scanning for escape"),
|
||||
r: rune => r,
|
||||
};
|
||||
if (!ascii::isxdigit(r)) {
|
||||
return syntaxerr(loc,
|
||||
"unexpected rune scanning for escape");
|
||||
};
|
||||
buf[i] = r: u32: u8;
|
||||
};
|
||||
let s = strings::fromutf8_unsafe(buf[..n]);
|
||||
return strconv::stou32b(s, strconv::base::HEX) as u32: rune;
|
||||
};
|
||||
|
||||
fn lex_rune(lex: *lexer, loc: location) (rune | error) = {
|
||||
let r = match (next(lex)?) {
|
||||
io::EOF => return syntaxerr(loc,
|
||||
"unexpected EOF scanning for rune"),
|
||||
r: rune => r,
|
||||
};
|
||||
if (r != '\\') {
|
||||
return r;
|
||||
};
|
||||
r = match (next(lex)?) {
|
||||
io::EOF => return syntaxerr(loc,
|
||||
"unexpected EOF scanning for escape"),
|
||||
r: rune => r,
|
||||
};
|
||||
return switch (r) {
|
||||
'\\' => '\\',
|
||||
'\'' => '\'',
|
||||
'0' => '\0',
|
||||
'a' => '\a',
|
||||
'b' => '\b',
|
||||
'f' => '\f',
|
||||
'n' => '\n',
|
||||
'r' => '\r',
|
||||
't' => '\t',
|
||||
'v' => '\v',
|
||||
'"' => '\"',
|
||||
'x' => lex_unicode(lex, loc, 2),
|
||||
'u' => lex_unicode(lex, loc, 4),
|
||||
'U' => lex_unicode(lex, loc, 8),
|
||||
};
|
||||
};
|
||||
|
||||
fn lex_string(
|
||||
lex: *lexer,
|
||||
loc: location,
|
||||
) ((token, location) | io::EOF | error) = {
|
||||
let chars: []u8 = [];
|
||||
for (true) match (next(lex)?) {
|
||||
io::EOF => return syntaxerr(loc, "unexpected EOF scanning string literal"),
|
||||
r: rune =>
|
||||
if (r == '"') break
|
||||
else {
|
||||
unget(lex, r);
|
||||
r = lex_rune(lex, loc)?;
|
||||
append(chars, ...utf8::encoderune(r));
|
||||
},
|
||||
};
|
||||
return (strings::fromutf8(chars): literal, loc);
|
||||
};
|
||||
|
||||
fn lex_rn_str(
|
||||
lex: *lexer,
|
||||
loc: location,
|
||||
) ((token, location) | io::EOF | error) = {
|
||||
let r = match (next(lex)) {
|
||||
r: rune => r,
|
||||
(io::EOF | io::error) => abort(),
|
||||
};
|
||||
switch (r) {
|
||||
'\"' => return lex_string(lex, loc),
|
||||
'\'' => void,
|
||||
* => abort(), // Invariant
|
||||
};
|
||||
|
||||
// Rune literal
|
||||
let ret: (token, location) = (lex_rune(lex, loc)?: literal, loc);
|
||||
match (next(lex)?) {
|
||||
io::EOF =>
|
||||
return syntaxerr(loc, "unexpected EOF"),
|
||||
n: rune => if (n != '\'')
|
||||
return syntaxerr(loc, "expected \"\'\""),
|
||||
};
|
||||
return ret;
|
||||
};
|
||||
|
||||
fn lex_name(
|
||||
lex: *lexer,
|
||||
loc: location,
|
||||
) ((token, location) | io::EOF | error) = {
|
||||
let chars: []u8 = [];
|
||||
match (next(lex)) {
|
||||
r: rune => {
|
||||
assert(is_name(r, false));
|
||||
append(chars, ...utf8::encoderune(r));
|
||||
},
|
||||
(io::EOF | io::error) => abort(),
|
||||
};
|
||||
|
||||
for (true) match (next(lex)?) {
|
||||
io::EOF => break,
|
||||
r: rune => {
|
||||
if (!is_name(r, true)) {
|
||||
unget(lex, r);
|
||||
break;
|
||||
};
|
||||
append(chars, ...utf8::encoderune(r));
|
||||
},
|
||||
};
|
||||
|
||||
let n = strings::fromutf8(chars);
|
||||
return match (sort::search(bmap[..btoken::LAST_KEYWORD+1],
|
||||
size(str), &n, &ncmp)) {
|
||||
// TODO: Validate that names are ASCII
|
||||
null => (n: name: token, loc),
|
||||
v: *void => {
|
||||
let tok = v: uintptr - &bmap[0]: uintptr;
|
||||
tok /= size(str): uintptr;
|
||||
(tok: btoken: token, loc);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
fn lex2(
|
||||
lexr: *lexer,
|
||||
loc: location,
|
||||
r: rune,
|
||||
) ((token, location) | io::EOF | error) = {
|
||||
let n = match (next(lexr)?) {
|
||||
io::EOF => io::EOF,
|
||||
r: rune => r,
|
||||
};
|
||||
let tok: token = switch (r) {
|
||||
'^' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'^' => return (btoken::LXOR: token, loc),
|
||||
'=' => return (btoken::BXOREQ: token, loc),
|
||||
* => btoken::BXOR,
|
||||
},
|
||||
io::EOF => btoken::BXOR,
|
||||
},
|
||||
'*' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'=' => return (btoken::TIMESEQ: token, loc),
|
||||
* => btoken::TIMES,
|
||||
},
|
||||
io::EOF => btoken::TIMES,
|
||||
},
|
||||
'/' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'=' => return (btoken::DIVEQ: token, loc),
|
||||
'/' => {
|
||||
// Comment
|
||||
for (true) match (next(lexr)?) {
|
||||
io::EOF => break,
|
||||
r: rune => if (r == '\n') {
|
||||
break;
|
||||
},
|
||||
};
|
||||
return lex(lexr);
|
||||
},
|
||||
* => btoken::DIV,
|
||||
},
|
||||
io::EOF => btoken::DIV,
|
||||
},
|
||||
'%' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'=' => return (btoken::MODEQ: token, loc),
|
||||
* => btoken::MODULO,
|
||||
},
|
||||
io::EOF => btoken::MODULO,
|
||||
},
|
||||
'+' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'=' => return (btoken::PLUSEQ: token, loc),
|
||||
* => btoken::PLUS,
|
||||
},
|
||||
io::EOF => btoken::PLUS,
|
||||
},
|
||||
'-' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'=' => return (btoken::MINUSEQ: token, loc),
|
||||
* => btoken::MINUS,
|
||||
},
|
||||
io::EOF => btoken::MINUS,
|
||||
},
|
||||
':' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
':' => return (btoken::DOUBLE_COLON: token, loc),
|
||||
* => btoken::COLON,
|
||||
},
|
||||
io::EOF => btoken::COLON,
|
||||
},
|
||||
'!' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'=' => return (btoken::NEQUAL: token, loc),
|
||||
* => btoken::LNOT,
|
||||
},
|
||||
io::EOF => btoken::LNOT,
|
||||
},
|
||||
'&' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'&' => return (btoken::LAND: token, loc),
|
||||
'=' => return (btoken::ANDEQ: token, loc),
|
||||
* => btoken::BAND,
|
||||
},
|
||||
io::EOF => btoken::BAND,
|
||||
},
|
||||
'|' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'|' => return (btoken::LOR: token, loc),
|
||||
'=' => return (btoken::OREQ: token, loc),
|
||||
* => btoken::BOR,
|
||||
},
|
||||
io::EOF => btoken::BOR,
|
||||
},
|
||||
'=' => match (n) {
|
||||
r: rune => switch (r) {
|
||||
'=' => return (btoken::LEQUAL: token, loc),
|
||||
* => btoken::EQUAL,
|
||||
},
|
||||
io::EOF => btoken::EQUAL,
|
||||
},
|
||||
* => return syntaxerr(loc, "unknown token sequence"),
|
||||
};
|
||||
unget(lexr, n);
|
||||
return (tok, loc);
|
||||
};
|
||||
|
||||
fn lex3(
|
||||
lex: *lexer,
|
||||
loc: location,
|
||||
r: rune,
|
||||
) ((token, location) | io::EOF | error) = {
|
||||
let n = match (next(lex)?) {
|
||||
io::EOF => return switch (r) {
|
||||
'.' => (btoken::DOT: token, loc),
|
||||
'<' => (btoken::LESS: token, loc),
|
||||
'>' => (btoken::GREATER: token, loc),
|
||||
},
|
||||
r: rune => r,
|
||||
};
|
||||
return switch (r) {
|
||||
'.' => lex3dot(lex, loc, n),
|
||||
'<' => lex3lt(lex, loc, n),
|
||||
'>' => lex3gt(lex, loc, n),
|
||||
* => syntaxerr(loc, "unknown token sequence"),
|
||||
};
|
||||
};
|
||||
|
||||
fn lex3dot(
|
||||
lex: *lexer,
|
||||
loc: location,
|
||||
n: rune,
|
||||
) ((token, location) | io::EOF | error) = {
|
||||
let tok: token = switch (n) {
|
||||
'.' => {
|
||||
let q = match (next(lex)?) {
|
||||
io::EOF => io::EOF,
|
||||
r: rune => r,
|
||||
};
|
||||
let t = match (q) {
|
||||
r: rune => switch (r) {
|
||||
'.' => return (btoken::ELLIPSIS: token, loc),
|
||||
* => btoken::SLICE,
|
||||
},
|
||||
io::EOF => btoken::SLICE,
|
||||
};
|
||||
unget(lex, q);
|
||||
t;
|
||||
},
|
||||
* => {
|
||||
unget(lex, n);
|
||||
btoken::DOT;
|
||||
}
|
||||
};
|
||||
return (tok, loc);
|
||||
};
|
||||
|
||||
fn lex3lt(
|
||||
lex: *lexer,
|
||||
loc: location,
|
||||
n: rune,
|
||||
) ((token, location) | io::EOF | error) = {
|
||||
let tok: token = switch (n) {
|
||||
'<' => {
|
||||
let q = match (next(lex)?) {
|
||||
io::EOF => io::EOF,
|
||||
r: rune => r,
|
||||
};
|
||||
let t = match (q) {
|
||||
r: rune => switch (r) {
|
||||
'=' => return (btoken::LSHIFTEQ: token, loc),
|
||||
* => btoken::LSHIFT,
|
||||
},
|
||||
io::EOF => btoken::LSHIFT,
|
||||
};
|
||||
unget(lex, q);
|
||||
t;
|
||||
},
|
||||
'=' => btoken::LESSEQ,
|
||||
* => {
|
||||
unget(lex, n);
|
||||
btoken::LESS;
|
||||
}
|
||||
};
|
||||
return (tok, loc);
|
||||
};
|
||||
|
||||
fn lex3gt(
|
||||
lex: *lexer,
|
||||
loc: location,
|
||||
n: rune,
|
||||
) ((token, location) | io::EOF | error) = {
|
||||
let tok: token = switch (n) {
|
||||
'>' => {
|
||||
let q = match (next(lex)?) {
|
||||
io::EOF => io::EOF,
|
||||
r: rune => r,
|
||||
};
|
||||
let t = match (q) {
|
||||
r: rune => switch (r) {
|
||||
'=' => return (btoken::RSHIFTEQ: token, loc),
|
||||
* => btoken::RSHIFT,
|
||||
},
|
||||
io::EOF => btoken::RSHIFT,
|
||||
};
|
||||
unget(lex, q);
|
||||
t;
|
||||
},
|
||||
'=' => btoken::GREATEREQ,
|
||||
* => {
|
||||
unget(lex, n);
|
||||
btoken::GREATER;
|
||||
}
|
||||
};
|
||||
return (tok, loc);
|
||||
};
|
||||
|
||||
// Unlex a single token. The next call to [lex] will return this token, location
|
||||
// pair. Only one unlex is supported at a time; you must call [lex] before
|
||||
// calling [unlex] again.
|
||||
export fn unlex(lex: *lexer, tok: (token, location)) void = {
|
||||
assert(lex.un is void, "attempted to unlex more than one token");
|
||||
lex.un = tok;
|
||||
};
|
||||
|
||||
fn next(lex: *lexer) (rune | io::EOF | io::error) = {
|
||||
match (lex.rb[0]) {
|
||||
void => void,
|
||||
r: (rune | io::EOF) => {
|
||||
lex.rb[0] = lex.rb[1];
|
||||
lex.rb[1] = void;
|
||||
return r;
|
||||
},
|
||||
};
|
||||
|
||||
for (true) {
|
||||
return match (bufio::scanrune(lex.in)) {
|
||||
e: (io::EOF | io::error) => e,
|
||||
r: rune => {
|
||||
lexloc(lex, r);
|
||||
r;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
abort("unreachable");
|
||||
};
|
||||
|
||||
fn nextw(lex: *lexer) ((rune, location) | io::EOF | io::error) = {
|
||||
for (true) {
|
||||
let loc = mkloc(lex);
|
||||
match (next(lex)) {
|
||||
e: (io::error | io::EOF) => return e,
|
||||
r: rune => if (!ascii::isspace(r)) {
|
||||
return (r, loc);
|
||||
},
|
||||
};
|
||||
};
|
||||
abort();
|
||||
};
|
||||
|
||||
fn lexloc(lex: *lexer, r: rune) void = {
|
||||
switch (r) {
|
||||
'\n' => {
|
||||
lex.loc.0 += 1;
|
||||
lex.loc.1 = 1;
|
||||
},
|
||||
'\t' => lex.loc.1 += 8,
|
||||
* => lex.loc.1 += 1,
|
||||
};
|
||||
};
|
||||
|
||||
fn unget(lex: *lexer, r: (rune | io::EOF)) void = {
|
||||
if (!(lex.rb[0] is void)) {
|
||||
assert(lex.rb[1] is void, "ungot too many runes");
|
||||
lex.rb[1] = lex.rb[0];
|
||||
};
|
||||
lex.rb[0] = r;
|
||||
};
|
||||
|
||||
fn mkloc(lex: *lexer) location = location {
|
||||
path = lex.path,
|
||||
line = lex.loc.0,
|
||||
col = lex.loc.1,
|
||||
};
|
||||
|
||||
fn syntaxerr(loc: location, why: str) error = (loc, why);
|
||||
@ -0,0 +1,60 @@
|
||||
use strings;
|
||||
|
||||
type limited_stream = struct {
|
||||
stream: stream,
|
||||
source: *stream,
|
||||
limit: size,
|
||||
};
|
||||
|
||||
fn limited_stream_new(source: *stream, limit: size) *limited_stream = {
|
||||
return alloc(limited_stream {
|
||||
stream = stream {
|
||||
name = strings::dup(source.name),
|
||||
closer = &limited_close,
|
||||
...
|
||||
},
|
||||
source = source,
|
||||
limit = limit,
|
||||
});
|
||||
};
|
||||
|
||||
// Create an overlay stream that only allows a limited amount of bytes to be
|
||||
// read from the underlying stream.
|
||||
export fn limitreader(source: *stream, limit: size) *stream = {
|
||||
let stream = limited_stream_new(source, limit);
|
||||
stream.stream.reader = &limited_read;
|
||||
return &stream.stream;
|
||||
};
|
||||
|
||||
// Create an overlay stream that only allows a limited amount of bytes to be
|
||||
// written to the underlying stream.
|
||||
export fn limitwriter(source: *stream, limit: size) *stream = {
|
||||
let stream = limited_stream_new(source, limit);
|
||||
stream.stream.writer = &limited_write;
|
||||
return &stream.stream;
|
||||
};
|
||||
|
||||
fn limited_read(s: *stream, buf: []u8) (size | EOF | error) = {
|
||||
let stream = s: *limited_stream;
|
||||
if (len(buf) > stream.limit) {
|
||||
buf = buf[..stream.limit];
|
||||
};
|
||||
stream.limit -= len(buf);
|
||||
return read(stream.source, buf);
|
||||
};
|
||||
|
||||
fn limited_write(s: *stream, buf: const []u8) (size | error) = {
|
||||
let stream = s: *limited_stream;
|
||||
let slice = if (len(buf) > stream.limit) {
|
||||
buf[..stream.limit];
|
||||
} else {
|
||||
buf[..];
|
||||
};
|
||||
stream.limit -= len(slice);
|
||||
return write(stream.source, slice);
|
||||
};
|
||||
|
||||
fn limited_close(s: *stream) void = {
|
||||
free(s.name);
|
||||
free(s);
|
||||
};
|
||||
@ -0,0 +1,54 @@
|
||||
// Minimum value which can be stored in an i8 type.
|
||||
export def I8_MIN: i8 = -128;
|
||||
|
||||
// Maximum value which can be stored in an i8 type.
|
||||
export def I8_MAX: i8 = 127;
|
||||
|
||||
// Minimum value which can be stored in an i16 type.
|
||||
export def I16_MIN: i16 = -32708;
|
||||
|
||||
// Maximum value which can be stored in an i16 type.
|
||||
export def I16_MAX: i16 = 32707;
|
||||
|
||||
// Minimum value which can be stored in an i32 type.
|
||||
export def I32_MIN: i32 = -2147483648;
|
||||
|
||||
// Maximum value which can be stored in an i32 type.
|
||||
export def I32_MAX: i32 = 2147483647;
|
||||
|
||||
// Minimum value which can be stored in an i64 type
|
||||
export def I64_MIN: i64 = -9223372036854775808;
|
||||
|
||||
// Maximum value which can be stored in an i64 type.
|
||||
export def I64_MAX: i64 = 9223372036854775807;
|
||||
|
||||
|
||||
// Minimum value which can be stored in a u8 type.
|
||||
export def U8_MIN: u8 = 0;
|
||||
|
||||
// Maximum value which can be stored in a u8 type.
|
||||
export def U8_MAX: u8 = 255;
|
||||
|
||||
// Minimum value which can be stored in a u16 type
|
||||
export def U16_MIN: u16 = 0;
|
||||
|
||||
// Maximum value which can be stored in a u16 type.
|
||||
export def U16_MAX: u16 = 65535;
|
||||
|
||||
// Minimum value which can be stored in a u32 type
|
||||
export def U32_MIN: u32 = 0;
|
||||
|
||||
// Maximum value which can be stored in a u32 type.
|
||||
export def U32_MAX: u32 = 4294967295;
|
||||
|
||||
// Minimum value which can be stored in a u64 type
|
||||
export def U64_MIN: u64 = 0;
|
||||
|
||||
// Maximum value which can be stored in a u64 type.
|
||||
export def U64_MAX: u64 = 18446744073709551615;
|
||||
|
||||
// Maximum value which can be stored in a rune.
|
||||
export def RUNE_MIN: rune = U32_MIN: rune;
|
||||
|
||||
// Maximum value which can be stored in a rune.
|
||||
export def RUNE_MAX: rune = U32_MAX: rune;
|
||||
@ -0,0 +1,73 @@
|
||||
// Reads a u16 from a buffer in little-endian order.
|
||||
export fn legetu16(buf: []u8) u16 = {
|
||||
return
|
||||
(buf[1] << 8u16) |
|
||||
(buf[0] << 0);
|
||||
};
|
||||
|
||||
// Writes a u16 into a buffer in little-endian order.
|
||||
export fn leputu16(buf: []u8, in: u16) void = {
|
||||
buf[0] = (in >> 0): u8;
|
||||
buf[1] = (in >> 8): u8;
|
||||
};
|
||||
|
||||
// Reads a u32 from a buffer in little-endian order.
|
||||
export fn legetu32(buf: []u8) u32 = {
|
||||
return
|
||||
(buf[3] << 24u32) |
|
||||
(buf[2] << 16u32) |
|
||||
(buf[1] << 8u32) |
|
||||
(buf[0] << 0);
|
||||
};
|
||||
|
||||
// Writes a u32 into a buffer in little-endian order.
|
||||
export fn leputu32(buf: []u8, in: u32) void = {
|
||||
buf[0] = (in): u8;
|
||||
buf[1] = (in >> 8): u8;
|
||||
buf[2] = (in >> 16): u8;
|
||||
buf[3] = (in >> 24): u8;
|
||||
};
|
||||
|
||||
// Reads a u64 from a buffer in little-endian order.
|
||||
export fn legetu64(buf: []u8) u64 = {
|
||||
return
|
||||
(buf[7] << 56u64) |
|
||||
(buf[6] << 48u64) |
|
||||
(buf[5] << 40u64) |
|
||||
(buf[4] << 32u64) |
|
||||
(buf[3] << 24u64) |
|
||||
(buf[2] << 16u64) |
|
||||
(buf[1] << 8u64) |
|
||||
(buf[0] << 0);
|
||||
};
|
||||
|
||||
// Writes a u64 into a buffer in little-endian order.
|
||||
export fn leputu64(buf: []u8, in: u64) void = {
|
||||
buf[0] = (in >> 0): u8;
|
||||
buf[1] = (in >> 8): u8;
|
||||
buf[2] = (in >> 16): u8;
|
||||
buf[3] = (in >> 24): u8;
|
||||
buf[4] = (in >> 32): u8;
|
||||
buf[5] = (in >> 40): u8;
|
||||
buf[6] = (in >> 48): u8;
|
||||
buf[7] = (in >> 56): u8;
|
||||
};
|
||||
|
||||
@test fn little() void = {
|
||||
let buf: [8]u8 = [0...];
|
||||
leputu16(buf, 0x1234);
|
||||
assert(buf[0] == 0x34 && buf[1] == 0x12);
|
||||
assert(legetu16(buf) == 0x1234);
|
||||
|
||||
leputu32(buf, 0x12345678);
|
||||
assert(buf[0] == 0x78 && buf[1] == 0x56
|
||||
&& buf[2] == 0x34 && buf[3] == 0x12);
|
||||
assert(legetu32(buf) == 0x12345678);
|
||||
|
||||
leputu64(buf, 0x1234567887654321);
|
||||
assert(buf[0] == 0x21 && buf[1] == 0x43
|
||||
&& buf[2] == 0x65 && buf[3] == 0x87
|
||||
&& buf[4] == 0x78 && buf[5] == 0x56
|
||||
&& buf[6] == 0x34 && buf[7] == 0x12);
|
||||
assert(legetu64(buf) == 0x1234567887654321);
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
use getopt;
|
||||
use os;
|
||||
|
||||
export fn main() void = {
|
||||
let help: []getopt::help = [
|
||||
"compile, run, and test Hare programs",
|
||||
"<build | cache | deps | run | test | version>", "args...",
|
||||
];
|
||||
let cmd = getopt::parse(os::args, help...);
|
||||
defer getopt::finish(&cmd);
|
||||
if (len(cmd.args) < 1) {
|
||||
getopt::printusage(os::stderr, os::args[0], help...);
|
||||
os::exit(1);
|
||||
};
|
||||
if (cmd.args[0] == "build") build(cmd.args)
|
||||
else if (cmd.args[0] == "cache") cache(cmd.args)
|
||||
else if (cmd.args[0] == "deps") deps(cmd.args)
|
||||
else if (cmd.args[0] == "run") run(cmd.args)
|
||||
else if (cmd.args[0] == "test") test(cmd.args)
|
||||
else if (cmd.args[0] == "version") version(cmd.args)
|
||||
else {
|
||||
getopt::printusage(os::stderr, os::args[0], help...);
|
||||
os::exit(1);
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,163 @@
|
||||
// This is a simple memory allocator, based on Appel, Andrew W., and David A.
|
||||
// Naumann. "Verified sequential malloc/free." It is not thread-safe.
|
||||
//
|
||||
// Large allocations are handled with mmap.
|
||||
//
|
||||
// For small allocations, we set up 50 bins, where each bin is responsible for
|
||||
// 16 different allocation sizes (e.g. bin 1 handles allocations from 10 thru 26
|
||||
// bytes); except for the first and last bin, which are responsible for fewer
|
||||
// than 16 allocation sizes.
|
||||
//
|
||||
// Each bin is 1MiB (BIGBLOCK) in size. We ceil the allocation size to the
|
||||
// largest size supported for this bin, then break the bin up into smaller
|
||||
// blocks. Each block is structured as [{sz: size, data..., link: *void}...];
|
||||
// where sz is the size of this (small) block, data is is set aside for the
|
||||
// user's actual allocation, and link is a pointer to the next bin's data field.
|
||||
//
|
||||
// In short, a bin for a particular size is pre-filled with all allocations of
|
||||
// that size, and the first word of each allocation is set to a pointer to the
|
||||
// next allocation. As such, malloc becomes:
|
||||
//
|
||||
// 1. Look up bin; pre-fill if not already allocated
|
||||
// 2. Let p = bin; bin = *bin; return p
|
||||
//
|
||||
// Then, free is simply:
|
||||
// 1. Look up bin
|
||||
// 2. *p = bin;
|
||||
// 3. bin = p;
|
||||
//
|
||||
// Note that over time this can cause the ordering of the allocations in each
|
||||
// bin to become non-continuous. This has no consequences for performance or
|
||||
// correctness.
|
||||
|
||||
def ALIGN: size = 2;
|
||||
def WORD: size = size(size);
|
||||
def WASTE: size = WORD * ALIGN - WORD;
|
||||
def BIGBLOCK: size = (2 << 16) * WORD;
|
||||
|
||||
let bins: [50]nullable *void = [null...];
|
||||
|
||||
fn bin2size(b: size) size = ((b + 1) * ALIGN - 1) * WORD;
|
||||
|
||||
fn size2bin(s: size) size = {
|
||||
assert(s <= bin2size(len(bins) - 1), "Size exceeds maximum for bin");
|
||||
return (s + (WORD * (ALIGN - 1) - 1)) / (WORD * ALIGN);
|
||||
};
|
||||
|
||||
// Allocates n bytes of memory and returns a pointer to them, or null if there
|
||||
// is insufficient memory.
|
||||
export fn malloc(n: size) nullable *void = {
|
||||
return if (n == 0) null
|
||||
else if (n > bin2size(len(bins) - 1)) malloc_large(n)
|
||||
else malloc_small(n);
|
||||
};
|
||||
|
||||
fn malloc_large(n: size) nullable *void = {
|
||||
let p = segmalloc(n + WASTE + WORD);
|
||||
if (p == null) {
|
||||
return null;
|
||||
};
|
||||
let bsize = (p: uintptr + WASTE: uintptr): *[1]size;
|
||||
bsize[0] = n;
|
||||
return (p: uintptr + WASTE: uintptr + WORD: uintptr): nullable *void;
|
||||
};
|
||||
|
||||
fn malloc_small(n: size) nullable *void = {
|
||||
const b = size2bin(n);
|
||||
let p = bins[b];
|
||||
if (p == null) {
|
||||
p = fill_bin(b);
|
||||
if (p != null) {
|
||||
bins[b] = p;
|
||||
};
|
||||
};
|
||||
return if (p != null) {
|
||||
let q = *(p: **void);
|
||||
bins[b] = q;
|
||||
p;
|
||||
} else null;
|
||||
};
|
||||
|
||||
fn fill_bin(b: size) nullable *void = {
|
||||
const s = bin2size(b);
|
||||
let p = segmalloc(BIGBLOCK);
|
||||
return if (p == null) null else list_from_block(s, p: uintptr);
|
||||
};
|
||||
|
||||
fn list_from_block(s: size, p: uintptr) nullable *void = {
|
||||
const nblocks = (BIGBLOCK - WASTE) / (s + WORD);
|
||||
|
||||
let q = p + WASTE: uintptr; // align q+WORD
|
||||
for (let j = 0z; j != nblocks - 1; j += 1) {
|
||||
let sz = q: *size;
|
||||
let useralloc = q + WORD: uintptr; // aligned
|
||||
let next = (useralloc + s: uintptr + WORD: uintptr): *void;
|
||||
*sz = s;
|
||||
*(useralloc: **void) = next;
|
||||
q += s: uintptr + WORD: uintptr;
|
||||
};
|
||||
|
||||
// Terminate last block:
|
||||
(q: *[1]size)[0] = s;
|
||||
*((q + 1: uintptr): *nullable *void) = null;
|
||||
|
||||
// Return first block:
|
||||
return (p + WASTE: uintptr + WORD: uintptr): *void;
|
||||
};
|
||||
|
||||
// Frees a pointer previously allocated with [malloc].
|
||||
export @symbol("rt.free") fn free_(_p: nullable *void) void = {
|
||||
if (_p != null) {
|
||||
let p = _p: *void;
|
||||
let bsize = (p: uintptr - size(size): uintptr): *[1]size;
|
||||
let s = bsize[0];
|
||||
if (s <= bin2size(len(bins) - 1)) free_small(p, s)
|
||||
else free_large(p, s);
|
||||
};
|
||||
};
|
||||
|
||||
fn free_large(_p: *void, s: size) void = {
|
||||
let p = (_p: uintptr - (WASTE: uintptr + WORD: uintptr)): *void;
|
||||
segfree(p, s + WASTE + WORD);
|
||||
};
|
||||
|
||||
fn free_small(p: *void, s: size) void = {
|
||||
let b = size2bin(s);
|
||||
let q = bins[b];
|
||||
*(p: **void) = q;
|
||||
bins[b] = p: nullable *void;
|
||||
};
|
||||
|
||||
// Changes the allocation size of a pointer to n bytes. If n is smaller than
|
||||
// the prior allocation, it is truncated; otherwise the allocation is expanded
|
||||
// and the values of the new bytes are undefined. May return a different pointer
|
||||
// than the one given if there is insufficient space to expand the pointer
|
||||
// in-place. Returns null if there is insufficient memory to support the
|
||||
// request.
|
||||
export fn realloc(_p: nullable *void, n: size) nullable *void = {
|
||||
if (n == 0) {
|
||||
free_(_p);
|
||||
return null;
|
||||
} else if (_p == null) {
|
||||
return malloc(n);
|
||||
};
|
||||
|
||||
let p = _p: *void;
|
||||
let bsize = (p: uintptr - size(size): uintptr): *size;
|
||||
let s = *bsize;
|
||||
if (s >= n) {
|
||||
return p;
|
||||
};
|
||||
|
||||
if (n < bin2size(len(bins) - 1) && size2bin(n) == size2bin(s)) {
|
||||
return p;
|
||||
};
|
||||
|
||||
let new = malloc(n);
|
||||
if (new != null) {
|
||||
memcpy(new: *void, p, s);
|
||||
free(p);
|
||||
};
|
||||
|
||||
return new;
|
||||
};
|
||||
@ -0,0 +1,333 @@
|
||||
use bufio;
|
||||
use bytes;
|
||||
use encoding::hex;
|
||||
use encoding::utf8;
|
||||
use errors;
|
||||
use fmt;
|
||||
use fs;
|
||||
use hare::ast;
|
||||
use hare::unparse;
|
||||
use io;
|
||||
use os;
|
||||
use path;
|
||||
use strconv;
|
||||
use strings;
|
||||
use time;
|
||||
|
||||
// The manifest file format is a series of line-oriented records. Lines starting
|
||||
// with # are ignored.
|
||||
//
|
||||
// - "version" indicates the manifest format version, currently 1.
|
||||
// - "input" is an input file, and its fields are the file hash, path, inode,
|
||||
// and mtime as a Unix timestamp.
|
||||
// - "module" is a version of a module, and includes the module hash and the set
|
||||
// of input hashes which produce it.
|
||||
|
||||
def VERSION: int = 1;
|
||||
|
||||
fn getinput(in: []input, hash: []u8) nullable *input = {
|
||||
for (let i = 0z; i < len(in); i += 1) {
|
||||
if (bytes::equal(in[i].hash, hash)) {
|
||||
return &in[i];
|
||||
};
|
||||
};
|
||||
return null;
|
||||
};
|
||||
|
||||
// Loads the module manifest from the build cache for the given ident. The
|
||||
// return value borrows the ident parameter. If the module is not found, an
|
||||
// empty manifest is returned.
|
||||
export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = {
|
||||
let manifest = manifest {
|
||||
ident = ident,
|
||||
inputs = [],
|
||||
versions = [],
|
||||
};
|
||||
let ipath = identpath(manifest.ident);
|
||||
defer free(ipath);
|
||||
let cachedir = path::join(ctx.cache, ipath);
|
||||
defer free(cachedir);
|
||||
|
||||
let mpath = path::join(cachedir, "manifest");
|
||||
defer free(mpath);
|
||||
|
||||
// TODO: We can probably eliminate these locks by using atomic writes
|
||||
// instead
|
||||
let l = lock(ctx.fs, cachedir)?;
|
||||
defer unlock(ctx.fs, cachedir, l);
|
||||
|
||||
let file = match (fs::open(ctx.fs, mpath, fs::flags::RDONLY)) {
|
||||
errors::noentry => return manifest,
|
||||
err: fs::error => return err,
|
||||
file: *io::stream => file,
|
||||
};
|
||||
defer io::close(file);
|
||||
|
||||
let inputs: []input = [], versions: []version = [];
|
||||
|
||||
let buf: [4096]u8 = [0...];
|
||||
let file = bufio::buffered(file, buf, []);
|
||||
for (true) {
|
||||
let line = match (bufio::scanline(file)?) {
|
||||
io::EOF => break,
|
||||
line: []u8 => match (strings::try_fromutf8(line)) {
|
||||
// Treat an invalid manifest as empty
|
||||
utf8::invalid => return manifest,
|
||||
s: str => s,
|
||||
},
|
||||
};
|
||||
defer free(line);
|
||||
|
||||
if (strings::has_prefix(line, "#")) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let tok = strings::tokenize(line, " ");
|
||||
let kind = match (strings::next_token(&tok)) {
|
||||
void => continue,
|
||||
s: str => s,
|
||||
};
|
||||
|
||||
if (kind == "version") {
|
||||
let ver = match (strings::next_token(&tok)) {
|
||||
void => return manifest,
|
||||
s: str => s,
|
||||
};
|
||||
match (strconv::stoi(ver)) {
|
||||
v: int => if (v != VERSION) {
|
||||
return manifest;
|
||||
},
|
||||
* => return manifest,
|
||||
};
|
||||
} else if (kind == "input") {
|
||||
let hash = match (strings::next_token(&tok)) {
|
||||
void => return manifest, s: str => s,
|
||||
}, path = match (strings::next_token(&tok)) {
|
||||
void => return manifest, s: str => s,
|
||||
}, inode = match (strings::next_token(&tok)) {
|
||||
void => return manifest, s: str => s,
|
||||
}, mtime = match (strings::next_token(&tok)) {
|
||||
void => return manifest, s: str => s,
|
||||
};
|
||||
|
||||
let hash = match (hex::decode(hash)) {
|
||||
* => return manifest,
|
||||
b: []u8 => b,
|
||||
};
|
||||
let inode = match (strconv::stoz(inode)) {
|
||||
* => return manifest,
|
||||
z: size => z,
|
||||
};
|
||||
let mtime = match (strconv::stoi64(mtime)) {
|
||||
* => return manifest,
|
||||
i: i64 => time::from_unix(i),
|
||||
};
|
||||
|
||||
let parsed = parse_name(path);
|
||||
let ftype = match (type_for_ext(path)) {
|
||||
void => return manifest,
|
||||
ft: filetype => ft,
|
||||
};
|
||||
|
||||
append(inputs, input {
|
||||
hash = hash,
|
||||
path = strings::dup(path),
|
||||
ft = ftype,
|
||||
stat = fs::filestat {
|
||||
mask = fs::stat_mask::MTIME | fs::stat_mask::INODE,
|
||||
mtime = mtime,
|
||||
inode = inode,
|
||||
},
|
||||
basename = strings::dup(parsed.0),
|
||||
tags = parsed.2,
|
||||
});
|
||||
} else if (kind == "module") {
|
||||
let modhash = match (strings::next_token(&tok)) {
|
||||
void => return manifest, s: str => s,
|
||||
};
|
||||
let modhash = match (hex::decode(modhash)) {
|
||||
* => return manifest,
|
||||
b: []u8 => b,
|
||||
};
|
||||
|
||||
let minputs: []input = [];
|
||||
for (true) {
|
||||
let hash = match (strings::next_token(&tok)) {
|
||||
void => break,
|
||||
s: str => s,
|
||||
};
|
||||
let hash = match (hex::decode(hash)) {
|
||||
* => return manifest,
|
||||
b: []u8 => b,
|
||||
};
|
||||
defer free(hash);
|
||||
|
||||
let input = match (getinput(inputs, hash)) {
|
||||
null => return manifest,
|
||||
i: *input => i,
|
||||
};
|
||||
append(minputs, *input);
|
||||
};
|
||||
|
||||
append(versions, version {
|
||||
hash = modhash,
|
||||
inputs = minputs,
|
||||
});
|
||||
} else {
|
||||
return manifest;
|
||||
};
|
||||
|
||||
// Check for extra tokens
|
||||
match (strings::next_token(&tok)) {
|
||||
void => void,
|
||||
s: str => return manifest,
|
||||
};
|
||||
};
|
||||
|
||||
manifest.inputs = inputs;
|
||||
manifest.versions = versions;
|
||||
return manifest;
|
||||
};
|
||||
|
||||
// Returns true if the desired module version is present and current in this
|
||||
// manifest.
|
||||
export fn current(manifest: *manifest, version: *version) bool = {
|
||||
// TODO: This is kind of dumb. What we really need to do is:
|
||||
// 1. Update scan to avoid hashing the file if a manifest is present,
|
||||
// and indicate that the hash is cached somewhere in the type. Get an
|
||||
// up-to-date stat.
|
||||
// 2. In [current], test if the inode and mtime are equal to the
|
||||
// manifest version. If so, presume the file is up-to-date. If not,
|
||||
// check the hash and update the manifest to the new inode/mtime if
|
||||
// the hash matches. If not, the module is not current; rebuild.
|
||||
let cached: nullable *version = null;
|
||||
for (let i = 0z; i < len(manifest.versions); i += 1) {
|
||||
if (bytes::equal(manifest.versions[i].hash, version.hash)) {
|
||||
cached = &manifest.versions[i];
|
||||
break;
|
||||
};
|
||||
};
|
||||
let cached = match (cached) {
|
||||
null => return false,
|
||||
v: *version => v,
|
||||
};
|
||||
|
||||
assert(len(cached.inputs) == len(version.inputs));
|
||||
for (let i = 0z; i < len(cached.inputs); i += 1) {
|
||||
let a = cached.inputs[i], b = cached.inputs[i];
|
||||
assert(a.path == b.path);
|
||||
let ast = a.stat, bst = b.stat;
|
||||
if (ast.inode != bst.inode
|
||||
|| time::compare(ast.mtime, bst.mtime) != 0) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// Writes a module manifest to the build cache.
|
||||
export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = {
|
||||
let ipath = identpath(manifest.ident);
|
||||
defer free(ipath);
|
||||
let cachedir = path::join(ctx.cache, ipath);
|
||||
defer free(cachedir);
|
||||
|
||||
let mpath = path::join(cachedir, "manifest");
|
||||
defer free(mpath);
|
||||
|
||||
let l = lock(ctx.fs, cachedir)?;
|
||||
defer unlock(ctx.fs, cachedir, l);
|
||||
|
||||
let fd = fs::create(ctx.fs, mpath, 0o644)?;
|
||||
defer io::close(fd);
|
||||
|
||||
let ident = unparse::identstr(manifest.ident);
|
||||
defer free(ident);
|
||||
fmt::fprintfln(fd, "# {}", ident)?;
|
||||
fmt::fprintln(fd, "# This file is an internal Hare implementation detail.")?;
|
||||
fmt::fprintln(fd, "# The format is not stable.")?;
|
||||
fmt::fprintfln(fd, "version {}", VERSION)?;
|
||||
for (let i = 0z; i < len(manifest.inputs); i += 1) {
|
||||
const input = manifest.inputs[i];
|
||||
let hash = hex::encodestr(input.hash);
|
||||
defer free(hash);
|
||||
|
||||
const want = fs::stat_mask::INODE | fs::stat_mask::MTIME;
|
||||
assert(input.stat.mask & want == want);
|
||||
fmt::fprintfln(fd, "input {} {} {} {}",
|
||||
hash, input.path, input.stat.inode,
|
||||
time::unix(input.stat.mtime));
|
||||
};
|
||||
|
||||
for (let i = 0z; i < len(manifest.versions); i += 1) {
|
||||
const ver = manifest.versions[i];
|
||||
let hash = hex::encodestr(ver.hash);
|
||||
defer free(hash);
|
||||
|
||||
fmt::fprintf(fd, "module {}", hash);
|
||||
|
||||
for (let j = 0z; j < len(ver.inputs); j += 1) {
|
||||
let hash = hex::encodestr(ver.inputs[i].hash);
|
||||
defer free(hash);
|
||||
|
||||
fmt::fprintf(fd, " {}", hash);
|
||||
};
|
||||
|
||||
fmt::fprintln(fd);
|
||||
};
|
||||
};
|
||||
|
||||
fn lock(fs: *fs::fs, cachedir: str) (*io::stream | error) = {
|
||||
// XXX: I wonder if this should be some generic function in fs or
|
||||
// something
|
||||
match (os::mkdirs(cachedir)) {
|
||||
errors::exists => void,
|
||||
void => void,
|
||||
e: fs::error => return e,
|
||||
};
|
||||
let lockpath = path::join(cachedir, "manifest.lock");
|
||||
defer free(lockpath);
|
||||
|
||||
let logged = false;
|
||||
for (true) {
|
||||
match (fs::create(fs, lockpath, 0o644, fs::flags::EXCL)) {
|
||||
fd: *io::stream => return fd,
|
||||
(errors::busy | errors::exists) => void,
|
||||
err: fs::error => return err,
|
||||
};
|
||||
if (!logged) {
|
||||
fmt::errorfln("Waiting for lock on {}...", lockpath);
|
||||
logged = true;
|
||||
};
|
||||
time::sleep(1 * time::SECOND);
|
||||
};
|
||||
|
||||
abort("Unreachable");
|
||||
};
|
||||
|
||||
fn unlock(fs: *fs::fs, cachedir: str, s: *io::stream) void = {
|
||||
let lockpath = path::join(cachedir, "manifest.lock");
|
||||
defer free(lockpath);
|
||||
match (fs::remove(fs, lockpath)) {
|
||||
void => void,
|
||||
err: fs::error => abort("Error removing module lock"),
|
||||
};
|
||||
};
|
||||
|
||||
fn input_finish(in: *input) void = {
|
||||
free(in.hash);
|
||||
free(in.path);
|
||||
free(in.basename);
|
||||
tags_free(in.tags);
|
||||
};
|
||||
|
||||
// Frees resources associated with this manifest.
|
||||
export fn manifest_finish(m: *manifest) void = {
|
||||
for (let i = 0z; i < len(m.inputs); i += 1) {
|
||||
input_finish(&m.inputs[i]);
|
||||
};
|
||||
|
||||
for (let i = 0z; i < len(m.versions); i += 1) {
|
||||
free(m.versions[i].inputs);
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,232 @@
|
||||
use hash;
|
||||
use io;
|
||||
use crypto::math;
|
||||
use endian;
|
||||
|
||||
// The size, in bytes, of a MD5 digest.
|
||||
export def SIZE: size = 16;
|
||||
|
||||
def chunk: size = 64;
|
||||
def init0: u32 = 0x67452301;
|
||||
def init1: u32 = 0xEFCDAB89;
|
||||
def init2: u32 = 0x98BADCFE;
|
||||
def init3: u32 = 0x10325476;
|
||||
|
||||
type digest = struct {
|
||||
hash: hash::hash,
|
||||
h: [4]u32,
|
||||
x: [chunk]u8,
|
||||
nx: size,
|
||||
ln: size,
|
||||
};
|
||||
|
||||
// Creates a [hash::hash] which computes a MD5 hash as defined in RFC 1321. Note
|
||||
// that MD5 is cryptographically broken and should not be used for secure
|
||||
// applications. Where possible, applications are encouraged to use [sha256] or
|
||||
// [sha512] instead.
|
||||
export fn md5() *hash::hash = {
|
||||
let md5 = alloc(digest {
|
||||
hash = hash::hash {
|
||||
stream = io::stream {
|
||||
writer = &write,
|
||||
closer = &close,
|
||||
...
|
||||
},
|
||||
sum = &sum,
|
||||
reset = &reset,
|
||||
sz = SIZE,
|
||||
...
|
||||
},
|
||||
});
|
||||
let hash = &md5.hash;
|
||||
hash::reset(hash);
|
||||
return hash;
|
||||
};
|
||||
|
||||
fn write(st: *io::stream, buf: const []u8) (size | io::error) = {
|
||||
let h = st: *digest;
|
||||
let b: []u8 = buf;
|
||||
let nn = len(buf);
|
||||
|
||||
h.ln += nn;
|
||||
|
||||
if (h.nx > 0) {
|
||||
// Compute how many bytes can be copied into h.x
|
||||
let r = len(h.x) - h.nx;
|
||||
let n = if (nn > r) r else nn;
|
||||
h.x[h.nx..] = b[..n];
|
||||
h.nx += n;
|
||||
if (h.nx == chunk) {
|
||||
block(h, h.x[..]);
|
||||
h.nx = 0;
|
||||
};
|
||||
b = b[n..];
|
||||
};
|
||||
if (len(b) >= chunk) {
|
||||
let n = len(b) & ~(chunk - 1);
|
||||
block(h, b[..n]);
|
||||
b = b[n..];
|
||||
};
|
||||
if (len(b) > 0) {
|
||||
let n = len(b);
|
||||
h.x[..n] = b[..];
|
||||
h.nx = n;
|
||||
};
|
||||
return nn;
|
||||
};
|
||||
|
||||
fn close(st: *io::stream) void = free(st);
|
||||
|
||||
fn reset(h: *hash::hash) void = {
|
||||
let h = h: *digest;
|
||||
h.h[0] = init0;
|
||||
h.h[1] = init1;
|
||||
h.h[2] = init2;
|
||||
h.h[3] = init3;
|
||||
h.nx = 0;
|
||||
h.ln = 0;
|
||||
};
|
||||
|
||||
fn sum(h: *hash::hash) []u8 = {
|
||||
let h = h: *digest;
|
||||
let copy = *h;
|
||||
let h = ©
|
||||
|
||||
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
|
||||
let ln = h.ln;
|
||||
let tmp: [1 + 63 + 8]u8 = [0x80, 0...];
|
||||
const pad = (55 - ln) % 64;
|
||||
endian::leputu32(tmp[1+pad..], (ln << 3) : u32);
|
||||
write(&h.hash.stream, tmp[..1+pad+8]); // append the length in bits
|
||||
|
||||
assert(h.nx == 0);
|
||||
|
||||
// Where we write the digest
|
||||
let d: [SIZE]u8 = [0...];
|
||||
endian::leputu32(d[0..], h.h[0]);
|
||||
endian::leputu32(d[4..], h.h[1]);
|
||||
endian::leputu32(d[8..], h.h[2]);
|
||||
endian::leputu32(d[12..], h.h[3]);
|
||||
|
||||
let slice: []u8 = alloc([], SIZE);
|
||||
append(slice, ...d);
|
||||
return slice;
|
||||
};
|
||||
|
||||
// A generic, pure Hare version of the MD5 block step
|
||||
fn block(h: *digest, p: []u8) void = {
|
||||
// load state
|
||||
let a = h.h[0];
|
||||
let b = h.h[1];
|
||||
let c = h.h[2];
|
||||
let d = h.h[3];
|
||||
|
||||
for (len(p) >= chunk; p = p[chunk..]) {
|
||||
|
||||
// save current state
|
||||
let aa = a, bb = b, cc = c, dd = d;
|
||||
|
||||
// load input block
|
||||
let x0 = endian::legetu32(p[4 * 0x0..]);
|
||||
let x0 = endian::legetu32(p[4 * 0x0..]);
|
||||
let x1 = endian::legetu32(p[4 * 0x1..]);
|
||||
let x2 = endian::legetu32(p[4 * 0x2..]);
|
||||
let x3 = endian::legetu32(p[4 * 0x3..]);
|
||||
let x4 = endian::legetu32(p[4 * 0x4..]);
|
||||
let x5 = endian::legetu32(p[4 * 0x5..]);
|
||||
let x6 = endian::legetu32(p[4 * 0x6..]);
|
||||
let x7 = endian::legetu32(p[4 * 0x7..]);
|
||||
let x8 = endian::legetu32(p[4 * 0x8..]);
|
||||
let x9 = endian::legetu32(p[4 * 0x9..]);
|
||||
let xa = endian::legetu32(p[4 * 0xa..]);
|
||||
let xb = endian::legetu32(p[4 * 0xb..]);
|
||||
let xc = endian::legetu32(p[4 * 0xc..]);
|
||||
let xd = endian::legetu32(p[4 * 0xd..]);
|
||||
let xe = endian::legetu32(p[4 * 0xe..]);
|
||||
let xf = endian::legetu32(p[4 * 0xf..]);
|
||||
|
||||
// round 1
|
||||
a = b + math::rotl32((((c^d)&b)^d)+a+x0+0xd76aa478, 7);
|
||||
d = a + math::rotl32((((b^c)&a)^c)+d+x1+0xe8c7b756, 12);
|
||||
c = d + math::rotl32((((a^b)&d)^b)+c+x2+0x242070db, 17);
|
||||
b = c + math::rotl32((((d^a)&c)^a)+b+x3+0xc1bdceee, 22);
|
||||
a = b + math::rotl32((((c^d)&b)^d)+a+x4+0xf57c0faf, 7);
|
||||
d = a + math::rotl32((((b^c)&a)^c)+d+x5+0x4787c62a, 12);
|
||||
c = d + math::rotl32((((a^b)&d)^b)+c+x6+0xa8304613, 17);
|
||||
b = c + math::rotl32((((d^a)&c)^a)+b+x7+0xfd469501, 22);
|
||||
a = b + math::rotl32((((c^d)&b)^d)+a+x8+0x698098d8, 7);
|
||||
d = a + math::rotl32((((b^c)&a)^c)+d+x9+0x8b44f7af, 12);
|
||||
c = d + math::rotl32((((a^b)&d)^b)+c+xa+0xffff5bb1, 17);
|
||||
b = c + math::rotl32((((d^a)&c)^a)+b+xb+0x895cd7be, 22);
|
||||
a = b + math::rotl32((((c^d)&b)^d)+a+xc+0x6b901122, 7);
|
||||
d = a + math::rotl32((((b^c)&a)^c)+d+xd+0xfd987193, 12);
|
||||
c = d + math::rotl32((((a^b)&d)^b)+c+xe+0xa679438e, 17);
|
||||
b = c + math::rotl32((((d^a)&c)^a)+b+xf+0x49b40821, 22);
|
||||
|
||||
// round 2
|
||||
a = b + math::rotl32((((b^c)&d)^c)+a+x1+0xf61e2562, 5);
|
||||
d = a + math::rotl32((((a^b)&c)^b)+d+x6+0xc040b340, 9);
|
||||
c = d + math::rotl32((((d^a)&b)^a)+c+xb+0x265e5a51, 14);
|
||||
b = c + math::rotl32((((c^d)&a)^d)+b+x0+0xe9b6c7aa, 20);
|
||||
a = b + math::rotl32((((b^c)&d)^c)+a+x5+0xd62f105d, 5);
|
||||
d = a + math::rotl32((((a^b)&c)^b)+d+xa+0x02441453, 9);
|
||||
c = d + math::rotl32((((d^a)&b)^a)+c+xf+0xd8a1e681, 14);
|
||||
b = c + math::rotl32((((c^d)&a)^d)+b+x4+0xe7d3fbc8, 20);
|
||||
a = b + math::rotl32((((b^c)&d)^c)+a+x9+0x21e1cde6, 5);
|
||||
d = a + math::rotl32((((a^b)&c)^b)+d+xe+0xc33707d6, 9);
|
||||
c = d + math::rotl32((((d^a)&b)^a)+c+x3+0xf4d50d87, 14);
|
||||
b = c + math::rotl32((((c^d)&a)^d)+b+x8+0x455a14ed, 20);
|
||||
a = b + math::rotl32((((b^c)&d)^c)+a+xd+0xa9e3e905, 5);
|
||||
d = a + math::rotl32((((a^b)&c)^b)+d+x2+0xfcefa3f8, 9);
|
||||
c = d + math::rotl32((((d^a)&b)^a)+c+x7+0x676f02d9, 14);
|
||||
b = c + math::rotl32((((c^d)&a)^d)+b+xc+0x8d2a4c8a, 20);
|
||||
|
||||
// round 3
|
||||
a = b + math::rotl32((b^c^d)+a+x5+0xfffa3942, 4);
|
||||
d = a + math::rotl32((a^b^c)+d+x8+0x8771f681, 11);
|
||||
c = d + math::rotl32((d^a^b)+c+xb+0x6d9d6122, 16);
|
||||
b = c + math::rotl32((c^d^a)+b+xe+0xfde5380c, 23);
|
||||
a = b + math::rotl32((b^c^d)+a+x1+0xa4beea44, 4);
|
||||
d = a + math::rotl32((a^b^c)+d+x4+0x4bdecfa9, 11);
|
||||
c = d + math::rotl32((d^a^b)+c+x7+0xf6bb4b60, 16);
|
||||
b = c + math::rotl32((c^d^a)+b+xa+0xbebfbc70, 23);
|
||||
a = b + math::rotl32((b^c^d)+a+xd+0x289b7ec6, 4);
|
||||
d = a + math::rotl32((a^b^c)+d+x0+0xeaa127fa, 11);
|
||||
c = d + math::rotl32((d^a^b)+c+x3+0xd4ef3085, 16);
|
||||
b = c + math::rotl32((c^d^a)+b+x6+0x04881d05, 23);
|
||||
a = b + math::rotl32((b^c^d)+a+x9+0xd9d4d039, 4);
|
||||
d = a + math::rotl32((a^b^c)+d+xc+0xe6db99e5, 11);
|
||||
c = d + math::rotl32((d^a^b)+c+xf+0x1fa27cf8, 16);
|
||||
b = c + math::rotl32((c^d^a)+b+x2+0xc4ac5665, 23);
|
||||
|
||||
// round 4
|
||||
a = b + math::rotl32((c^(b|~d))+a+x0+0xf4292244, 6);
|
||||
d = a + math::rotl32((b^(a|~c))+d+x7+0x432aff97, 10);
|
||||
c = d + math::rotl32((a^(d|~b))+c+xe+0xab9423a7, 15);
|
||||
b = c + math::rotl32((d^(c|~a))+b+x5+0xfc93a039, 21);
|
||||
a = b + math::rotl32((c^(b|~d))+a+xc+0x655b59c3, 6);
|
||||
d = a + math::rotl32((b^(a|~c))+d+x3+0x8f0ccc92, 10);
|
||||
c = d + math::rotl32((a^(d|~b))+c+xa+0xffeff47d, 15);
|
||||
b = c + math::rotl32((d^(c|~a))+b+x1+0x85845dd1, 21);
|
||||
a = b + math::rotl32((c^(b|~d))+a+x8+0x6fa87e4f, 6);
|
||||
d = a + math::rotl32((b^(a|~c))+d+xf+0xfe2ce6e0, 10);
|
||||
c = d + math::rotl32((a^(d|~b))+c+x6+0xa3014314, 15);
|
||||
b = c + math::rotl32((d^(c|~a))+b+xd+0x4e0811a1, 21);
|
||||
a = b + math::rotl32((c^(b|~d))+a+x4+0xf7537e82, 6);
|
||||
d = a + math::rotl32((b^(a|~c))+d+xb+0xbd3af235, 10);
|
||||
c = d + math::rotl32((a^(d|~b))+c+x2+0x2ad7d2bb, 15);
|
||||
b = c + math::rotl32((d^(c|~a))+b+x9+0xeb86d391, 21);
|
||||
|
||||
// add saved state
|
||||
a += aa;
|
||||
b += bb;
|
||||
c += cc;
|
||||
d += dd;
|
||||
};
|
||||
|
||||
// save state
|
||||
h.h[0] = a;
|
||||
h.h[1] = b;
|
||||
h.h[2] = c;
|
||||
h.h[3] = d;
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
export fn memcpy(dest: *void, src: *void, amt: size) void = {
|
||||
let a = dest: *[*]u8, b = src: *[*]u8;
|
||||
for (let i = 0z; i < amt; i += 1) {
|
||||
a[i] = b[i];
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
export fn memset(dest: *void, val: u8, amt: size) void = {
|
||||
let a = dest: *[*]u8;
|
||||
for (let i = 0z; i < amt; i += 1) {
|
||||
a[i] = val;
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,85 @@
|
||||
use bytes;
|
||||
use encoding::utf8;
|
||||
use strings;
|
||||
|
||||
// Returns the directory name for a given path. For a path to a file name, this
|
||||
// returns the directory in which that file resides. For a path to a directory,
|
||||
// this returns the path to its parent directory. The return value is borrowed
|
||||
// from the input, use [dup] to extend its lifetime.
|
||||
export fn dirname(path: str) str = {
|
||||
let b = strings::toutf8(path);
|
||||
let i = match (bytes::rindex(b, PATHSEP)) {
|
||||
void => return path,
|
||||
z: size => z,
|
||||
};
|
||||
if (i == 0) {
|
||||
i += 1;
|
||||
};
|
||||
return strings::fromutf8_unsafe(b[..i]);
|
||||
};
|
||||
|
||||
@test fn dirname() void = {
|
||||
assert(dirname("/foo/bar") == "/foo");
|
||||
assert(dirname("/foo") == "/");
|
||||
assert(dirname("/") == "/");
|
||||
assert(dirname("foo/bar") == "foo");
|
||||
assert(dirname("foo") == "foo");
|
||||
};
|
||||
|
||||
// Returns the final component of a given path. For a path to a file name, this
|
||||
// returns the file name. For a path to a directory, this returns the directory
|
||||
// name. The return value is borrowed from the input, use [dup] to extend its
|
||||
// lifetime.
|
||||
export fn basename(path: str) str = {
|
||||
let b = strings::toutf8(path);
|
||||
let i = match (bytes::rindex(b, PATHSEP)) {
|
||||
void => return path,
|
||||
z: size => if (z + 1 < len(b)) z + 1z else 0z,
|
||||
};
|
||||
return strings::fromutf8_unsafe(b[i..]);
|
||||
};
|
||||
|
||||
@test fn basename() void = {
|
||||
assert(basename("/foo/bar") == "bar");
|
||||
assert(basename("/foo") == "foo");
|
||||
assert(basename("/") == "/");
|
||||
assert(basename("foo/bar") == "bar");
|
||||
assert(basename("foo") == "foo");
|
||||
};
|
||||
|
||||
// Returns the file name and extension for a path. The return value is borrowed
|
||||
// from the input, see [strings::dup] to extend its lifetime.
|
||||
//
|
||||
// The extension includes the '.' character.
|
||||
//
|
||||
// extension("foo/example") => ("example", "")
|
||||
// extension("foo/example.txt") => ("example", ".txt")
|
||||
// extension("foo/example.tar.gz") => ("example", ".tar.gz")
|
||||
export fn extension(p: str) (str, str) = {
|
||||
let p = basename(p);
|
||||
let b = strings::toutf8(p);
|
||||
if (len(b) == 0 || b[len(b) - 1] == PATHSEP) {
|
||||
return (p, "");
|
||||
};
|
||||
let b = strings::toutf8(p);
|
||||
let i = match (bytes::index(b, '.': u32: u8)) {
|
||||
void => return (p, ""),
|
||||
z: size => z,
|
||||
};
|
||||
let e = b[i..];
|
||||
let n = b[..i];
|
||||
return (strings::fromutf8_unsafe(n), strings::fromutf8_unsafe(e));
|
||||
};
|
||||
|
||||
@test fn extension() void = {
|
||||
assert(extension("").0 == "");
|
||||
assert(extension("").1 == "");
|
||||
assert(extension("foo/bar").0 == "bar");
|
||||
assert(extension("foo/bar").1 == "");
|
||||
assert(extension("foo/bar.txt").0 == "bar");
|
||||
assert(extension("foo/bar.txt").1 == ".txt");
|
||||
assert(extension("foo/bar.tar.gz").0 == "bar");
|
||||
assert(extension("foo/bar.tar.gz").1 == ".tar.gz");
|
||||
assert(extension("foo.bar/baz.ha").0 == "baz");
|
||||
assert(extension("foo.bar/baz.ha").1 == ".ha");
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
// Converts a u16 from host order to network order.
|
||||
export fn htonu16(in: u16) u16 = {
|
||||
if (host == &big) return in;
|
||||
return in >> 8 | (in & 0xFF) << 8;
|
||||
};
|
||||
|
||||
// Converts a u32 from host order to network order.
|
||||
export fn htonu32(in: u32) u32 = {
|
||||
if (host == &big) return in;
|
||||
return in >> 24 | in >> 8 & 0xFF00 | in << 8 & 0xFF0000 | in << 24;
|
||||
};
|
||||
|
||||
// Converts a u64 from host order to network order.
|
||||
export fn htonu64(in: u64) u64 = {
|
||||
if (host == &big) return in;
|
||||
return (htonu32(in: u32): u64 << 32u64) | htonu32((in >> 32): u32): u64;
|
||||
};
|
||||
|
||||
@test fn hton() void = {
|
||||
if (host == &big) return;
|
||||
assert(htonu16(0xCAFE) == 0xFECA);
|
||||
assert(htonu32(0xDEADBEEF) == 0xEFBEADDE);
|
||||
assert(htonu64(0xCAFEBABEDEADBEEF) == 0xEFBEADDEBEBAFECA);
|
||||
};
|
||||
|
||||
// Converts a u16 from network order to host order.
|
||||
export fn ntohu16(in: u16) u16 = htonu16(in);
|
||||
|
||||
// Converts a u32 from network order to host order.
|
||||
export fn ntohu32(in: u32) u32 = htonu32(in);
|
||||
|
||||
// Converts a u64 from network order to host order.
|
||||
export fn ntohu64(in: u64) u64 = htonu64(in);
|
||||
|
||||
@test fn ntoh() void = {
|
||||
if (host == &big) return;
|
||||
assert(htonu16(0xFECA) == 0xCAFE);
|
||||
assert(htonu32(0xEFBEADDE) == 0xDEADBEEF);
|
||||
assert(htonu64(0xEFBEADDEBEBAFECA) == 0xCAFEBABEDEADBEEF);
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
use errors;
|
||||
use rt;
|
||||
|
||||
// Adds the argument to the niceness of the current process. The input should be
|
||||
// between -20 and 19 (inclusive); lower numbers represent a higher priority.
|
||||
// Generally, you must have elevated permissions to reduce your niceness, but
|
||||
// not to increase it.
|
||||
export fn nice(inc: int) (void | errors::opaque) = {
|
||||
let prio = inc;
|
||||
if (inc > -40 && inc <= 40) {
|
||||
prio += rt::getpriority(rt::PRIO_PROCESS, 0) as int;
|
||||
};
|
||||
if (prio > 19) {
|
||||
prio = 19;
|
||||
};
|
||||
if (prio < -20) {
|
||||
prio = -20;
|
||||
};
|
||||
return match (rt::setpriority(rt::PRIO_PROCESS, 0, prio)) {
|
||||
void => void,
|
||||
err: rt::errno => errors::errno(err),
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,96 @@
|
||||
use types;
|
||||
|
||||
// Converts any [types::signed] to a string in a given base. The return value is
|
||||
// statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn signedtosb(n: types::signed, b: base) const str = {
|
||||
return match (n) {
|
||||
i: int => itosb(i, b),
|
||||
i: i8 => i8tosb(i, b),
|
||||
i: i16 => i16tosb(i, b),
|
||||
i: i32 => i32tosb(i, b),
|
||||
i: i64 => i64tosb(i, b),
|
||||
};
|
||||
};
|
||||
|
||||
// Converts any [types::signed] to a string in base 10. The return value is
|
||||
// statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn signedtos(n: types::signed) const str = signedtosb(n, base::DEC);
|
||||
|
||||
// Converts any [types::unsigned] to a string in a given base. The return value
|
||||
// is statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn unsignedtosb(n: types::unsigned, b: base) const str = {
|
||||
return match (n) {
|
||||
u: size => ztosb(u, b),
|
||||
u: uint => utosb(u, b),
|
||||
u: u8 => u8tosb(u, b),
|
||||
u: u16 => u16tosb(u, b),
|
||||
u: u32 => u32tosb(u, b),
|
||||
u: u64 => u64tosb(u, b),
|
||||
};
|
||||
};
|
||||
|
||||
// Converts any [types::unsigned] to a string in base 10. The return value is
|
||||
// statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn unsignedtos(n: types::unsigned) const str = unsignedtosb(n, base::DEC);
|
||||
|
||||
// Converts any [types::integer] to a string in a given base, which must be 2,
|
||||
// 8, 10, or 16. The return value is statically allocated and will be
|
||||
// overwritten on subsequent calls; see [strings::dup] to duplicate the result.
|
||||
export fn integertosb(n: types::integer, b: base) const str = {
|
||||
return match (n) {
|
||||
s: types::signed => signedtosb(s, b),
|
||||
u: types::unsigned => unsignedtosb(u, b),
|
||||
};
|
||||
};
|
||||
|
||||
// Converts any [types::integer] to a string in base 10. The return value is
|
||||
// statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn integertos(n: types::integer) const str = integertosb(n, base::DEC);
|
||||
|
||||
// Converts any [types::floating] to a string in a given base. The return value
|
||||
// is statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn floatingtosb(n: types::floating, b: base) const str = {
|
||||
abort(); // TODO
|
||||
};
|
||||
|
||||
// Converts any [types::floating] to a string in base 10. The return value is
|
||||
// statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn floatingtos(n: types::floating) const str = floatingtosb(n, base::DEC);
|
||||
|
||||
// Converts any [types::numeric] to a string in a given base. The return value
|
||||
// is statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn numerictosb(n: types::numeric, b: base) const str = {
|
||||
return match (n) {
|
||||
i: types::integer => integertosb(i, b),
|
||||
f: types::floating => floatingtosb(f, b),
|
||||
};
|
||||
};
|
||||
|
||||
// Converts any [types::numeric] to a string in base 10. The return value is
|
||||
// statically allocated and will be overwritten on subsequent calls; see
|
||||
// [strings::dup] to duplicate the result.
|
||||
export fn numerictos(n: types::numeric) const str = numerictosb(n, base::DEC);
|
||||
|
||||
@test fn numeric() void = {
|
||||
const cases: [_]types::numeric = [
|
||||
42u8, 1337u16, 1337u32, 1337u64, 42i8, -42i8, 1337i16, -1337i16,
|
||||
1337i32, -1337i32, 1337i64, -1337i64, 1337i, -1337i, 1337u,
|
||||
-1337i,
|
||||
];
|
||||
const expected = [
|
||||
"42", "1337", "1337", "1337", "42", "-42", "1337", "-1337",
|
||||
"1337", "-1337", "1337", "-1337", "1337", "-1337", "1337",
|
||||
"-1337",
|
||||
];
|
||||
for (let i = 0z; i < len(cases); i += 1) {
|
||||
assert(numerictos(cases[i]) == expected[i]);
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
// An "opaque" error is used as a portable error type for an underlying error
|
||||
// which is implementation-specific. It provides a function which can be used to
|
||||
// produce a string describing the error, and a small storage area for arbitrary
|
||||
// implementation-specific storage.
|
||||
//
|
||||
// The following example shows the usage of this type for custom errors:
|
||||
//
|
||||
// fn wraperror(err: myerror) error::opaque = {
|
||||
// static assert(size(myerror) <= size(error::opaque_data));
|
||||
// let wrapped = opaque { strerror = &opaque_strerror, ... };
|
||||
// let myptr = &wrapped.data: *myerror;
|
||||
// *myptr = err;
|
||||
// };
|
||||
//
|
||||
// fn opaque_strerror(err: *opaque_data) const str = {
|
||||
// let ptr = &err: *myerr;
|
||||
// return strerror(*ptr);
|
||||
// };
|
||||
export type opaque = struct {
|
||||
strerror: *fn(op: *opaque_data) const str,
|
||||
data: opaque_data,
|
||||
}!;
|
||||
|
||||
// Up to 24 bytes of arbitrary data that the opaque error type may use for
|
||||
// domain-specific storage.
|
||||
export type opaque_data = [24]u8;
|
||||
@ -0,0 +1,27 @@
|
||||
use fs;
|
||||
use io;
|
||||
use path;
|
||||
|
||||
// Opens a file.
|
||||
//
|
||||
// If no flags are provided, [fs::flags::RDONLY], [fs::flags::NOCTTY],
|
||||
// [fs::flags::CLOEXEC] are used when opening the file. If you pass your own
|
||||
// flags, it is recommended that you add the latter two unless you know that you
|
||||
// do not want them.
|
||||
export fn open(path: str, flags: fs::flags...) (*io::stream | fs::error) =
|
||||
fs::open(cwd, path, flags...);
|
||||
|
||||
// Creates a new file and opens it for writing.
|
||||
//
|
||||
// If no flags are provided, [fs::flags::WRONLY], [fs::flags::NOCTTY],
|
||||
// [fs::flags::CLOEXEC] are used when opening the file. If you pass your own
|
||||
// flags, it is recommended that you add the latter two unless you know that you
|
||||
// do not want them.
|
||||
//
|
||||
// Only the permission bits of the mode are used. If other bits are set, they
|
||||
// are discarded.
|
||||
export fn create(
|
||||
path: str,
|
||||
mode: fs::mode,
|
||||
flags: fs::flags...
|
||||
) (*io::stream | fs::error) = fs::create(cwd, path, mode, flags...);
|
||||
@ -0,0 +1,110 @@
|
||||
use encoding::utf8;
|
||||
use io;
|
||||
use strings;
|
||||
|
||||
// Appends zero or more strings to an [io::stream]. The stream needn't be a
|
||||
// strio stream, but it's often efficient if it is. Returns the number of bytes
|
||||
// written, or an error.
|
||||
export fn concat(st: *io::stream, strs: str...) (size | io::error) = {
|
||||
let n = 0z;
|
||||
for (let i = 0z; i < len(strs); i += 1) {
|
||||
let q = 0z;
|
||||
let buf = strings::toutf8(strs[i]);
|
||||
for (q < len(buf)) {
|
||||
let w = io::write(st, buf[q..])?;
|
||||
n += w;
|
||||
q -= w;
|
||||
};
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
@test fn concat() void = {
|
||||
let st = dynamic();
|
||||
defer io::close(st);
|
||||
concat(st, "hello") as size;
|
||||
concat(st, " ", "world") as size;
|
||||
assert(string(st) == "hello world");
|
||||
};
|
||||
|
||||
// Joins several strings together by a delimiter and writes them to a stream.
|
||||
// The stream needn't be a strio stream, but it's often more efficient if it is.
|
||||
// Returns the number of bytes written, or an error.
|
||||
export fn join(st: *io::stream, delim: str, strs: str...) (size | io::error) = {
|
||||
let n = 0z;
|
||||
let delim = strings::toutf8(delim);
|
||||
for (let i = 0z; i < len(strs); i += 1) {
|
||||
let q = 0z;
|
||||
let buf = strings::toutf8(strs[i]);
|
||||
for (q < len(buf)) {
|
||||
let w = io::write(st, buf[q..])?;
|
||||
n += w;
|
||||
q -= w;
|
||||
};
|
||||
if (i + 1 < len(strs)) {
|
||||
let q = 0z;
|
||||
for (q < len(delim)) {
|
||||
let w = io::write(st, delim[q..])?;
|
||||
n += w;
|
||||
q -= w;
|
||||
};
|
||||
};
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
@test fn join() void = {
|
||||
let st = dynamic();
|
||||
defer io::close(st);
|
||||
join(st, "::", "hello", "world") as size;
|
||||
assert(string(st) == "hello::world");
|
||||
truncate(st);
|
||||
join(st, "::") as size;
|
||||
assert(string(st) == "");
|
||||
truncate(st);
|
||||
join(st, "::", "foo") as size;
|
||||
assert(string(st) == "foo");
|
||||
};
|
||||
|
||||
// Joins several strings together by a delimiter and writes them to a stream, in
|
||||
// reverse order. The stream needn't be a strio stream, but it's often more
|
||||
// efficient if it is. Returns the number of bytes written, or an error.
|
||||
export fn rjoin(st: *io::stream, delim: str, strs: str...) (size | io::error) = {
|
||||
let n = 0z;
|
||||
let delim = strings::toutf8(delim);
|
||||
for (let i = len(strs); i > 0; i -= 1) {
|
||||
let q = 0z;
|
||||
let buf = strings::toutf8(strs[i - 1]);
|
||||
for (q < len(buf)) {
|
||||
let w = io::write(st, buf[q..])?;
|
||||
n += w;
|
||||
q -= w;
|
||||
};
|
||||
if (i - 1 > 0) {
|
||||
let q = 0z;
|
||||
for (q < len(delim)) {
|
||||
let w = io::write(st, delim[q..])?;
|
||||
n += w;
|
||||
q -= w;
|
||||
};
|
||||
};
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
@test fn rjoin() void = {
|
||||
let st = dynamic();
|
||||
defer io::close(st);
|
||||
rjoin(st, "::", "hello", "world") as size;
|
||||
assert(string(st) == "world::hello");
|
||||
truncate(st);
|
||||
rjoin(st, "::") as size;
|
||||
assert(string(st) == "");
|
||||
truncate(st);
|
||||
rjoin(st, "::", "foo") as size;
|
||||
assert(string(st) == "foo");
|
||||
};
|
||||
|
||||
// Appends a rune to a stream.
|
||||
export fn appendrune(st: *io::stream, r: rune) (size | io::error) =
|
||||
io::write(st, utf8::encoderune(r));
|
||||
@ -0,0 +1,199 @@
|
||||
use ascii;
|
||||
use bufio;
|
||||
use encoding::utf8;
|
||||
use io;
|
||||
use strings;
|
||||
use strio;
|
||||
|
||||
// Returns an XML parser which reads from a stream. The caller must call
|
||||
// [parser_free] when they are finished with it.
|
||||
//
|
||||
// Hare's XML parser only supports UTF-8 encoded input files.
|
||||
//
|
||||
// This function will attempt to read the XML prologue before returning, and
|
||||
// will return an error if it is not valid.
|
||||
export fn parse(in: *io::stream) (*parser | error) = {
|
||||
// XXX: The main reason we allocate this instead of returning it on the
|
||||
// stack is so that we have a consistent address for the bufio buffer.
|
||||
// This is kind of lame, maybe we can avoid that.
|
||||
let par = alloc(parser {
|
||||
orig = in,
|
||||
in = in,
|
||||
...
|
||||
});
|
||||
if (!bufio::isbuffered(in)) {
|
||||
par.in = bufio::buffered(par.in, par.buf[..], []);
|
||||
};
|
||||
prolog(par)?;
|
||||
return par;
|
||||
};
|
||||
|
||||
// Frees the resources associated with this parser. Does not close the
|
||||
// underlying stream.
|
||||
export fn parser_free(par: *parser) void = {
|
||||
if (par.in != par.orig) {
|
||||
io::close(par.in);
|
||||
};
|
||||
free(par);
|
||||
};
|
||||
|
||||
// Scans for and returns the next [token]. The caller must pass the returned
|
||||
// token to [token_free] when they're done with it.
|
||||
export fn scan(par: *parser) (token | void | error) = {
|
||||
want(par, OPTWS)?;
|
||||
let rn: rune = match (bufio::scanrune(par.in)?) {
|
||||
io::EOF => return void,
|
||||
rn: rune => rn,
|
||||
};
|
||||
bufio::unreadrune(par.in, rn);
|
||||
return switch (par.state) {
|
||||
state::ELEMENT => switch (rn) {
|
||||
'<' => {
|
||||
let el = scan_element(par);
|
||||
par.state = state::ATTRS;
|
||||
el;
|
||||
},
|
||||
* => syntaxerr,
|
||||
},
|
||||
state::ATTRS => {
|
||||
abort(); // TODO
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
fn scan_element(par: *parser) (token | error) = {
|
||||
want(par, '<')?;
|
||||
return scan_name(par)?: elementstart;
|
||||
};
|
||||
|
||||
fn scan_name(par: *parser) (str | error) = {
|
||||
let buf = strio::dynamic();
|
||||
|
||||
const rn = match (bufio::scanrune(par.in)?) {
|
||||
io::EOF => return syntaxerr,
|
||||
rn: rune => rn,
|
||||
};
|
||||
if (!isnamestart(rn)) {
|
||||
return syntaxerr;
|
||||
};
|
||||
strio::appendrune(buf, rn);
|
||||
|
||||
for (true) match (bufio::scanrune(par.in)?) {
|
||||
io::EOF => return syntaxerr,
|
||||
rn: rune => if (isname(rn)) {
|
||||
strio::appendrune(buf, rn);
|
||||
} else {
|
||||
bufio::unreadrune(par.in, rn);
|
||||
break;
|
||||
},
|
||||
};
|
||||
|
||||
return strio::finish(buf);
|
||||
};
|
||||
|
||||
fn prolog(par: *parser) (void | error) = {
|
||||
want(par, "<?xml", WS)?;
|
||||
|
||||
want(par, "version", OPTWS, '=', OPTWS)?;
|
||||
let quot = quote(par)?;
|
||||
want(par, OPTWS, "1.")?;
|
||||
for (true) match (bufio::scanrune(par.in)?) {
|
||||
io::EOF => break,
|
||||
rn: rune => if (!ascii::isdigit(rn)) {
|
||||
bufio::unreadrune(par.in, rn);
|
||||
break;
|
||||
},
|
||||
};
|
||||
want(par, quot)?;
|
||||
|
||||
// TODO: Replace this with attribute() when it's written
|
||||
let hadws = want(par, OPTWS)?;
|
||||
let encoding = match (bufio::scanrune(par.in)) {
|
||||
io::EOF => false,
|
||||
rn: rune => {
|
||||
bufio::unreadrune(par.in, rn);
|
||||
hadws && rn == 'e';
|
||||
},
|
||||
};
|
||||
if (encoding) {
|
||||
want(par, "encoding", OPTWS, '=', OPTWS)?;
|
||||
let quot = quote(par)?;
|
||||
match (want(par, "UTF-8")) {
|
||||
syntaxerr => return utf8::invalid,
|
||||
err: error => return err,
|
||||
bool => void,
|
||||
};
|
||||
want(par, quot)?;
|
||||
};
|
||||
|
||||
let hadws = want(par, OPTWS)?;
|
||||
let standalone = match (bufio::scanrune(par.in)) {
|
||||
io::EOF => false,
|
||||
rn: rune => {
|
||||
bufio::unreadrune(par.in, rn);
|
||||
hadws && rn == 's';
|
||||
},
|
||||
};
|
||||
if (standalone) {
|
||||
want(par, "standalone", OPTWS, '=', OPTWS)?;
|
||||
let quot = quote(par)?;
|
||||
// TODO: Should we support standalone="no"?
|
||||
want(par, "yes", quot)?;
|
||||
};
|
||||
|
||||
want(par, OPTWS, "?>")?;
|
||||
// TODO: Parse doctypedecl & misc
|
||||
return;
|
||||
};
|
||||
|
||||
// Mandatory if true
|
||||
type whitespace = bool;
|
||||
def WS: whitespace = true;
|
||||
def OPTWS: whitespace = false;
|
||||
|
||||
fn quote(par: *parser) (rune | error) = {
|
||||
return match (bufio::scanrune(par.in)?) {
|
||||
* => return syntaxerr,
|
||||
rn: rune => switch (rn) {
|
||||
'"', '\'' => rn,
|
||||
* => return syntaxerr,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
fn want(par: *parser, tok: (rune | str | whitespace)...) (bool | error) = {
|
||||
let hadws = false;
|
||||
for (let i = 0z; i < len(tok); i += 1) match (tok[i]) {
|
||||
x: rune => {
|
||||
let have = match (bufio::scanrune(par.in)?) {
|
||||
* => return syntaxerr,
|
||||
rn: rune => rn,
|
||||
};
|
||||
if (have != x) {
|
||||
return syntaxerr;
|
||||
};
|
||||
},
|
||||
x: str => {
|
||||
let iter = strings::iter(x);
|
||||
for (true) match (strings::next(&iter)) {
|
||||
rn: rune => want(par, rn)?,
|
||||
void => break,
|
||||
};
|
||||
},
|
||||
ws: whitespace => {
|
||||
let n = 0;
|
||||
for (true; n += 1) match (bufio::scanrune(par.in)?) {
|
||||
io::EOF => break,
|
||||
rn: rune => if (!ascii::isspace(rn)) {
|
||||
bufio::unreadrune(par.in, rn);
|
||||
break;
|
||||
},
|
||||
};
|
||||
if (ws && n < 1) {
|
||||
return syntaxerr;
|
||||
};
|
||||
hadws = n >= 1;
|
||||
},
|
||||
};
|
||||
return hadws;
|
||||
};
|
||||
@ -0,0 +1,135 @@
|
||||
use bufio;
|
||||
use io;
|
||||
use os;
|
||||
use strconv;
|
||||
use strings;
|
||||
|
||||
// An invalid entry was encountered during parsing.
|
||||
export type invalid = void!;
|
||||
|
||||
// A Unix-like password database entry.
|
||||
export type pwent = struct {
|
||||
// Login name
|
||||
username: str,
|
||||
// Optional encrypted password
|
||||
password: str,
|
||||
// Numerical user ID
|
||||
uid: uint,
|
||||
// Numerical group ID
|
||||
gid: uint,
|
||||
// User name or comment field
|
||||
comment: str,
|
||||
// User home directory
|
||||
homedir: str,
|
||||
// Optional user command interpreter
|
||||
shell: str,
|
||||
};
|
||||
|
||||
// Reads a Unix-like password entry from a stream. The caller must free the
|
||||
// result using [unix::passwd::pwent_finish].
|
||||
export fn nextpw(stream: *io::stream) (pwent | io::EOF | io::error | invalid) = {
|
||||
let line = match (bufio::scanline(stream)?) {
|
||||
io::EOF => return io::EOF,
|
||||
ln: []u8 => ln,
|
||||
};
|
||||
let line = match (strings::try_fromutf8(line)) {
|
||||
s: str => s,
|
||||
* => return invalid,
|
||||
};
|
||||
|
||||
let fields = strings::split(line, ":");
|
||||
defer free(fields);
|
||||
|
||||
if (len(fields) != 7) {
|
||||
return invalid;
|
||||
};
|
||||
|
||||
let uid = match (strconv::stou(fields[2])) {
|
||||
u: uint => u,
|
||||
* => return invalid,
|
||||
};
|
||||
|
||||
let gid = match (strconv::stou(fields[3])) {
|
||||
u: uint => u,
|
||||
* => return invalid,
|
||||
};
|
||||
|
||||
return pwent {
|
||||
// Borrows the return value of bufio::scanline
|
||||
username = fields[0],
|
||||
password = fields[1],
|
||||
uid = uid,
|
||||
gid = gid,
|
||||
comment = fields[4],
|
||||
homedir = fields[5],
|
||||
shell = fields[6],
|
||||
};
|
||||
};
|
||||
|
||||
// Frees resources associated with [pwent].
|
||||
export fn pwent_finish(ent: pwent) void = {
|
||||
// pwent fields are sliced from one allocated string returned by
|
||||
// bufio::scanline. Freeing the first field frees the entire string in
|
||||
// one go.
|
||||
free(ent.username);
|
||||
};
|
||||
|
||||
// Looks up a user by name in a Unix-like password file. It expects a password
|
||||
// database file at /etc/passwd. Aborts if that file doesn't exist or is not
|
||||
// properly formatted.
|
||||
//
|
||||
// See [unix::passwd::nextpw] for low-level parsing API.
|
||||
export fn getuser(username: str) (pwent | void) = {
|
||||
let file = match (os::open("/etc/passwd")) {
|
||||
s: *io::stream => s,
|
||||
* => abort("Can't open /etc/passwd"),
|
||||
};
|
||||
defer io::close(file);
|
||||
|
||||
for (true) {
|
||||
let ent = match (nextpw(file)) {
|
||||
e: pwent => e,
|
||||
io::EOF => break,
|
||||
* => abort("/etc/passwd entry is invalid"),
|
||||
};
|
||||
defer pwent_finish(ent);
|
||||
|
||||
if (ent.username == username) {
|
||||
return ent;
|
||||
};
|
||||
};
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
@test fn nextpw() void = {
|
||||
let buf = bufio::fixed(strings::toutf8(
|
||||
"sircmpwn:x:1000:1000:sircmpwn's comment:/home/sircmpwn:/bin/mrsh\n"
|
||||
"alex:x:1001:1001::/home/alex:/bin/zsh"), io::mode::READ);
|
||||
defer free(buf);
|
||||
|
||||
let ent = nextpw(buf) as pwent;
|
||||
defer pwent_finish(ent);
|
||||
|
||||
assert(ent.username == "sircmpwn");
|
||||
assert(ent.password == "x");
|
||||
assert(ent.uid == 1000);
|
||||
assert(ent.gid == 1000);
|
||||
assert(ent.comment == "sircmpwn's comment");
|
||||
assert(ent.homedir == "/home/sircmpwn");
|
||||
assert(ent.shell == "/bin/mrsh");
|
||||
|
||||
let ent = nextpw(buf) as pwent;
|
||||
defer pwent_finish(ent);
|
||||
|
||||
assert(ent.username == "alex");
|
||||
assert(ent.password == "x");
|
||||
assert(ent.uid == 1001);
|
||||
assert(ent.gid == 1001);
|
||||
assert(ent.comment == "");
|
||||
assert(ent.homedir == "/home/alex");
|
||||
assert(ent.shell == "/bin/zsh");
|
||||
|
||||
// No more entries
|
||||
assert(nextpw(buf) is io::EOF);
|
||||
};
|
||||
@ -0,0 +1,197 @@
|
||||
use fmt;
|
||||
use hare::ast;
|
||||
use hare::module;
|
||||
use os::exec;
|
||||
use os;
|
||||
use path;
|
||||
use strings;
|
||||
use temp;
|
||||
|
||||
type status = enum {
|
||||
SCHEDULED,
|
||||
COMPLETE,
|
||||
SKIP,
|
||||
};
|
||||
|
||||
type task = struct {
|
||||
status: status,
|
||||
depend: []*task,
|
||||
output: str,
|
||||
cmd: []str,
|
||||
};
|
||||
|
||||
fn task_free(task: *task) void = {
|
||||
free(task.depend);
|
||||
free(task.output);
|
||||
free(task.cmd);
|
||||
free(task);
|
||||
};
|
||||
|
||||
type modcache = struct {
|
||||
hash: u32,
|
||||
task: *task,
|
||||
ident: ast::ident,
|
||||
version: module::version,
|
||||
};
|
||||
|
||||
type plan = struct {
|
||||
context: *module::context,
|
||||
workdir: str,
|
||||
counter: uint,
|
||||
scheduled: []*task,
|
||||
complete: []*task,
|
||||
script: str,
|
||||
environ: [](str, str),
|
||||
modmap: [64][]modcache,
|
||||
};
|
||||
|
||||
fn mkplan(ctx: *module::context) plan = {
|
||||
const rtdir = match (module::lookup(ctx, ["rt"])) {
|
||||
err: module::error => fmt::fatal("Error resolving rt: {}",
|
||||
module::strerror(err)),
|
||||
ver: module::version => ver.basedir,
|
||||
};
|
||||
return plan {
|
||||
context = ctx,
|
||||
workdir = temp::dir(),
|
||||
script = path::join(rtdir, "hare.sc"),
|
||||
environ = alloc([
|
||||
(strings::dup("HARECACHE"), strings::dup(ctx.cache)),
|
||||
]),
|
||||
...
|
||||
};
|
||||
};
|
||||
|
||||
fn plan_finish(plan: *plan) void = {
|
||||
os::rmdirall(plan.workdir);
|
||||
free(plan.workdir);
|
||||
|
||||
for (let i = 0z; i < len(plan.complete); i += 1) {
|
||||
let task = plan.complete[i];
|
||||
task_free(task);
|
||||
};
|
||||
free(plan.complete);
|
||||
|
||||
for (let i = 0z; i < len(plan.scheduled); i += 1) {
|
||||
let task = plan.scheduled[i];
|
||||
task_free(task);
|
||||
};
|
||||
free(plan.scheduled);
|
||||
|
||||
for (let i = 0z; i < len(plan.environ); i += 1) {
|
||||
free(plan.environ[i].0);
|
||||
free(plan.environ[i].1);
|
||||
};
|
||||
free(plan.environ);
|
||||
|
||||
for (let i = 0z; i < len(plan.modmap); i += 1) {
|
||||
free(plan.modmap[i]);
|
||||
};
|
||||
};
|
||||
|
||||
fn plan_execute(plan: *plan, verbose: bool) void = {
|
||||
for (len(plan.scheduled) != 0) {
|
||||
let next: nullable *task = null;
|
||||
let i = 0z;
|
||||
for (i < len(plan.scheduled); i += 1) {
|
||||
let task = plan.scheduled[i];
|
||||
let eligible = true;
|
||||
for (let j = 0z; j < len(task.depend); j += 1) {
|
||||
if (task.depend[j].status == status::SCHEDULED) {
|
||||
eligible = false;
|
||||
break;
|
||||
};
|
||||
};
|
||||
if (eligible) {
|
||||
next = task;
|
||||
break;
|
||||
};
|
||||
};
|
||||
// TODO: This can be a type assertion
|
||||
let task = match (next) {
|
||||
null => abort(),
|
||||
t: *task => t,
|
||||
};
|
||||
|
||||
match (execute(plan, task, verbose)) {
|
||||
err: exec::error => fmt::fatal("Error: {}: {}",
|
||||
task.cmd[0], exec::strerror(err)),
|
||||
err: exec::exit_status! => fmt::fatal("Error: {}: {}",
|
||||
task.cmd[0], exec::exitstr(err)),
|
||||
void => void,
|
||||
};
|
||||
|
||||
task.status = status::COMPLETE;
|
||||
|
||||
delete(plan.scheduled[i]);
|
||||
append(plan.complete, task);
|
||||
};
|
||||
|
||||
update_modcache(plan);
|
||||
};
|
||||
|
||||
fn update_cache(plan: *plan, mod: modcache) void = {
|
||||
let manifest = module::manifest {
|
||||
ident = mod.ident,
|
||||
inputs = mod.version.inputs,
|
||||
versions = [mod.version],
|
||||
};
|
||||
match (module::manifest_write(plan.context, &manifest)) {
|
||||
err: module::error => fmt::fatal(
|
||||
"Error updating module cache: {}",
|
||||
module::strerror(err)),
|
||||
void => void,
|
||||
};
|
||||
};
|
||||
|
||||
fn update_modcache(plan: *plan) void = {
|
||||
for (let i = 0z; i < len(plan.modmap); i += 1) {
|
||||
let mods = plan.modmap[i];
|
||||
if (len(mods) == 0) {
|
||||
continue;
|
||||
};
|
||||
for (let j = 0z; j < len(mods); j += 1) {
|
||||
if (mods[j].task.status == status::COMPLETE) {
|
||||
update_cache(plan, mods[j]);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fn execute(
|
||||
plan: *plan,
|
||||
task: *task,
|
||||
verbose: bool,
|
||||
) (void | exec::error | exec::exit_status!) = {
|
||||
if (verbose) {
|
||||
for (let i = 0z; i < len(task.cmd); i += 1) {
|
||||
fmt::errorf("{} ", task.cmd[i]);
|
||||
};
|
||||
fmt::errorln();
|
||||
};
|
||||
|
||||
let cmd = exec::cmd(task.cmd[0], task.cmd[1..]...)?;
|
||||
for (let i = 0z; i < len(plan.environ); i += 1) {
|
||||
let e = plan.environ[i];
|
||||
exec::setenv(&cmd, e.0, e.1);
|
||||
};
|
||||
|
||||
let proc = exec::start(&cmd)?;
|
||||
let st = exec::wait(&proc)?;
|
||||
return exec::check(&st);
|
||||
};
|
||||
|
||||
fn mkfile(plan: *plan, ext: str) str = {
|
||||
static let namebuf: [32]u8 = [0...];
|
||||
const name = fmt::bsprintf(namebuf, "temp.{}.{}",
|
||||
plan.counter, ext);
|
||||
plan.counter += 1;
|
||||
return path::join(plan.workdir, name);
|
||||
};
|
||||
|
||||
fn mkdepends(t: *task...) []*task = {
|
||||
// XXX: This should just be one alloc call
|
||||
let deps: []*task = alloc([], len(t));
|
||||
append(deps, ...t);
|
||||
return deps;
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
export @noreturn fn start_linux(iv: *[*]uintptr) void = {
|
||||
// TODO: Find & parse auxv
|
||||
argc = iv[0]: size;
|
||||
argv = &iv[1]: *[*]*char;
|
||||
envp = &argv[argc + 1]: *[*]nullable *char;
|
||||
start_ha();
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue