System Macros

Many of the system macros MAY be defined as template macros, and when possible, the specification includes a template. Templates are given here as normative example, but system macros are not required to be implemented as template macros.

The macros that can be defined as templates are included as system macros because of their broad applicability, and so that Ion implementations can provide optimizations for these macros that run directly in the implementations' runtime environments rather than in the macro evaluator. For example, a macro such as add_symbols does not produce user values, so an Ion Reader could bypass evaluating the template and directly update the encoding context with the new symbols.

For normative examples, see system_macros in the Ion conformance test suite.

Stream Constructors

none

(macro none () (.values))

none accepts no values and produces nothing (an empty stream).

values

(macro values (v*) v)

This is, essentially, the identity function. It produces a stream from any number of arguments, concatenating the streams produced by the nested expressions. Used to aggregate multiple values or sub-streams to pass to a single argument, or to produce multiple results.

default

(macro default (expr* default_expr*)
    // If `expr` is empty...
    (.if_none (%expr)
        // then expand `default_expr` instead.
        (%default_expr)
        // If it wasn't empty, then expand `expr`.
        (%expr)
    )
)

default tests expr to determine whether it expands to the empty stream. If it does not, default will produce the expansion of expr. If it does, default will produce the expansion of default_expr instead.

flatten

(macro flatten (sequence*) /* Not representable in TDL */)

The flatten system macro constructs a stream from the content of one or more sequences.

Produces a stream with the contents of all the sequence values. Any annotations on the sequence values are discarded. Any non-sequence arguments will raise an error. Any null arguments will be ignored.

Examples:

(:flatten [a, b, c] (d e f))       => a b c d e f
(:flatten [[], null.list] foo::()) => [] null.list

The flatten macro can also be used to splice the content of one list or s-expression into another list or s-expression.

[1, 2, (:flatten [a, b]), 3, 4] => [1, 2, a, b, 3, 4]

parse_ion

parse_ion is a special form because (unlike macros) its argument must specifically be a literal value. However, because of its usefulness for embedding an Ion stream in another Ion stream, it has an address in the system macro table.

See Special forms: parse_ion.

Value Constructors

annotate

(macro annotate (ann* value) /* Not representable in TDL */)

Produces the value prefixed with the annotations anns1. Each ann must be a non-null, unannotated string or symbol.

(:annotate (: "a2") a1::true) => a2::a1::true

make_string

(macro make_string (content*) /* Not representable in TDL */)

Produces a non-null, unannotated string containing the concatenated content produced by the arguments. Nulls (of any type) are forbidden. Any annotations on the arguments are discarded.

make_symbol

(macro make_symbol (content*) /* Not representable in TDL */)

Produces a non-null, unannotated symbol containing the concatenated content produced by the arguments. Nulls (of any type) are forbidden. Any annotations on the arguments are discarded.

make_blob

(macro make_blob (lobs*) /* Not representable in TDL */)

Produces a non-null, unannotated blob containing the concatenated content produced by the arguments. Nulls (of any type) are forbidden. Any annotations on the arguments are discarded.

make_list

(macro make_list (sequences*) [ (.flatten sequences) ])

Produces a non-null, unannotated list by concatenating the content of any number of non-null list or sexp inputs.

(:make_list)                  => []
(:make_list (1 2))            => [1, 2]
(:make_list (1 2) [3, 4])     => [1, 2, 3, 4]
(:make_list ((1 2)) [[3, 4]]) => [(1 2), [3, 4]]

make_sexp

(macro make_sexp (sequences*) ( (.flatten sequences) ))

Produces a non-null, unannotated sexp by concatenating the content of any number of non-null list or sexp inputs.

(:make_sexp)                  => ()
(:make_sexp (1 2))            => (1 2)
(:make_sexp (1 2) [3, 4])     => (1 2 3 4)
(:make_sexp ((1 2)) [[3, 4]]) => ((1 2) [3, 4])

make_struct

(macro make_struct (structs*) /* Not representable in TDL */)

Produces a non-null, unannotated struct by combining the fields of any number of non-null structs.

(:make_struct)    => {}
(:make_struct
  {k1: 1, k2: 2}
  {k3: 3}
  {k4: 4})        => {k1:1, k2:2, k3:3, k4:4}

make_field

(macro make_field (field_name value) /* Not representable in TDL */)

Produces a non-null, unannotated, single-field struct using the given field name and value.

The field_name parameter may be (or evaluate to) any non-null text value, and the value parameter may be (or evaluate to) any single value.

This can be used to dynamically construct field names based on macro parameters.

Example:

(macro foo_struct (extra_name extra_value)
       (make_struct 
         {
           foo_a: 1,
           foo_b: 2,
         }
         (make_field (make_string "foo_" (%extra_name)) (%extra_value))
       ))

Then:

(:foo_struct c 3) => { foo_a: 1, foo_b: 2, foo_c: 3 }

make_decimal

(macro make_decimal (coefficient exponent) /* Not representable in TDL */)

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.

Both coefficient and exponent must be (or evaluate to) a single integer value.

