Merge pull request #369 from esawady/hare

Add Hare support
pull/381/head
Wilfred Hughes 2022-09-15 09:33:07 +07:00 committed by GitHub
commit 39bd04002c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
159 changed files with 63726 additions and 0 deletions

@ -202,6 +202,11 @@ fn main() {
src_dir: "vendor/tree-sitter-hack-src",
extra_files: vec!["scanner.cc"],
},
TreeSitterParser {
name: "tree-sitter-hare",
src_dir: "vendor/tree-sitter-hare-src",
extra_files: vec![],
},
TreeSitterParser {
name: "tree-sitter-haskell",
src_dir: "vendor/tree-sitter-haskell-src",

@ -46,6 +46,9 @@ bfb410815de1fb0fd47fa078fbd8e329 -
sample_files/hack_before.php sample_files/hack_after.php
50babcb945cf3b3ba12d5481a1bf8ccf -
sample_files/hare_before.ha sample_files/hare_after.ha
8edc2f3ae2eaeba7bcf328ed00398767 -
sample_files/haskell_before.hs sample_files/haskell_after.hs
324d1c9b2a04133a75b1975272ffb6ad -

@ -0,0 +1,14 @@
use fmt;
export fn main() void = {
const greetings = [
"Hello, world!",
"¡Hola Mundo!",
"Γειά σου Κόσμε!",
"Привет, мир!",
"こんにちは世界!",
];
for (let i = 0z; i < len(greetings); i += 1) {
fmt::println(greetings[i])!;
};
};

@ -0,0 +1,5 @@
use fmt;
export fn main() void = {
fmt::println("Hello, world!");
};

@ -34,6 +34,7 @@ pub enum Language {
Gleam,
Go,
Hack,
Hare,
Haskell,
Hcl,
Html,
@ -85,6 +86,7 @@ pub fn language_name(language: Language) -> &'static str {
Gleam => "Gleam",
Go => "Go",
Hack => "Hack",
Hare => "Hare",
Haskell => "Haskell",
Hcl => "HCL",
Html => "HTML",
@ -149,6 +151,7 @@ pub const LANG_EXTENSIONS: &'static [(Language, &[&str])] = &[
(Gleam, &["gleam"]),
(Go, &["go"]),
(Hack, &["hack", "hck", "hhi"]),
(Hare, &["ha"]),
(Haskell, &["hs"]),
(Hcl, &["hcl", "nomad", "tf", "tfvars", "workflow"]),
(Html, &["html", "htm", "xhtml"]),

@ -55,6 +55,7 @@ extern "C" {
fn tree_sitter_elvish() -> ts::Language;
fn tree_sitter_gleam() -> ts::Language;
fn tree_sitter_go() -> ts::Language;
fn tree_sitter_hare() -> ts::Language;
fn tree_sitter_hack() -> ts::Language;
fn tree_sitter_haskell() -> ts::Language;
fn tree_sitter_hcl() -> ts::Language;
@ -327,6 +328,19 @@ pub fn from_language(language: guess::Language) -> TreeSitterConfig {
.unwrap(),
}
}
Hare => {
let language = unsafe { tree_sitter_hare() };
TreeSitterConfig {
language,
atom_nodes: vec!["string_constant", "rune_constant"].into_iter().collect(),
delimiter_tokens: vec![("[", "]"), ("(", ")"), ("{", "}")],
highlight_query: ts::Query::new(
language,
include_str!("../../vendor/highlights/hare.scm"),
)
.unwrap(),
}
}
Haskell => {
let language = unsafe { tree_sitter_haskell() };
TreeSitterConfig {

@ -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,85 @@
// Implements the Adler-32 checksum algorithm. It is a non-cryptographic
// checksum.
use endian;
use hash;
use io;
use strings;
type state = struct {
hash: hash::hash,
a: u32,
b: u32,
};
// Creates a [hash::hash] which computes the Adler-32 checksum algorithm.
export fn adler32() *hash::hash = alloc(state {
hash = hash::hash {
stream = io::stream {
writer = &write,
closer = &close,
...
},
sum = &sum,
reset = &reset,
sz = 4,
},
a = 1,
b = 0,
}): *hash::hash;
fn close(s: *io::stream) void = free(s);
fn write(s: *io::stream, buf: const []u8) (size | io::error) = {
let s = s: *state;
for (let i = 0z; i < len(buf); i += 1) {
s.a = (s.a + buf[i]) % 65521;
s.b = (s.b + s.a) % 65521;
};
return len(buf);
};
fn reset(h: *hash::hash) void = {
let h = h: *state;
h.a = 1;
h.b = 0;
};
fn sum(h: *hash::hash) []u8 = {
let h = h: *state;
let buf: [4]u8 = [0...];
// RFC 1950 specifies that Adler-32 checksums are stored in network
// order.
endian::beputu32(buf, (h.b << 16) | h.a);
return alloc(buf);
};
export fn sum32(h: *hash::hash) u32 = {
assert(h.reset == &reset);
let h = h: *state;
return h.b << 16 | h.a;
};
@test fn adler32() void = {
const vectors: [](str, u32) = [
("", 1),
("hello world", 436929629),
("Hare is a cool language", 1578567727),
("'UNIX was not designed to stop its users from doing stupid things, as that would also stop them from doing clever things' - Doug Gwyn", 2140876731),
("'Life is too short to run proprietary software' - Bdale Garbee", 3135706652),
("'The central enemy of reliability is complexity.' - Geer et al", 3170309588),
("'A language that doesnt have everything is actually easier to program in than some that do.' - Dennis Ritchie", 1148528899),
];
let hash = adler32();
defer hash::close(hash);
for (let i = 0z; i < len(vectors); i += 1) {
let vec = vectors[i];
hash::reset(hash);
hash::write(hash, strings::toutf8(vec.0));
let s = hash::sum(hash);
defer free(s);
assert(endian::begetu32(s) == vec.1);
assert(sum32(hash) == vec.1);
};
};

@ -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,179 @@
// Implements the FowlerNollVo (FNV) hash function. This hash is recommended
// for hash map keys and similar applications. It is a non-cryptographic hash.
use endian;
use hash;
use io;
use strings;
def prime32: u32 = 16777619;
def prime64: u64 = 1099511628211;
def basis32: u32 = 2166136261;
def basis64: u64 = 14695981039346656037;
type state32 = struct {
hash: hash::hash,
v: u32,
};
type state64 = struct {
hash: hash::hash,
v: u64,
};
// Creates a [hash::hash] which computes the FNV-1 32-bit hash function.
//
// Unless you have a reason to use this, [fnv32a] is recommended instead.
export fn fnv32() *hash::hash = alloc(state32 {
hash = hash::hash {
stream = io::stream {
writer = &fnv32_write,
closer = &fnv_close,
},
sum = &fnv32_sum,
reset = &fnv32_reset,
sz = 4,
},
v = basis32,
}): *hash::hash;
// Creates a [hash::hash] which computes the FNV-1a 32-bit hash function.
export fn fnv32a() *hash::hash = alloc(state32 {
hash = hash::hash {
stream = io::stream {
writer = &fnv32a_write,
closer = &fnv_close,
},
sum = &fnv32_sum,
reset = &fnv32_reset,
sz = 4,
},
v = basis32,
}): *hash::hash;
// Creates a [hash::hash] which computes the FNV-1 64-bit hash function.
//
// Unless you have a reason to use this, [fnv64a] is recommended instead.
export fn fnv64() *hash::hash = alloc(state64 {
hash = hash::hash {
stream = io::stream {
writer = &fnv64_write,
closer = &fnv_close,
},
sum = &fnv64_sum,
reset = &fnv64_reset,
sz = 8,
},
v = basis64,
}): *hash::hash;
// Creates a [hash::hash] which computes the FNV-1a 64-bit hash function.
export fn fnv64a() *hash::hash = alloc(state64 {
hash = hash::hash {
stream = io::stream {
writer = &fnv64a_write,
closer = &fnv_close,
},
sum = &fnv64_sum,
reset = &fnv64_reset,
sz = 8,
},
v = basis64,
}): *hash::hash;
fn fnv_close(s: *io::stream) void = free(s);
fn fnv32_write(s: *io::stream, buf: const []u8) (size | io::error) = {
let s = s: *state32;
for (let i = 0z; i < len(buf); i += 1) {
s.v *= prime32;
s.v ^= buf[i];
};
return len(buf);
};
fn fnv32a_write(s: *io::stream, buf: const []u8) (size | io::error) = {
let s = s: *state32;
for (let i = 0z; i < len(buf); i += 1) {
s.v ^= buf[i];
s.v *= prime32;
};
return len(buf);
};
fn fnv32_reset(h: *hash::hash) void = {
let h = h: *state32;
h.v = basis32;
};
fn fnv32_sum(h: *hash::hash) []u8 = {
let h = h: *state32;
let buf: [4]u8 = [0...];
endian::host.putu32(buf, h.v);
return alloc(buf);
};
fn fnv64_write(s: *io::stream, buf: const []u8) (size | io::error) = {
let s = s: *state64;
for (let i = 0z; i < len(buf); i += 1) {
s.v *= prime64;
s.v ^= buf[i];
};
return len(buf);
};
fn fnv64a_write(s: *io::stream, buf: const []u8) (size | io::error) = {
let s = s: *state64;
for (let i = 0z; i < len(buf); i += 1) {
s.v ^= buf[i];
s.v *= prime64;
};
return len(buf);
};
fn fnv64_reset(h: *hash::hash) void = {
let h = h: *state64;
h.v = basis64;
};
fn fnv64_sum(h: *hash::hash) []u8 = {
let h = h: *state64;
let buf: [8]u8 = [0...];
endian::host.putu64(buf, h.v);
return alloc(buf);
};
// Returns the sum of a 32-bit FNV hash.
export fn sum32(h: *hash::hash) u32 = {
assert(h.reset == &fnv32_reset);
let h = h: *state32;
return h.v;
};
// Returns the sum of a 64-bit FNV hash.
export fn sum64(h: *hash::hash) u64 = {
assert(h.reset == &fnv64_reset);
let h = h: *state64;
return h.v;
};
@test fn fnv32() void = {
// TODO: Expand these tests
// I am too tired
const vectors: [_](str, u32) = [
("", 2166136261),
("hello world", 1418570095),
("Hare is a cool language", 2663852071),
("'UNIX was not designed to stop its users from doing stupid things, as that would also stop them from doing clever things' - Doug Gwyn", 1203174417),
("'Life is too short to run proprietary software' - Bdale Garbee", 493463614),
("'The central enemy of reliability is complexity.' - Geer et al", 3263526736),
("'A language that doesnt have everything is actually easier to program in than some that do.' - Dennis Ritchie", 3069348265),
];
let hash = fnv32();
defer hash::close(hash);
for (let i = 0z; i < len(vectors); i += 1) {
let vec = vectors[i];
hash::reset(hash);
hash::write(hash, strings::toutf8(vec.0));
assert(sum32(hash) == vec.1);
};
};

@ -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 = &in;
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 = &copy;
// 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