~sircmpwn/harelang.org

064396dbaab8aa7b1f7bbd7b969ca787784024cd — Amelia Clarke a month ago 736c964
tutorials: consistently wrap text

Signed-off-by: Amelia Clarke <selene@perilune.dev>
2 files changed, 93 insertions(+), 91 deletions(-)

M content/tutorials/introduction.md
M content/tutorials/stdlib.md
M content/tutorials/introduction.md => content/tutorials/introduction.md +72 -72
@@ 36,9 36,9 @@ sections:
      };
  details: |
      Start by setting up your local development environment according to the
      [installation instructions](/documentation/install/). To test that it works, copy
      this code sample into a file named `main.ha`, and run the following
      command:
      [installation instructions](/documentation/install/). To test that it
      works, copy this code sample into a file named `main.ha`, and run the
      following command:

          $ hare run main.ha
          Hello world!


@@ 210,8 210,8 @@ sections:
      A key take-away from this should be the "haredoc" tool, which you should
      use liberally as you work with Hare. Try to use it to learn about the
      other standard library functions we're using in this sample, such as
      `strings::fromutf8`. You can also use it to browse the modules
      themselves &nbsp;&mdash; try `haredoc fmt` or `haredoc strings`.
      `strings::fromutf8`. You can also use it to browse the modules themselves
      &nbsp;&mdash; try `haredoc fmt` or `haredoc strings`.

- title: Using const & let to define variables
  sample: |


@@ 243,10 243,10 @@ sections:
      };
  details: |
      This sample is designed to illustrate a few common ways to use variables
      in Hare with the `const` and `let` keywords. When declaring a
      variable, you must provide the initial value with an *initializer*, such
      as a constant value like `42` or an arbitrary expression like a function
      call. A variable declared with `const` cannot be modified after it is
      in Hare with the `const` and `let` keywords. When declaring a variable,
      you must provide the initial value with an *initializer*, such as a
      constant value like `42` or an arbitrary expression like a function call.
      A variable declared with `const` cannot be modified after it is
      initialized, but you can modify `let` variables with the `=` operator.

      However, though you cannot modify a const variable, you can *re-bind* it


@@ 303,13 303,13 @@ sections:
      or specified with an appropriate suffix.

      <p class="alert"><strong>Note</strong>:
      If you are not already familiar with binary floating point arithmetic, you
      may be surprised when arithmetic using f32 and f64 types gives unexpected
      results. Programmers unfamiliar with the subject are encouraged to read
      the <a
        href="https://en.wikipedia.org/wiki/Floating-point_arithmetic"
      >Wikipedia page on Floating-point arithmetic</a>
      to better understand the theory and trade-offs.
        If you are not already familiar with binary floating point arithmetic,
        you may be surprised when arithmetic using f32 and f64 types gives
        unexpected results. Programmers unfamiliar with the subject are
        encouraged to read the <a
          href="https://en.wikipedia.org/wiki/Floating-point_arithmetic"
        >Wikipedia page on Floating-point arithmetic</a> to better understand
        the theory and trade-offs.
      </p>

      Hare also supports a number of composite types, a few examples of which


@@ 424,10 424,10 @@ sections:
      };
  details: |
      A *slicing expression* is used to "slice" arrays and slices with the `..`
      operator. This creates a new slice which references a subset of the
      source object, such that `y[2..5]` will produce a slice whose 0th value
      is the 2nd value of "y", and whose length is 5 - 2 = 3. Slicing does not copy
      the underlying data, so modifying the items in a slice will modify the
      operator. This creates a new slice which references a subset of the source
      object, such that `y[2..5]` will produce a slice whose 0th value is the
      2nd value of "y", and whose length is 5 - 2 = 3. Slicing does not copy the
      underlying data, so modifying the items in a slice will modify the
      underlying array.

      Accesses to arrays and slices are *bounds checked*, which means that


