# Implementing an Aura

An aura is a metadata tag for Hoon to identify and manipulate atoms correctly. Auras nest inside of each other logically: `@uvJ` nests inside of `@uv`, which in turn nests in `@u` then `@`. Auras allow programmers to encode intent into their handling of values.

To implement an aura in Hoon, we follow these steps:

1. **Design the aura.** A lot of existing letter pairs are spoken for, and new auras *should* nest logically. Currently unoccupied letters include: `abeghjkmovwxyz`. If you can make a case for why your new aura logically nests under an existing aura, then you implement such a nesting; you'll just need to make the case for it in your pull request submission.
2. **Implement the base logic in `/sys/hoon`.** This, by and large, means providing a core or door which can correctly carry out arithmetic, conversion, processing, and so forth on your atom.
3. **Implement a pretty-printer in `|co`.** This should match the atom syntax from the next step.
4. **Implement parser in `|so`.** Compose a parsing rule which is distinct from all others and add it in the appropriate sections here. (Finding a unique syntax that follows Hoon's rules, like URL-safe characters only and not parseable as Hoon code, can be rather challenging now.) The aura parser prefixes elements with a type `$term`; properly the pair is called a `$dime`. This will also allow you to type the literal atom in the Dojo.

We prefer the parsed form and the prettyprinted form to coincide so we can copy and paste a result directly back in as valid Hoon. (This is generally true for atoms but not for other molds; consider `+set` for instance.)

## Example: Sexagesimal Degrees <a href="#example-sexagesimal-degrees" id="example-sexagesimal-degrees"></a>

Classically, angular measurements using degrees subdivided each degree into 60 minutes and each minute into 60 seconds. Although less common in an age rife with [floating-point values](https://xkcd.com/2170/), proficiency with [sexagesimal notation](https://en.wikipedia.org/wiki/Degree_\(angle\)#Subdivisions) lends distinction and *gravitas*.

$$5°6'7''$$

### Preliminaries <a href="#preliminaries" id="preliminaries"></a>

You should fork the [Urbit repo](https://github.com/urbit/urbit) and create a working branch for this project. While per the guide you will not be PRing this code back against the main repo, this is the standard working environment setup.

### Design <a href="#design" id="design"></a>

In this example, we will produce a degree–minute–second (DMS) aura. We will call it `@udms`. It will have the visual form of three integers prefixed with `.` dots, e.g. `.359.59.59` for 359°59'59''. This distinguishes it from `@rs`, which has two similarly-separated values, e.g. `.1.2`; and from `@if`, which has four, e.g. `.1.2.3.4`. For atoms, the literal input form and literal output form should be the same.

It will have the bit-logical form of an integer of arbitrary size, with each whole-number increment representing a second. This would permit overflow, i.e. values greater than 360° such as 720°; however, we will supply a `+norm` arm to enforce modular arithmetic at 360°.

### Base Logic <a href="#base-logic" id="base-logic"></a>

We need to be able to perform arithmetic and type conversion with `@udms` values. Some value representations have an “unpacked“ tuple form, like dates and floating-point values. This makes it easier to shunt the values between auxiliary functions. We'll define one as well here, `$sexa` (for *sexagesimal*, base-60).

At this point, we implement modular arithmetic and wrap the values properly in `+op`. For instance, wrapping around at 360°=0° should work properly, similar to midnight. Subtraction is liable to underflow, so we need a special handler for it in `+dg`; since we have one, we may as well handle `+add` the same way for consistency.

$$359° + 2° = 1°$$

\\

$$59' + 1' = 1°$$

\\

$$59'' + 1'' = 1'$$

\\

$$1°59'59'' + 1'' = 2°$$

\\

$$3° - 1° = 2°$$

\\

$$1° - 3° = 358°$$

\\

$$0° - 1'' = 359°59'59''$$

Let's write some unit tests first.

**`/tests/sys/dms`**

```hoon
/+  *test
|%
++  test-add-zero
  ;:  weld
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 180 0 0) (wrap:dg 0 0 0))
  ==
++  test-add-dms
  ;:  weld
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 179 0 0) (wrap:dg 1 0 0))
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 90 1 0) (wrap:dg 89 59 0))
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 1 1 1) (wrap:dg 178 58 59))
  %+  expect-eq
    !>  (wrap:dg 180 0 0)
    !>  (add:dg (wrap:dg 179 0 59) (wrap:dg 0 0 1))
  ==
++  test-sub-dms
  ;:  weld
  %+  expect-eq
    !>  (wrap:dg 90 0 0)
    !>  (sub:dg (wrap:dg 180 0 0) (wrap:dg 90 0 0))
  %+  expect-eq
    !>  (wrap:dg 90 0 0)
    !>  (sub:dg (wrap:dg 0 0 0) (wrap:dg 270 0 0))
  %+  expect-eq
    !>  (wrap:dg 90 0 0)
    !>  (sub:dg (wrap:dg 90 0 0) (wrap:dg 360 0 0))
  %+  expect-eq
    !>  (wrap:dg 89 0 0)
    !>  (sub:dg (wrap:dg 90 0 0) (wrap:dg 1 0 0))
  %+  expect-eq
    !>  (wrap:dg 89 59 0)
    !>  (sub:dg (wrap:dg 90 0 0) (wrap:dg 0 1 0))
  %+  expect-eq
    !>  (wrap:dg 89 59 59)
    !>  (sub:dg (wrap:dg 90 0 0) (wrap:dg 0 0 1))
  %+  expect-eq
    !>  (wrap:dg 359 59 59)
    !>  (sub:dg (wrap:dg 0 0 0) (wrap:dg 0 0 1))
  ==
++  test-unwrap-dms
  ;:  weld
  %+  expect-eq
    !>  .90.0.0
    !>  (wrap:dg 90 0 0)
  %+  expect-eq
    !>  .0.45.0
    !>  (wrap:dg 0 45 0)
  %+  expect-eq
    !>  .0.0.45
    !>  (wrap:dg 0 0 45)
  %+  expect-eq
    !>  .1.2.3
    !>  (wrap:dg 1 2 3)
  %+  expect-eq
    !>  .360.0.0
    !>  (wrap:dg 360 0 0)
  ==
++  test-norm-dms
  ;:  weld
  %+  expect-eq
    !>  (wrap:dg 1 30 0)
    !>  (norm:dg (wrap:dg 0 90 0))
  %+  expect-eq
    !>  (wrap:dg 1 0 0)
    !>  (norm:dg (wrap:dg 0 60 0))
  %+  expect-eq
    !>  (wrap:dg 0 1 30)
    !>  (norm:dg (wrap:dg 0 0 90))
  %+  expect-eq
    !>  (wrap:dg 0 0 0)
    !>  (norm:dg (wrap:dg 360 0 0))
  %+  expect-eq
    !>  (wrap:dg 0 0 0)
    !>  (norm:dg (wrap:dg 359 60 0))
  %+  expect-eq
    !>  (wrap:dg 0 0 0)
    !>  (norm:dg (wrap:dg 359 59 60))
  %+  expect-eq
    !>  .1.0.0
    !>  .0.60.0
  ==
++  test-parse-dms
  ;:  weld
  %+  expect-eq
    !>  [p=[p=1 q=6] q=[~ u=[p=3.723 q=[p=[p=1 q=6] q=""]]]]
    !>  (dems:so [[1 1] "1.2.3"])
  %+  expect-eq
    !>  3.723
    !>  (scan "1.2.3" dems:so)
  %-  expect-fail
    |.  (scan ".5.6" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
  %-  expect-fail
    |.  (scan ".5.6.7.8" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
  ==
--
```

The Hoon logic will be located in a `+dg` arm. This needs to be sufficiently high in the stack that our prettyprinter and parser logic know about them, so let's put `+dg` in the fourth core. Search for `layer-5` and paste `+dg` in a few lines above that after `$hump`. (Strictly speaking, we should make sure that this works in a userspace `/lib` library first but here we'll just insert it into `/sys/hoon` and rely on our unit tests.)

**`/sys/hoon`**

```hoon
++  dg
  |%
  +$  sexa  $:(d=@ud m=@ud s=@ud)
  :: Convert to $sexa expanded form.
  ++  unwrap
    |=  p=@udms
    ^-  sexa
    =/  d  (div p 3.600)
    =/  s  (sub p (mul d 3.600))
    =/  m  (div s 60)
    =.  s  (sub s (mul m 60))
    [d m s]
  :: Convert to atomic @udms form.
  ++  wrap
    |=  =sexa
    ^-  @udms
    =,  sexa
    :(add (mul d 3.600) (mul m 60) s)
  :: Handle arithmetic.
  ++  add  |=([p=@udms q=@udms] ^-(@udms (^add `@`p `@`q)))
  ++  sub  |=([p=@udms q=@udms] ^-(@udms (~(dif fo 1.296.000) `@`p `@`q)))
  :: Convert to @rs.
  ++  to-rs  |=(p=@udms ^-(@rs (div:rs (sun:rs p) .3600)))
  :: Convert to @rd.
  ++  to-rd  |=(p=@udms ^-(@rd (div:rd (sun:rd p) .~3600)))
  :: Roll over values out of range of 360°.
  ++  norm
    |=  p=@udms
    ^-  @udms
    (~(fra fo 1.296.000) `@`p 1.296.000)
  --
```

### Pretty-Printing <a href="#pretty-printing" id="pretty-printing"></a>

The atom literal should be constructed in `+rend`, which has a cascade of switch statements over the atom aura. Let's adopt the output syntax to be the same as the input syntax, `.ddd.mm.ss`.

```hoon
++  co
  ~%  %co  ..co  ~
  |_
    ...
    ++  rend
      =+  [yed=(end 3 p.p.lot) hay=(cut 3 [1 1] p.p.lot)]
      |-  ^-  tape
      ?+    yed  (z-co q.p.lot)
        ...
            %u
          ?:  ?=(%c hay)
            %+  welp  ['0' 'c' (reap (pad:fa q.p.lot) '1')]
            (c-co (enc:fa q.p.lot))
          ::
          =;  gam=(pair tape tape)
            (weld p.gam ?:(=(0 q.p.lot) `tape`['0' ~] q.gam))
          ?+  hay  [~ ((ox-co [10 3] |=(a=@ ~(d ne a))) q.p.lot)]
          ...
              %d
            =/  ham  (cut 3 [2 1] p.p.lot)
            ?+    ham  [~ ((ox-co [10 3] |=(a=@ ~(d ne a))) q.p.lot)]
                %m
              =/  has  (cut 3 [3 1] p.p.lot)
              ?+    has  [~ ((ox-co [10 3] |=(a=@ ~(d ne a))) q.p.lot)]
                  %s
                =/  =sexa:dg  (unwrap:dg q.p.lot)
                :-  ['.' ~]
                `tape`:(weld (a-co d.sexa) "." (a-co m.sexa) "." (a-co s.sexa))
              ==
            ==
          ==
```

Every type has its own special conventions, so you need to adapt to follow whatever those may be.

### Parsing <a href="#parsing" id="parsing"></a>

A parsing rule which correctly handles the aura is:

```hoon
;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag))
```

as demonstrated by these tests. (See also `+dem:ag`.)

```hoon
> (;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)) [[1 1] ".1.2.3"])
[p=[p=1 q=7] q=[~ u=[p=[1 2 3] q=[p=[p=1 q=7] q=""]]]]

> (scan ".5.6.7" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
[5 6 7]

> (scan ".5.6" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
{1 5}
syntax error
dojo: hoon expression failed

> (scan ".5.6.7.8" ;~(pfix dot ;~((glue dot) dem:ag dem:ag dem:ag)))
{1 9}
syntax error
dojo: hoon expression failed
```

Ultimately we will pack these together into the atom form `@udms`.

```hoon
> (scan "1.2.3" (cook |=([d=@ m=@ s=@] :(add (mul d 3.600) (mul m 60) s)) ;~((glue dot) dem:ag dem:ag dem:ag)))
3.723
```

Note that this form overflows still, but can be corrected using `+norm:dg`.

Our parser logic needs to be cited in `+zust:so` because that arm parses atoms prefixed with `.` dot. This means that the `pfix dot` gets eaten, and our actual parsing rule only needs to handle the rest of the symbol. Add a `+dems` arm after `+zust` and register it in the `+pose` in `+zust` as well. Since parsers in a `+pose` resolve in order, it should come after IPv4 `@if` addresses (`.1.1.1.1`) and before `@rs` values (`.1.1`).

```hoon
++  so
  ~%  %so  +  ~
  |%
  ...
  ++  zust
    ~+
    ;~  pose
      (stag %is bip:ag)
      (stag %if lip:ag)
      (stag %udms dems)
      royl
      ...
    ==
  ++  dems
    |=  b=nail
    %.  b
    %+  cook
      |=([d=@ m=@ s=@] :(add (mul d 3.600) (mul m 60) s))
    ;~((glue dot) dem:ag dem:ag dem:ag)
  --
```

You also need to modify `$iota` to include `[%udms @udms]` to satisfy the type system for typed paths.

At this point, a compiled `/sys/hoon` should detect the typed atom syntax correctly. This aura should pass all tests and be usable in conventional Hoon code.

```hoon
> .1.2.3
.1.2.3

> `@ud`.1.2.3
3.723
```

## Exercises <a href="#exercises" id="exercises"></a>

What else should be implemented?

* Radian-based angles could be supported (let's say as `@ur`).
* Arguably, there should be an absolute angle (analogous to `@da`) representing a direction in 2D space, and a relative angle (analogous to `@dr`) representing a turn in 2D space.
* You'd also want to decide how to deal with negative arcs, if supported; a zigzag scheme similar to `@s` would be most apt (`@sdms`).
* A spherical trigonometry project could build on these mathematics for an astronomical or avionic calculator. (`@uds` for steradians?)
* `*@udms` displays incorrectly. Why, and how would you fix it?


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.urbit.org/hoon/aura-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
