Skip to content

Commit

Permalink
Expands on macro definition rules, updates examples (#349)
Browse files Browse the repository at this point in the history
* Updates all examples to use `(%param)` syntax for variable expansion
* Removes the `.literal` special form
* Adds more detail to the macro definition section
* Fixes several broken mdbook links
  • Loading branch information
zslayton authored Oct 11, 2024
1 parent fd7b212 commit f20b72e
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 78 deletions.
8 changes: 4 additions & 4 deletions _books/ion-1-1/src/binary/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ It is illegal for an annotations sequence to appear before any of the following:
### Annotations With Symbol Addresses
Opcodes `0xE4` through `0xE6` indicate one or more annotations encoded as symbol addresses. If the opcode is:

* `0xE4`, a single [`FlexUInt`](#flexuint)-encoded symbol address follows.
* `0xE5`, two [`FlexUInt`](#flexuint)-encoded symbol addresses follow.
* `0xE6`, a [`FlexUInt`](#flexuint) follows that represents the number of bytes needed to encode
* `0xE4`, a single [`FlexUInt`](primitives/flex_uint.md#flexuint)-encoded symbol address follows.
* `0xE5`, two [`FlexUInt`](primitives/flex_uint.md#flexuint)-encoded symbol addresses follow.
* `0xE6`, a [`FlexUInt`](primitives/flex_uint.md#flexuint) follows that represents the number of bytes needed to encode
the annotations sequence, which can be made up of any number of `FlexUInt` symbol addresses.

##### Encoding of `$10::false`
Expand Down Expand Up @@ -51,7 +51,7 @@ E5 07 15 17 19 6F

### Annotations With `FlexSym` Text

Opcodes `0xE7` through `0xE9` indicate one or more annotations encoded as [`FlexSym`](#flexsym)s.
Opcodes `0xE7` through `0xE9` indicate one or more annotations encoded as [`FlexSym`](primitives/flex_sym#flexsym)s.

If the opcode is:

Expand Down
12 changes: 6 additions & 6 deletions _books/ion-1-1/src/binary/e_expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,20 +317,20 @@ consider the following two macro definitions.

The `point2D` macro takes two `flex_int`-encoded values as arguments.
```ion
(macro point2D (flex_int::$x flex_int::$y)
(macro point2D (flex_int::x flex_int::y)
{
x: $x,
y: $y,
x: (%x),
y: (%y),
}
)
```

The `line` macro takes a pair of `point2D` invocations as arguments.
```ion
(macro line (point2D::$start point2D::$end)
(macro line (point2D::start point2D::end)
{
start: $start,
end: $end,
start: (%start),
end: (%end),
}
)
```
Expand Down
4 changes: 2 additions & 2 deletions _books/ion-1-1/src/binary/values.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
* [timestamp](values/timestamp.md)
* [string](values/string.md)
* [symbol](values/symbol.md)
* [blob](values/blob.md)
* [clob](values/clob.md)
* [blob](values/lob.md#blobs)
* [clob](values/lob.md#clobs)
* [list](values/list.md)
* [sexp](values/sexp.md)
* [struct](values/struct.md)
36 changes: 29 additions & 7 deletions _books/ion-1-1/src/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,45 @@ This means that Ion readers and writers often spend substantial resources proces
Consider this example excerpt from a webserver's log file:

```ion
{method: GET, statusCode: 200, status: "OK", protocol: https, clientIp: ip_addr::"192.168.1.100", resource: "index.html"}
{method: GET, statusCode: 200, status: "OK", protocol: https, clientIp: ip_addr::"192.168.1.100", resource: "images/funny.jpg"}
{method: GET, statusCode: 200, status: "OK", protocol: https, clientIp: ip_addr::"192.168.1.101", resource: "index.html"}
{
method: GET,
statusCode: 200,
status: "OK",
protocol: https,
clientIp: ip_addr::"192.168.1.100",
resource: "index.html"
}
{
method: GET,
statusCode: 200,
status: "OK",
protocol: https,
clientIp:
ip_addr::"192.168.1.100",
resource: "images/funny.jpg"
}
{
method: GET,
statusCode: 200,
status: "OK",
protocol: https,
clientIp: ip_addr::"192.168.1.101",
resource: "index.html"
}
```

_Macros_ allow users to define fill-in-the-blank templates for their data. This enables applications to focus on encoding and decoding the parts of the data that are distinctive, eliding the work needed to encode the boilerplate.

Using this macro definition:
```ion
(macro getOk ($clientIp $resource)
(macro getOk (clientIp resource)
{
method: GET,
statusCode: 200,
status: "OK",
protocol: (literal https),
clientIp: (annotate "ip_addr" $clientIp),
resource: "index.html"
protocol: https,
clientIp: (.annotate "ip_addr" (%clientIp)),
resource: (%resource)
})
```

Expand Down
60 changes: 52 additions & 8 deletions _books/ion-1-1/src/macros/defining_macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ A macro is defined using a `macro` clause within a [module](../modules.md)'s [`m
| [`signature`](#macro-signatures) | An s-expression enumerating the parameters this macro accepts. |
| [`template`](#template-definition-language-tdl) | A template definition language (TDL) expression that can be evaluated to produce zero or more Ion values. |

#### Example macro clause
```ion
// ┌─── name
// │ ┌─── signature
// ┌┴┐ ┌──┴──┐
(macro foo (x y z)
{ // ─┐
x: (%x), // │
y: (%y), // ├─ template
z: (%z), // │
} // ─┘
)
```

### Macro names

Syntactically, macro names are [identifiers](../modules.md#identifiers). Each macro name in a macro table must be unique.
Expand All @@ -21,12 +35,22 @@ In some circumstances, it may not make sense to name a macro. (For example, when

### Macro Parameters

Macros accept zero or more parameters.
A _parameter_ is a named stream of Ion values. The stream's contents are determined by the macro's invocation.
A macro's parameters are declared in the [macro signature](#macro-signatures).

Each parameter is comprised of three elements:
1. A name
2. An encoding
3. A cardinality
Each parameter declaration is comprised of three elements:
1. A [name](#parameter-names)
2. An optional [encoding](#parameter-encodings)
3. An optional [cardinality](#parameter-cardinalities)

#### Example parameter declaration
```ion
// ┌─── encoding
// │ ┌─── name
// │ │┌─── cardinality
// ┌───┴───┐ ││
flex_uint::x*
```

#### Parameter names

Expand All @@ -51,6 +75,13 @@ To specify an encoding, the [parameter name](#parameter-names) is annotated with
| `float16` `float32` `float64` | Fixed-width float |
| `flex_symbol` | [`FlexSym`](../binary/primitives/flex_sym.md)-encoded SID or text |

When writing text Ion, the declared encoding does not affect how values are serialized.
However, it does constrain the domain of values that that parameter will accept.
When transcribing from text to binary, it must be possible to serialize all values passed as an argument using the parameter's declared encoding.
This means that parameters with a primitive encoding cannot be annotated or a `null` of any type.
If an `int` or a `float` is being passed to a parameter with a fixed-width encoding,
that value must fit within the range of values that can be represented by that width.
For example, the value `256` cannot be passed to a parameter with an encoding of `uint8` because a `uint8` can only represent values in the range `[0, 255]`.

#### Parameter cardinalities

Expand All @@ -64,15 +95,15 @@ A parameter name may optionally be followed by a _cardinality modifier_. This is
| `+` | one-or-more values |

If no modifier is specified, the parameter's cardinality will default to exactly-one.
An `exactly-one` parameter can only accept a single expression as its argument.
An `exactly-one` parameter will always expand to a stream containing a single value.

Parameters with a cardinality other than `exactly-one` are called _variadic parameters_.

If an argument expression expands to a number of values that the cardinality forbids, the reader must raise an error.

##### Optional parameters

Parameters with a cardinality that can accept an empty group as an argument (`?` and `*`) are called
Parameters with a cardinality that can accept an empty expression group as an argument (`?` and `*`) are called
_optional parameters_. In text Ion, their corresponding arguments can be elided from e-expressions and TDL macro
invocations when they appear in tail position. When an argument is elided, it is treated as though an explicit
empty group `(::)` had been passed in its place.
Expand Down Expand Up @@ -119,7 +150,20 @@ cause them to appear in a position corresponding to a different argument.

### Macro signatures

A macro signature is an s-expression containing a series of parameter definitions.
A macro's _signature_ is the ordered sequence of parameters which an invocation of that macro must define.
Syntactically, the signature is an s-expression of [parameter declarations](#macro-parameters).

#### Example macro signature
```ion
(w flex_uint::x* float16::y? z+)
```

| Name | Encoding | Cardinality |
|:----:|:-----------:|:--------------:|
| `w` | `tagged` | `exactly-one` |
| `x` | `flex_uint` | `zero-or-more` |
| `y` | `float16` | `zero-or-one` |
| `z` | `tagged` | `one-or-more` |

<!-- TODO; grammar and examples -->

Expand Down
63 changes: 28 additions & 35 deletions _books/ion-1-1/src/macros/special_forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Special forms are "special" precisely because they cannot be expressed as macros
Since the elements of macro-invocation expressions are themselves expressions, when you want something to not be evaluated that way, it must be a special form.

Finally, these special forms are part of the template language itself, and are not addressable outside of TDL;
the E-expression `(:literal foo)` must necessarily refer to some user-defined macro named literal, not to this special form.
the E-expression `(:if_none foo bar baz)` must necessarily refer to some user-defined macro named `if_none`, not to the special form of the same name.


> [!TODO]
Expand All @@ -20,16 +20,6 @@ the E-expression `(:literal foo)` must necessarily refer to some user-defined ma
> Candidates to be moved to system macros are `if_*` and `fail`.
> Additionally, the system macro `parse_ion` may need to be classified as a special form since it only accepts literals.
### `literal`

```ion
(macro USD_price (dollars) (.price dollars (.literal USD)))
```
In this template, we cannot write `(.price dollars USD)` because the symbol `USD` would be treated as an unbound variable reference and a syntax error, so we turn it into literal data by "escaping" it with `literal`.

> [!TIP]
> As an aside, there is no need for such a form in E-expressions, because in that context symbols and S-expressions are not "evaluated", and everything is literal except for E-expressions (which are not data, but encoding artifacts).
### `if_none`

```ion
Expand All @@ -48,16 +38,16 @@ The expanded second or third sub-expression becomes the result that is produced
```ion
(macro temperature (degrees scale)
{
degrees: degrees,
scale: (.if_none scale (.literal K) scale),
degrees: (%degrees),
scale: (.if_none (%scale) K (%scale)),
})
```
```ion
(:temperature 96 F) ⇒ {degrees:96, scale:F}
(:temperature 283 (::)) ⇒ {degrees:283, scale:K}
```

To refine things a bit further, trailing voidable arguments can be omitted entirely:
To refine things a bit further, trailing optional arguments can be omitted entirely:
```ion
(:temperature 283) ⇒ {degrees:283, scale:K}
```
Expand All @@ -66,7 +56,7 @@ To refine things a bit further, trailing voidable arguments can be omitted entir
> You can define a macro that wraps `if_none` to create a none-coalescing operator.
> ```ion
> (macro coalesce (maybe_none* default_expr+)
> (.if_none maybe_none default_expr maybe_none))
> (.if_none (%maybe_none) (%default_expr) (%maybe_none)))
> ```
### `if_some`
Expand All @@ -81,9 +71,9 @@ The `stream` expression must be expanded enough to determine whether it produces

Example:
```ion
(macro foo ($foo)
(macro foo (x)
{
foo: (.if_some $foo [$foo] null)
foo: (.if_some (%x) [(%x)] null)
})
```

Expand All @@ -93,13 +83,13 @@ Example:
(:foo (:: 2 3)) => { foo: [2, 3] }
```

The `false_branch` parameter may be elided, allowing `if_some` to serve as a _map-if-not-void_ function.
The `false_branch` parameter may be elided, allowing `if_some` to serve as a _map-if-not-none_ function.

Example:
```ion
(macro foo ($foo)
(macro foo (x)
{
foo: (.if_some $foo [$foo])
foo: (.if_some (%x) [(%x)])
})
```

Expand Down Expand Up @@ -143,9 +133,9 @@ For example:

```ion
(.for
[($word // Variable name
(.literal foo bar baz))] // Values over which to iterate
(.values $word $word)) // Template expression; `$word` will be replaced
[(word // Variable name
foo bar baz)] // Values over which to iterate
(.values (%word) (%word))) // Template expression; `(%word)` will be replaced
=>
foo foo bar bar baz baz
```
Expand All @@ -154,9 +144,9 @@ Multiple s-expressions can be specified. The streams will be iterated over in lo

```ion
(.for
(($x 1 2 3) // for $x in...
($y 4 5 6)) // for $y in...
($x $y)) // Template; `$x` and `$y` will be replaced
((x 1 2 3) // for x in...
(y 4 5 6)) // for y in...
((%x) (%y))) // Template; `(%x)` and `(%y)` will be replaced
=>
(1 4)
(2 5)
Expand All @@ -165,9 +155,9 @@ Multiple s-expressions can be specified. The streams will be iterated over in lo
Iteration will end when the shortest stream is exhausted.
```ion
(.for
[($x 1 2), // for $x in...
($y 3 4 5)] // for $y in...
($x $y)) // Template; `$x` and `$y` will be replaced
[(x 1 2), // for x in...
(y 3 4 5)] // for y in...
((%x) (%y))) // Template; `(%x)` and `(%y)` will be replaced
=>
(1 3)
(2 4)
Expand All @@ -177,16 +167,19 @@ Iteration will end when the shortest stream is exhausted.
Names defined inside a `for` shadow names in the parent scope.

```ion
(macro triple ($x)
(macro triple (x)
// └─── Parameter `x` is declared here...
(.for
(($x // Shadows macro argument `$x`
1 2 3))
$x
// ...but the `for` expression introduces a
// ┌─── new variable of the same name here.
((x a b c))
(%x)
// └─── This refers to the `for` expression's `x`, not the parameter.
)
)
(:triple 1)
(:triple 1) // Argument `1` is ignored
=>
1 1 1
a b c
```

The `for` special form can only be invoked in the body of template macro. It is not valid to use as an E-Expression.
Expand Down
Loading

0 comments on commit f20b72e

Please sign in to comment.