8.12

3.1 Syntax Objects🔗ℹ

The '' form produces a syntax object. The syntax object holds an unparsed shrubbery, not a parsed Rhombus expression.

> '1'

'1'

> 'hello'

'hello'

> '1 + 2'

'1 + 2'

> 'x:

     y'

'x:« y »'

> '1' + 2

+: contract violation

  expected: Number

  given: ’1’

Within the '', the $ operator unquotes the immediately following term. That is, the term after $ is a Rhombus expression whose value replaces the $ and its argument within the quoted form.

> '1 + $(2 + 3)'

'1 + 5'

A $ only unquotes when it is followed by a term, otherwise the $ itself remains quoted.

> '1 + $('$') 2'

'1 + $ 2'

Nesting quotes do not require a corresponding nesting of escaping $ to escape outside the original quotes. For example, '('$(1+2)')' is the same as '('3')' in Rhombus, even though the escape is inside two layers of quotes.

Like $, ... is treated specially within a ''-quoted term (except, like $, when it’s the only thing in the term). When ... immediately follows a term that includes at least one $, the form after that $ must refer to a repetition. Then, instead of the parenthesized group in place of $, the term before ... is replicated as many times as the repetition has items, and each of those items is used in one replication.

def [seq, ...] = ['1', '2', '3']

> '$seq ...'

'1 2 3'

> '(hi $seq) ...'

'(hi 1) (hi 2) (hi 3)'

When ... is the only term in a group, and when that group follows another, then ... replicates the preceding group. For example, putting ... after a , in parentheses means that it follows a the group before the ,, which effectively replicates that group with its separating comma:

> def [seq, ...] = ['1', '2', '3']

> '(hi $seq, ...)'

'(hi 1, hi 2, hi 3)'

Along the same lines, ... just after a | can replicate a preceding | block:

> def [seq, ...] = ['1', '2', '3']

> 'cond | $seq | ...'

'cond |« 1 » |« 2 » |« 3 »'

In other words, ... in various places within a quoted shrubbery works the way you’d expect it to work.

When '' is used in a binding position, it constructs a pattern that matches syntax objects, and it binds variables that are escaped in the pattern with $.

> def '$x + $y' = '1 + (2 + 3)'

> x

'1'

> y

'(2 + 3)'

A ... works the way you would expect in a syntax pattern, matching any ...-replicated pattern variables to form a repetition of matches:

def '$x + $y ... + 0' = '1 + 2 + 3 + 0'

> x

'1'

> [y, ...]

['2', '+', '3']

> '$y ...'

'2 + 3'

A tail pattern $id ... combined with a tail template $id ... is similar to using . in S-expression patterns and templates, where it allows sharing between the input and output syntax objects. That sharing and an associated expansion-cost difference is all the more important in the Rhombus expansion protocol, which must thread potentially long sequences into and out of macro transformers.

A $-escaped variable in a '' pattern matches one term among other terms in the group. A block created with : counts as a single term of its enclosing group, and a sequence of | alternatives (not an individual alternative) similarly counts as one term.

def '$x $y' = 'block: 1 2 3'

> x

'block'

> y

':« 1 2 3 »'

def '$z $w' = 'cond | is_ok: "good" | ~else: "bad"'

> z

'cond'

> w

'|« is_ok:« "good" » » |« ~else:« "bad" » »'

Keep in mind that '' creates syntax objects containing shrubberies that are not yet parsed, so a variable will not be matched to a multi-term sequence that would be parsed as an expression. For example, a pattern variable y by itself cannot be matched to a sequence 2 + 3:

> def '1 + $y + 4' = '1 + 2 + 3 + 4'

def: value does not satisfy annotation

  value: ’1 + 2 + 3 + 4’

  annotation: ’1 + $y + 4’

Having pattern variables always stand for individual terms turns out to be tedious, however. For example, to match a thunk pattern with a block that has any number of groups with any number of terms, you’d have to use two layers of ellipses. Then, to substitute that same body into a fun template, you’d have to use the two layers of ellipses again.

def thunk_form = 'thunk:

                    def x = 1

                    x + 1'

def 'thunk: $term ...; ...' = thunk_form

> 'fun (): $term ...; ...'

'fun ():« def x = 1; x + 1 »'

As a shorthand, when an escaped variable is alone in its group in a pattern, it stands for a match to the whole group (at least by default). A pattern variable that is alone in a multi-group context similarly stands for a match to all the groups.

def 'thunk: $group; ...' = thunk_form

def 'thunk: $body' = thunk_form

> [group, ...]

['def x = 1', 'x + 1']

> body

'def x = 1; x + 1'

As a further generalization, when an escaped variable is at the end of its group in a pattern, it stands for a match to remaining terms in group.

> def '1 + $y' = '1 + 2 + 3 + 4'

> y

'2 + 3 + 4'

These multi-term and multi-group syntax objects can be spliced into similar positions in templates, where an escape is by itself within its group or by itself in a multi-group position.

> 'fun (): $group; ...'

'fun ():« def x = 1; x + 1 »'

> 'fun (): $body'

'fun ():« def x = 1; x + 1 »'

There is no constraint that the original and destination contexts have the same shape, so a match from a block-like context can be put into a brackets context, for example.

> def '$x' = '1 + 2 + 3

              4 * 5 * 6'

> '[$x]'

'[1 + 2 + 3, 4 * 5 * 6]'

A multi-term, single-group syntax object can be spliced in place of any term escape, even if it is not at the end of the group.

> def '$x' = '1 + 2 + 3'

> '0 + $x + 4'

'0 + 1 + 2 + 3 + 4'

A multi-group syntax object splices multiple groups in place of a group escape only when the escape is alone in its group. A list of group syntax objects does not splice into group contexts, because that would create ambiguities among group and term contexts. Meanwhile, a single-term syntax object can be used as a group syntax object, a single-group syntax object can be used as a multi-group syntax object, and a single-term syntax object can be used as a multi-group syntax object.

Sometimes, a pattern variable that is at the end of a group is meant to match a single term and not a group of terms. To match a single term in a group context, annotate the pattern variable with the Term syntax class using the :: operator.

> def '$(x :: Term)' = '1'

> x

'1'

> def '$(x :: Term)' = '1 + 2'

def: value does not satisfy annotation

  value: ’1 + 2’

  annotation: ’$(x :: Term)’

You can similarly use the Group syntax class to match a single group instead of a multi-group sequence. There are several other predefined syntax classes, such as Identifier to match an identifier, String to match a string literal, and Int to match an integer literal.

In practice, you should use Block to match a block, preserving its lexical context for the implicit #%body form (see Implicit Forms).

def 'thunk: $(body :: Block)' = thunk_form

> 'fun () $body'

'fun ():« def x = 1; x + 1 »'