From f20b72e06abb852ecf30b1f60d4767fbf6bd1ab3 Mon Sep 17 00:00:00 2001 From: Zack Slayton Date: Fri, 11 Oct 2024 15:42:43 -0400 Subject: [PATCH] Expands on macro definition rules, updates examples (#349) * 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 --- _books/ion-1-1/src/binary/annotations.md | 8 +-- _books/ion-1-1/src/binary/e_expressions.md | 12 ++-- _books/ion-1-1/src/binary/values.md | 4 +- _books/ion-1-1/src/macros.md | 36 ++++++++--- _books/ion-1-1/src/macros/defining_macros.md | 60 ++++++++++++++++--- _books/ion-1-1/src/macros/special_forms.md | 63 +++++++++----------- _books/ion-1-1/src/macros/system_macros.md | 29 +++++---- _books/ion-1-1/theme/highlight.js | 2 +- 8 files changed, 136 insertions(+), 78 deletions(-) diff --git a/_books/ion-1-1/src/binary/annotations.md b/_books/ion-1-1/src/binary/annotations.md index 96b2837..3f3643d 100644 --- a/_books/ion-1-1/src/binary/annotations.md +++ b/_books/ion-1-1/src/binary/annotations.md @@ -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` @@ -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: diff --git a/_books/ion-1-1/src/binary/e_expressions.md b/_books/ion-1-1/src/binary/e_expressions.md index 3af1e2f..9596b9b 100644 --- a/_books/ion-1-1/src/binary/e_expressions.md +++ b/_books/ion-1-1/src/binary/e_expressions.md @@ -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), } ) ``` diff --git a/_books/ion-1-1/src/binary/values.md b/_books/ion-1-1/src/binary/values.md index f40ebbb..2712b3e 100644 --- a/_books/ion-1-1/src/binary/values.md +++ b/_books/ion-1-1/src/binary/values.md @@ -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) \ No newline at end of file diff --git a/_books/ion-1-1/src/macros.md b/_books/ion-1-1/src/macros.md index cec8d42..af02c42 100644 --- a/_books/ion-1-1/src/macros.md +++ b/_books/ion-1-1/src/macros.md @@ -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) }) ``` diff --git a/_books/ion-1-1/src/macros/defining_macros.md b/_books/ion-1-1/src/macros/defining_macros.md index a3570fc..0cd249f 100644 --- a/_books/ion-1-1/src/macros/defining_macros.md +++ b/_books/ion-1-1/src/macros/defining_macros.md @@ -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. @@ -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 @@ -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 @@ -64,7 +95,7 @@ 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_. @@ -72,7 +103,7 @@ If an argument expression expands to a number of values that the cardinality for ##### 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. @@ -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` | diff --git a/_books/ion-1-1/src/macros/special_forms.md b/_books/ion-1-1/src/macros/special_forms.md index 3b8350e..e3a3a26 100644 --- a/_books/ion-1-1/src/macros/special_forms.md +++ b/_books/ion-1-1/src/macros/special_forms.md @@ -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] @@ -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 @@ -48,8 +38,8 @@ 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 @@ -57,7 +47,7 @@ The expanded second or third sub-expression becomes the result that is produced (: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} ``` @@ -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` @@ -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) }) ``` @@ -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)]) }) ``` @@ -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 ``` @@ -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) @@ -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) @@ -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. diff --git a/_books/ion-1-1/src/macros/system_macros.md b/_books/ion-1-1/src/macros/system_macros.md index 771bd34..be0a500 100644 --- a/_books/ion-1-1/src/macros/system_macros.md +++ b/_books/ion-1-1/src/macros/system_macros.md @@ -79,7 +79,6 @@ Embedded text example: => foo bar ``` - Embedded binary example: ```ion (:parse_ion {{ 4AEB6qNmb2+jYmFy }} ) @@ -198,7 +197,7 @@ Example: foo_a: 1, foo_b: 2, } - (make_field (make_string "foo_" extra_name) extra_value) + (make_field (make_string "foo_" (%extra_name)) (%extra_value)) )) ``` Then: @@ -216,7 +215,7 @@ This is no more compact than the regular binary encoding for decimals. However, it can be used in conjunction with other macros, for example, to represent fixed-point numbers. ```ion -(macro usd (cents) (.annotate (.literal USD) (.make_decimal cents -2)) +(macro usd (cents) (.annotate USD (.make_decimal cents -2)) (:usd 199) => USD::1.99 ``` @@ -250,7 +249,7 @@ Example: 28 hour minute - (.make_decimal seconds_millis -3) 0)) + (.make_decimal (%seconds_millis) -3) 0)) ``` ### Encoding Utility Macros @@ -325,8 +324,8 @@ Sets the local symbol table, preserving any macros in the macro table. ```ion (macro set_symbols (symbols*) $ion_encoding::( - ((.literal symbol_table) [symbols]) - ((.literal macro_table $ion_encoding)) + (symbol_table [(%symbols)]) + (macro_table $ion_encoding) )) ``` @@ -346,8 +345,8 @@ Appends symbols to the local symbol table, preserving any macros in the macro ta ```ion (macro add_symbols (symbols*) $ion_encoding::( - ((.literal symbol_table $ion_encoding) [symbols]) - ((.literal macro_table $ion_encoding)) + (symbol_table $ion_encoding [(%symbols)]) + (macro_table $ion_encoding) )) ``` @@ -367,8 +366,8 @@ Sets the local macro table, preserving any symbols in the symbol table. ```ion (macro set_macros (macros*) $ion_encoding::( - ((.literal symbol_table $ion_encoding)) - ((.literal macro_table) macros) + (symbol_table $ion_encoding) + (macro_table (%macros)) )) ``` @@ -388,8 +387,8 @@ Appends macros to the local macro table, preserving any symbols in the symbol ta ```ion (macro add_macros (macros*) $ion_encoding::( - ((.literal symbol_table $ion_encoding)) - ((.literal macro_table $ion_encoding) macros) + (symbol_table $ion_encoding) + (macro_table $ion_encoding (%macros)) )) ``` @@ -409,9 +408,9 @@ Appends the content of the given module to the encoding context. ```ion (macro use (catalog_key version?) $ion_encoding::( - ((.literal import the_module) catalog_key (.if_void version 1 version)) - ((.literal symbol_table $ion_encoding the_module)) - ((.literal macro_table $ion_encoding the_module)) + (import the_module catalog_key (.if_none (%version) 1 (%version))) + (symbol_table $ion_encoding the_module) + (macro_table $ion_encoding the_module) )) ``` diff --git a/_books/ion-1-1/theme/highlight.js b/_books/ion-1-1/theme/highlight.js index 66133de..0d09582 100644 --- a/_books/ion-1-1/theme/highlight.js +++ b/_books/ion-1-1/theme/highlight.js @@ -1242,7 +1242,7 @@ hljs.registerLanguage("ion", function () { version_marker: "$ion_1_0 $ion_1_1", literal: "true false null +inf -inf nan", keyword: 'module macro symbol_table macro_table $ion_symbol_table $ion_encoding flex_uint flex_int uint8 uint16 uint32 uint64 flex_int int8 int16 int32 int64 flex_sym', - system_macro: "literal annotate make_string void values set_macros add_macros set_symbols add_symbols use" + system_macro: "for annotate make_string none values set_macros add_macros set_symbols add_symbols use" }, i = [n.C_LINE_COMMENT_MODE, n.C_BLOCK_COMMENT_MODE], t = [n.QUOTE_STRING_MODE, n.C_NUMBER_MODE], a = {end: ",", endsWithParent: !0, excludeEnd: !0, contains: t, keywords: e}, l = { begin: "{",