(macro usd (cents) (.annotate USD (.make_decimal cents -2))

(:usd 199) =>  USD::1.99

note

It is not possible to use make_decimal to construct any negative zero value because Ion integers do not have signed zero.

make_timestamp

(macro make_timestamp (year month? day? hour? minute? second? offset_minutes?) /* Not representable in TDL */)

Produces a non-null, unannotated timestamp at various levels of precision. When offset is absent, the result has unknown local offset; offset 0 denotes UTC.

The make_timestamp macro has rules that cannot be expressed in the macro signature because it must construct a valid Ion timestamp value.

The arguments to this macro may not be any null value. The evaluated argument for the year parameter must be an integer from 1 to 9999 inclusive. The evaluated argument for the month parameter, if present, must be an integer from 1 to 12 inclusive. The evaluated argument for the day parameter, if present, must be an integer that is a valid, 1-indexed day for the given month. The evaluated argument for the hour parameter, if present, must be an integer from 0 to 23 inclusive. The evaluated argument for the day parameter, if present, must be an integer from 0 to 59 inclusive. The evaluated argument for the second parameter, if present, must be a decimal or integer value that is greater than or equal to zero and less than 60. The evaluated arguments for all other parameters, if present, must be integer values.

The offset_minutes and hour parameters may only be present if minute is present. Aside from offset_minutes, if any evaluated argument is present, the evaluated arguments for all parameters to the left must also be present. The precision of the constructed timestamp is determined by which parameters have non-empty arguments.

Example:

(macro ts_today 
       (uint8::hour uint8::minute uint32::seconds_millis)
       (.make_timestamp
         2022
         4
         28
         hour
         minute
         (.make_decimal (%seconds_millis) -3) 0))

Encoding Utility Macros

repeat

The repeat system macro can be used for efficient run-length encoding.

(macro repeat (n! value*) /* Not representable in TDL */)

Produces a stream that repeats the specified value expression(s) n times.

The evaluated argument for n must be a non-null integer value that is equal to or greater than zero.

(:repeat 5 0)          => 0 0 0 0 0
(:repeat 2 true false) => true false true false

delta

(macro delta (deltas*) /* Not representable in TDL */)

The delta system macro can be used for directed delta encoding. It produces a stream that is equal in length to the deltas argument, defined by the recurrence relation:

output₀ = delta₀
outputₙ₊₁ = outputₙ + deltaₙ₊₁

Example:

(:delta 1000 1 2 3 -4) => 1000 1001 1003 1006 1002

sum

(macro sum (a b) /* Not representable in TDL */)

Produces the sum of two non-null integer arguments.

Examples:

(:sum 1 2) => 3

meta

(macro meta (anything*) (.none))

The meta macro accepts any values and emits nothing. It allows writers to encode data that will be not be surfaced to most readers. Readers can be configured to intercept calls to meta, allowing them to read the otherwise invisible data.

When transcribing from one format to another, writers should preserve invocations of meta when possible.

Example:

(:values
    (:meta {author: "Mike Smith", email: "mikesmith@example.com"})
    {foo:2,foo:1}
)
=>
{foo:2,foo:1}

Updating the Encoding Context

These macros are defined in terms of templates, but they are not necessarily implemented as template macros. Each of these macros produces only system values and may only be invoked where system values can occur (i.e. at the top level of a data stream). None of these macros may be invoked in TDL.

set_symbols

Redefines the default module's symbol table, preserving any macros in its macro table.

(macro set_symbols (symbols*)
       $ion::
       (module _
         (macros _)
         (symbols [(%symbols)])
       ))

The following examples are equivalent:

(:set_symbols foo bar)
$ion::
(module _
  (macros _)
  (symbols [foo, bar])
)

add_symbols

Appends symbols to the default module's symbol table, preserving any macros in its macro table.

(macro add_symbols (symbols*)
       $ion::
       (module _
         (macros _)
         (symbols _ [(%symbols)])
       ))

The following examples are equivalent:

(:add_symbols foo bar)
$ion::
(module _
  (macros _)
  (symbols _ [foo, bar])
)

set_macros

Sets the default module's macro table, preserving any symbols in its symbol table.

(macro set_macros (macros*)
       $ion::
       (module _
         (macros (%macros))
         (symbols _)
       ))

The following examples are equivalent:

(:set_macros (macro pi () 3.14159))
$ion::
(module _
  (macros (macro pi () 3.14159))
  (symbols _)
)

add_macros

Appends macros to the default module's macro table, preserving any symbols in its symbol table.

(macro add_macros (macros*)
       $ion::
       (module _
         (macros _ (%macros))
         (symbols _)
       ))

The following examples are equivalent:

(:add_macros (macro pi () 3.14159))
$ion::
(module _
  (macros _ (macro pi () 3.14159))
  (symbols _)
)

use

Appends the content of the given module to the default module.

(macro use (catalog_key version?)
       $ion::
       (module _
         (import the_module catalog_key (%version))
         (macros _ the_module)
         (symbols _ the_module)
       ))

The following examples are equivalent:

(:use "org.example.FooModule" 2)
$ion::
(module _
  (import the_module "org.example.FooModule" 2)
  (macros _ the_module)
  (symbols _ the_module)
)


  1. The annotations sequence comes first in the macro signature because it parallels how annotations are read from the data stream.^