@@ 490,11 490,11 @@ sections:
      	*ptr = *ptr + 1;
      };
  details: |
      This sample demonstrates the use of *pointers* and *stack allocation*,
      the latter of which is an important design pattern in Hare. First it
      passes a copy of the "i" variable to the "increment" function by
      *reference*, allowing the increment function to modify i from outside of
      "main" by using the `*` operator.
      This sample demonstrates the use of *pointers* and *stack allocation*, the
      latter of which is an important design pattern in Hare. First it passes a
      copy of the "i" variable to the "increment" function by *reference*,
      allowing the increment function to modify i from outside of "main" by
      using the `*` operator.

      When you call a function in Hare (or when the runtime calls "main" for
      you), a small block of memory called a *stack frame* is allocated to store


@@ 508,11 508,11 @@ sections:

      As a practical demonstration of stack allocation, this sample also
      computes and prints the SHA-256 hash of its source code. A common pattern
      in Hare is for a constructor like `sha256::sha256` to return an object
      on the stack, which you can then pass into other functions by reference
      using `&`. This is convenient for many objects which can be cleaned up by
      simply discarding their state on the stack, but other kinds of objects
      (such as file handles) require additional steps.
      in Hare is for a constructor like `sha256::sha256` to return an object on
      the stack, which you can then pass into other functions by reference using
      `&`. This is convenient for many objects which can be cleaned up by simply
      discarding their state on the stack, but other kinds of objects (such as
      file handles) require additional steps.
  # TODO: introduce nullable pointers here
- title: Dynamic memory allocation & defer
  sample: |


@@ 557,11 557,11 @@ sections:

      Unlike stack-allocated resources, which clean themselves up when the
      function exits, heap-allocated resources must be "freed" by the caller
      using the `free` keyword. Another concept shown here is the use of
      `defer` to *defer* the execution of an expression to the end of the
      current scope (that is, the code bound by `{` and `}`). This gives you the
      ability to write the code which cleans up an object right next to the code
      which creates that object.
      using the `free` keyword. Another concept shown here is the use of `defer`
      to *defer* the execution of an expression to the end of the current scope
      (that is, the code bound by `{` and `}`). This gives you the ability to
      write the code which cleans up an object right next to the code which
      creates that object.

      There are other kinds of resources that have to be cleaned up when you're
      done using them, such as open files. `defer` is useful for these cases,


@@ 629,12 629,12 @@ sections:
      local variables (*local* here meaning *local to a function*). If we
      declare them with `let`, we can modify them.

      A static local is also declared with `const` or `let`, with the
      addition of the `static` keyword. Note that, after being initially set
      to 41, the value of "x" remains consistent across repeated calls to
      "increment". Unlike a stack-allocated local, which is allocated space in
      that specific *function call*'s stack frame, static locals are allocated
      to the function itself.
      A static local is also declared with `const` or `let`, with the addition
      of the `static` keyword. Note that, after being initially set to 41, the
      value of "x" remains consistent across repeated calls to "increment".
      Unlike a stack-allocated local, which is allocated space in that specific
      *function call*'s stack frame, static locals are allocated to the function
      itself.

      A practical example of static allocation is shown in the third part of
      this program, which, like the previous example, reads up to 64 KiB from


@@ 682,8 682,8 @@ sections:
      used elsewhere.

      Many functions *borrow* resources to make use of them without taking
      responsibility for them. `strings::fromutf8` is an example of this, as
      we can learn from its documentation. The return value, "string", does not
      responsibility for them. `strings::fromutf8` is an example of this, as we
      can learn from its documentation. The return value, "string", does not
      need to be (and should not be) freed, and will become invalid when the
      buffer it's borrowed from is freed.



