8.12

3.6 Syntax Patterns and Classes🔗ℹ

As shown in Syntax Objects, a variable can be bound in a syntax pattern by escaping from the pattern with $. A $ can also be followed by a more complex escape. We have seen the use of the :: operator, for example. It takes a pattern variable and a syntax class name to specify the kind of syntax the pattern variable can match. The syntax classes Term, Group, and Multi are built in, among others.

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

Quotes create a syntax pattern, and they work nested inside an escape to create a nested pattern. Nesting immediately within an $ escape allows matching a literal $ or ..., analogous to the way those literals can be included when constructing syntax.

fun get_amt('$('$') $amt'): // matches `$` followed by any term

  amt

> def price_tag = '$('$') 17'

> price_tag

'$ 17'

> get_amt(price_tag)

'17'

> get_amt(' 17')

get_amt: argument does not satisfy annotation

  argument: ’€ 17’

  annotation: ’$(’$’) $amt’

Nested patterns are more useful with operators like || and &&, which take two syntax bindings and ensure that at least one matches or that both match, respectively. In particular, combining && with an identifier can give a name to a nested match.

// matches a parenthesized `*` term and names it `mult`

fun get_mult('1 + $(mult && '($_ * $_)')'):

  mult

> get_mult('1 + (2 * 3)')

'(2 * 3)'

> get_mult('1 + (2 / 3)')

get_mult: argument does not satisfy annotation

  argument: ’1 + (2 / 3)’

  annotation: ’1 + $(mult && ’($_ * $_)’)’

When a multi-term syntax pattern is used in a $ escape in a term context, the multi-term pattern is spliced into the enclosing group pattern. The || operator can try spliced sequences that have different lengths.

fun get_area_code('$('+ 1' || '') ($code) $_ - $_'):

  code

> get_area_code('+ 1 (801) 555 - 1212')

'801'

> get_area_code('(801) 555 - 1212')

'801'

Although Rhombus supports new binding operators through unquote_bind.macro, syntax classes provide a better way to organize most syntax abstractions. To define a new syntax class, use the syntax_class form:

syntax_class Arithmetic

| '$x + $y'

| '$x - $y'

Defining a syntax class in this way makes it available for use in syntax patterns, such as in def or match. The syntax class must be defined at the same phase as the referencing pattern. To define a syntax class for use in a macro definition, place it under the meta form.

meta syntax_class Arithmetic

| '$x + $y'

| '$x - $y'

Once defined, a syntax class can be used to annotate a pattern variable that matches any of pattern alternatives specified in the syntax class.

expr.macro 'add_one_to_expr $(a :: Arithmetic)':

  '$a + 1'

> add_one_to_expr 1 + 1

3

> add_one_to_expr 1 - 2

0

> add_one_to_expr 2 > 3

add_one_to_expr: expected the operator ‘+‘ or expected the operator ‘-‘

The $-escaped variables in a syntax class’s patterns bind to matched syntax objects as fields of the class. They can be accessed from a pattern variable using dot notation.

expr.macro 'right_operand $(a :: Arithmetic)':

  a.y

> right_operand 2 + 3

3

> right_operand 8 - 4

4

A field is accessible only when it appears in every pattern alternative of a syntax class.

syntax_class Arithmetic

| '$x + $y + $z'

| '$x - $y'

> def '$(a :: Arithmetic)' = '1 + 2 + 3'

> a.y

'2'

> a.z

z: no such field or method

  in value: ’1 + 2 + 3’

In other words, the fields of a syntax class are defined by the intersection of all escaped pattern variables found in the pattern alternatives. That’s more flexible than ||, which does not bind identifiers from either of its arguments.