The Mini-language

The mini-language used by jertl contains elements which could be called “JSON adjacent”. The syntax for data structures will be recognizable by developers who have worked with JSON. This is more so for Python developers given there are elements of the syntax which are identical to that of the structural matching used in Python’s match statement.

Literals

The syntax for literals follows that of JSON.

>>> jertl.match('true', True) is not None
True
>>> jertl.match('false', False) is not None
True
>>> jertl.match('null', None) is not None
True

For numbers to be considered a match they must be of the same type.

>>> jertl.match('4', 4.0) is not None
False

whereas

>>> jertl.match('4', 4) is not None
True

Strings are specified using double quotes

>>> jertl.match('"a string"', 'a string') is not None
True

Single quoted strings are not allowed.

>>> jertl.match("'a string'", 'a string') is not None
Traceback (most recent call last):
...
jertlParseError: line 1: 0 token recognition error at: '''

Variables

In a matching context unbound variables match the current focus.

>>> jertl.match('x', True).bindings
{'x': True}

If bound, the varialble binding must match the current focus.

>>> jertl.match('[x, x]', [False, False]).bindings
{'x': False}

if not, the match fails,

>>> jertl.match('[x, x]', [4, 4.0]).bindings
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'bindings'

In a fill context unbound variables are not allowed.

>>> jertl.fill('[x]')
Traceback (most recent call last):
...
jertlFillException: 'x' not bound

Anonymous variables are indicated using an underscore ‘_’. Like regular variables these match anything but do not result in a binding. When used multiple times in a pattern they do not have to refer to the same value.

>>> jertl.match('[_, _, _]', [1, 2, 3]).bindings
{}

Structures

Arrays

Arrays are represented by a comma seperated list of expressions surrounded by brackets. Each expression must match item in the corresponding position of the data being matched.

>>> jertl.match('[1, 2.0, true, null, "string"]', [1, 2.0, True, None, 'string']) is not None
True

Splat expressions indicate a variable which is to be bound to a slice of the list being processed. These can only occur within a list expression.

>>> jertl.match('[*before, 3, *after]', [1, 2, 3, 4]).bindings
{'before': [1, 2], 'after': [4]}

As with non-splatted variables, once it is bound it must match to the same value wherever it occurs in the pattern.

>>> jertl.match('[*x, y, *x]', [1,2,3,4,1,2,3]).bindings
{'x': [1, 2, 3], 'y': 4}

Splatted variables does not need to be splatted each time.

>>> jertl.match('[*x, x]', [1,2,3,[1,2,3]]).bindings
{'x': [1, 2, 3]}

Patterns containing splatted variables can result in multiple matches.

>>> for match in jertl.match_all('[*before, x, *after]', [1, 2, 3, 4]):
...     print(match.bindings)
...
{'before': [], 'x': 1, 'after': [2, 3, 4]}
{'before': [1], 'x': 2, 'after': [3, 4]}
{'before': [1, 2], 'x': 3, 'after': [4]}
{'before': [1, 2, 3], 'x': 4, 'after': []}

Anonymous variables may also be splatted.

>>> for match in jertl.match_all('[*_, x, *_]', [1, 2, 3, 4]):
...     print(match.bindings)
...
{'x': 1}
{'x': 2}
{'x': 3}
{'x': 4}

Objects

The syntax of objects is a superset of that of JSON. Key/value pairs are seperated by colons. Pairs are surrounded by curly braces “{}”. Keys must be string literals. Values can be any expression. In addition the last item in an object pattern can be a double splatted variable (”**variable”).

>>> jertl.match('{"integer": 1, "boolean": true, "anything": anything, "list": [*list]}',
...            {'integer': 1, 'boolean': True, 'anything': {'inner': 'object'}, 'list': ['a', 'list']}).bindings
{'anything': {'inner': 'object'}, 'list': ['a', 'list']}

Double splatted variables are bound to the key/value pairs of the focus which were not referenced in the object pattern.

>>> jertl.match('{"x": x, "y": y, **double_splat}',
...            {'x': 1, 'y': 2, 'z': 3, 'name': 'Harry'}).bindings
{'x': 1, 'y': 2, 'double_splat': {'z': 3, 'name': 'Harry'}}

Once a double splatted is bound it must match the current focus

>>> jertl.match('[{"x": x, **double_splat}, {"y": y, **double_splat}]',
...            [{'x': 1, 'z': 3, 'name': 'Harry'}, {'y': 2, 'z': 3, 'name': 'Harry'}]).bindings
{'x': 1, 'double_splat': {'z': 3, 'name': 'Harry'}, 'y': 2}

Anonymous variables may be double splatted but this will not do anything useful.

Operations

Simple transforms

The pattern for simple transforms is two structure patterns one each side of the IMPLICATION token

<structure> --> <structure>

For example

'{"name": name, "status": "employed"} --> {"name": name, "status": "retired"}'

Targeted matches

Conjoins and rules, which can match to multiple data structures, explicitly identify which structure to examine.

<variable> ~ <structure>

For example

'employee ~ {"name": name, "status": "employed"}'

The variable, in this case employee, must be bound.

Targeted fills

Similarly, rules can perform multiple fill operations. The targeted fill pattern specifies a variable to be bound to a filled structure.

<variable> := <structure>

For example

'retiree :- {"name": name, "status": "retired"}'

The variable, in this case retiree, must not be bound.

Collations

The syntax for collations are a sequence of targeted matches seperated by whitespace

supervisor ~ {"underlings": [*_, name, *_]}
employee   ~ {"name": name}

Rules

rules are a sequence of targeted matches seperated by whitespace followed by IMPLIES, then a sequence of targetted fills seperated by whitespace.

 movies       ~ [*_, {"title": title, "MPAA rating": rating},        *_]
 MPAA_ratings ~ [*_, {"rating": rating, "explanation": explanation}, *_]
-->
 movie       := {"title": title, "contents": explanation}

Comments

Everything following a double slash (‘//’) is ignored.