@@ 799,24 799,24 @@ sections:
      of the errors that can be caused during file operations.

      One way of handling these errors is with `!`, which you already know how
      to use. A more elegant way is to use `match`. A match expression takes
      an expression between its parentheses which can return one of several
      types from a tagged union, and each `case` handles one of these types.
      The first case in this example is the successful path, which we'll talk
      about more momentarily. The second case handles a specific error:
      `errors::noaccess`, and the third case handles any other errors.
      to use. A more elegant way is to use `match`. A match expression takes an
      expression between its parentheses which can return one of several types
      from a tagged union, and each `case` handles one of these types. The first
      case in this example is the successful path, which we'll talk about more
      momentarily. The second case handles a specific error: `errors::noaccess`,
      and the third case handles any other errors.

      The `case let` syntax is used to bind the value for each case to a
      variable. In the first branch, this value uses the `yield` keyword to
      yield it to the parent expression, which causes it to be "returned", in
      a manner of speaking, from the match expression. This assigns the desired
      yield it to the parent expression, which causes it to be "returned", in a
      manner of speaking, from the match expression. This assigns the desired
      `io::file` value to the file variable.

      The third case binds `fs::error` to an "err" variable, then passes it into
      `fs::strerror` to convert it to a string that can be presented to the
      user in an error message. This is a standard pattern in Hare: most modules
      will provide a "strerror" function which stringifies all of the errors
      which can be caused by that module.
      `fs::strerror` to convert it to a string that can be presented to the user
      in an error message. This is a standard pattern in Hare: most modules will
      provide a "strerror" function which stringifies all of the errors which
      can be caused by that module.
- title: Propagating errors
  sample: |
      use fmt;


@@ 939,8 939,8 @@ sections:

      We can return our custom error from "getnumber" upon encountering
      `io::EOF` (which is not ordinarily considered an error) from
      `bufio::read_line`, which is propagated through prompt to "main". If an I/O
      error were to occur here, it would be propagated similarly.
      `bufio::read_line`, which is propagated through prompt to "main". If an
      I/O error were to occur here, it would be propagated similarly.

      We can use any type as an error type if we wish. Some errors are an int or
      enum type containing an error code, or a struct with additional


@@ 997,8 997,8 @@ sections:
      a "program", such as libraries written in Hare.

      Also take note of the use of the `assert` built-in: given a condition (a
      bool), if the condition is false, the program is stopped and the
      message is printed. You can also skip the message if you don't need to be
      bool), if the condition is false, the program is stopped and the message
      is printed. You can also skip the message if you don't need to be
      specific; the file name and line number will be printed and you can
      generally figure out what went wrong regardless.
- section: Control flow


@@ 1251,8 1251,8 @@ sections:

      <div class="alert">
        <strong>Note:</strong> It is not possible to `insert`, `delete`, `free`
        and `append` to the slice being iterated over. It is encouraged to
        use normal for loops for this use case instead.
        and `append` to the slice being iterated over. It is encouraged to use
        normal for loops for this use case instead.
      </div>
- title: Flow control
  sample: |


@@ 1324,11 1324,11 @@ sections:
      [1]: https://en.wikipedia.org/wiki/Bottom_type

      This is important when you consider that Hare is an expression-oriented
      language. In the sample here, each branch of our switch expression
      yields a value (the name of the color) to serve as the result of the
      switch expression&nbsp;&mdash; except for `color::GREEN`. Because
      `abort()`'s result type is `never`, the branch doesn't yield a value at
      all, and is ignored when determining the switch expression's result type.
      language. In the sample here, each branch of our switch expression yields
      a value (the name of the color) to serve as the result of the switch
      expression&nbsp;&mdash; except for `color::GREEN`. Because `abort()`'s
      result type is `never`, the branch doesn't yield a value at all, and is
      ignored when determining the switch expression's result type.

      We have taken advantage of this behavior many times throughout the
      tutorial. For example, in [Using yield](#using-yield), `fmt::fatalf`


@@ 1964,8 1964,8 @@ sections:
      systems) at `/usr/src/hare/stdlib`, and third-party modules are installed
      at `/usr/src/hare/third-party`, both of which are configured in your
      `HAREPATH` by default. We recommend that you make liberal use of these
      resources in your work&nbsp;&mdash; don't hesitate to read the source
      code for your dependencies.
      resources in your work&nbsp;&mdash; don't hesitate to read the source code
      for your dependencies.

      These are the basics&nbsp;&mdash; enough to get started with real-world
      Hare programs. If you want to learn more, about how to organize large


@@ 1975,14 1975,14 @@ sections:
      [0]: /tutorials/modules
---

And that's the Hare programming language! Nice work getting through all of
that. We didn't cover everything here&nbsp;&mdash; check out the
And that's the Hare programming language! Nice work getting through all of that.
We didn't cover everything here&nbsp;&mdash; check out the
[Hare specification](/specification) if you want the comprehensive reference. If
you encounter any unusual code out there in the wild that you don't understand,
don't hesitate to reach out for help.

Speaking of which, this is a good time to remind you about our [community
resources](/documentation/community/):
Speaking of which, this is a good time to remind you about our
[community resources](/documentation/community/):

- The [hare-users](https://lists.sr.ht/~sircmpwn/hare-users) mailing list is a
  great place to ask questions

M content/tutorials/stdlib.md => content/tutorials/stdlib.md +21 -19
@@ 386,9 386,9 @@ of the buffer (freeing it yourself later with `free()`) without leaking memory.

If you have several sources of I/O to read from or write to, you may wish to
know which operations can be performed without blocking. Programmers familiar
with NONBLOCK usage on Unix systems can take advantage of it as described in [an
earlier section](#access-to-the-host-filesystem), but the recommended approach
on Unix uses [unix::poll], which is a wrapper around the portable
with NONBLOCK usage on Unix systems can take advantage of it as described in
[an earlier section](#access-to-the-host-filesystem), but the recommended
approach on Unix uses [unix::poll], which is a wrapper around the portable
[poll](https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html)
syscall. Be aware that this module only works with [io::file], rather than
[io::stream].


@@ 475,11 475,13 @@ fn limit_read(s: *stream, buf: []u8) (size | EOF | error) = {
library to facilitate common I/O usage scenarios. [io::limitwriter] is similar.
Additional useful utilities provided include:

* [io::tee]: a stream which copies reads and writes from an underling source to a secondary handle
* [io::tee]: a stream which copies reads and writes from an underling source to
  a secondary handle
* [io::empty]: a stream which always reads EOF and discards writes
* [io::zero]: a stream which always reads zeroes and discards writes
* [io::drain]: reads an entire I/O object into a []u8 slice
* [io::readall] & [io::writeall]: reads or writes an entire buffer, without underreads
* [io::readall] & [io::writeall]: reads or writes an entire buffer, without
  underreads

[io::tee]: https://docs.harelang.org/io#tee
[io::empty]: https://docs.harelang.org/io#empty


@@ 567,7 569,8 @@ with strings. Your attention is drawn to some of the highlights:
* [strings::index]: identify the first instance of a substring or rune
* [strings::iter]: iterate over the runes in a string
* [strings::join]: join several strings by a delimiter
* [strings::ltrim], [strings::rtrim], [strings::trim]: remove prefixes and suffixes
* [strings::ltrim], [strings::rtrim], [strings::trim]: remove prefixes and
  suffixes
* [strings::replace]: replace instances of a sub-string in a larger string
* [strings::sub]: extract a sub-string
* [strings::tokenize]: iterate over tokens in a string by some delimiter


@@ 609,19 612,18 @@ the most efficient means of building a complex string is via [memio] and [fmt].
### Working with paths

The [path](https://docs.harelang.org/path) module provides utilities for
normalizing and modifying filesystem paths. The paradigm of this module
is centered around the [path::buffer](https://docs.harelang.org/path), which
represents a normalized path. A path buffer can be converted to or from a
string at any time, but the advantage of using a buffer is that it is
mutable, and none of the functions in the path module will ever perform
heap allocation (unlike many string manipulations). Additionally, the path
buffer will ensure that the right path separators for the system are used, and
that the buffer does not exceed the system's maximum path length.

Most effective use of the path module involves creating one buffer and
passing around a pointer to that buffer, only converting it to a string when a
string is required. This prevents excessive copying and re-normalizing of the
buffer.
normalizing and modifying filesystem paths. The paradigm of this module is
centered around the [path::buffer](https://docs.harelang.org/path), which
represents a normalized path. A path buffer can be converted to or from a string
at any time, but the advantage of using a buffer is that it is mutable, and none
of the functions in the path module will ever perform heap allocation (unlike
many string manipulations). Additionally, the path buffer will ensure that the
right path separators for the system are used, and that the buffer does not
exceed the system's maximum path length.

Most effective use of the path module involves creating one buffer and passing
around a pointer to that buffer, only converting it to a string when a string is
required. This prevents excessive copying and re-normalizing of the buffer.

Below is a simple recursive filetree traversal program.