processor

connector grammar version 38

  1. Release Notes
    1. 38
      1. Syntax Update
      2. Configuration and variables merged
      3. Application Support
      4. Data limits in schema
      5. Auto union decoration
      6. Attribute filters removed
      7. Error logging
      8. Template engine improvements
      9. Type system improved
      10. Automatic version migrations
      11. Debugger improvements
  2. The Standard Libraries
    1. Calendar
    2. Network
    3. Plural
    4. Unicode
    5. Data
  3. Processor
    1. Obscure data
    2. The internal library.
    3. The type of the connector.
    4. Internal error handler.
    5. Library Hook
    6. Expression
    7. Safe Expression
    8. Statement
'none': component 'hook'

Release Notes

38

Upgrade instructions:

Ensure the project is build and validated with version 36.7 of the connector. Do not run a language-server while upgrading, to prevent it from interfering with the upgrade process.

The upgrade script requires a single argument, the path to the connector system to upgrade. Example:

.alan/devenv/system-types/connector/scripts/upgrade/upgrade.sh systems/connector

Syntax Update

The syntax has been changed to better align with other alan languages. Use the upgrade script to automatically convert a processor from version 36.6.

Configuration and variables merged

The functionality of configuration and variables have been merged. The strict type checking and data guarantees are used, but the data is obtained from the runtime configuration using a new platform feature.

Application Support

The connector now understand Application models and can be configured to migrate Application data from one model to another.

Data limits in schema

In addition to number range and text length limits, boolean can be constraint to a specific value and binary length can be limited.

Auto union decoration

When parsing, unions are automatically resolved to the first type that matches. This uses the syntactic order of the union’s types.

Attribute filters removed

Attribute filters on properties of schema nodes are removed. These can be set through data limits on the actual attributes.

Error logging

Logging to the functional error channel with @error: is now supported.

Template engine improvements

The template engine can infer the amount of decimal places from the input type and reports this information in it’s schema output. This is used to set the default style properties when the number is formatted. Templates now support escaping for text properties, either with a template wide dictionary or with an instruction specific dictionary. Escaping can also be disable per instruction.

Type system improved

The connector now uses a single type system internally, fixing many type related quirks. This also merges the different calling conventions used between statement and expressions. Type checking for XML and CSV has been improved to give proper design time feedback about supported layouts. Arbitrary types can now be instantiated out of their tree. Removed cardinality context, results are now either automatically merged (for plural values) or need to be resolved manually (with discard).

Automatic version migrations

The runtime automatically resolves differences between minor versions, allowing the runtime to execute packages for older versions within the same major version.

Debugger improvements

The debugger’s ability to step through a program is improved. It can now step into, over and out of statements.

The Standard Libraries

The connector provides a set of standard libraries. These libraries provide functionality outside the language constructs of the connector.

Calendar

The calendar library provides conversions between the connectors internal date time representation and broken down time.

define 'type': choice ( 'date' 'date-time' 'time' )

define 'calendar': {
	'year': integer
	'month': integer where range ( 1 , 12 )
	'day': integer where range ( 1 , 31 )
}

define 'weekday': choice ( 'Monday': 1 'Tuesday': 2 'Wednesday': 3 'Thursday': 4 'Friday': 5 'Saturday': 6 'Sunday': 7 )

define 'week': {
	'year': integer
	'week': integer where range ( 1 , 53 )
	'day': 'weekday'
}

define 'ordinal': {
	'year': integer
	'day': integer where range ( 1 , 366 )
}

define 'time': {
	'hour': integer where range ( 0 , 23 )
	'minute': integer where range ( 0 , 59 )
	'second': integer where range ( 0 , 59 )
}

define 'date time': {
	'calendar': 'calendar'
	'week': 'week'
	'ordinal': 'ordinal'
	'time': 'time'
}

define 'constructor': {
	'date': union (
		'calendar' 'calendar'
		'week' 'week'
		'ordinal' 'ordinal'
	)
	'time': optional 'time'
}

/* Converts Alan Time to broken down time.
 * When information is not present in the source, it is set to zero.
 * When timezone is not set, it will be evaluated in Etc/UTC.
 */
define 'convert': function (
	integer
	$'source type': 'type'
	$'timezone': optional text
) : 'date time' = "ca798e96fe9b6bd53c960192c8d0cf8cd4175e7f"

/* Construct Alan Time from broken down time.
 * The result is either a date-time or a date, depending on whether or not 'time' is set.
 * When timezone is not set, it will be evaluated in Etc/UTC.
 */
define 'construct': function (
	'constructor'
	$'timezone': optional text
) : integer = "b8397afa5b82bce0c97ff08e5ef7d120a8c1ee73"

library

Network

The network library provides functions to perform network request and the data structures to represent related objects.

define 'method': choice ( 'get' 'head' 'post' 'put' 'delete' )

define 'security': choice ( 'strict' 'preferred' 'none' )

define 'key value list': collection case folding text

define 'request': {
	'method': 'method'
	'parameters': optional 'key value list'
	'headers': optional 'key value list'
	'content': optional binary
}

define 'response': {
	'status': integer
	'headers': 'key value list'
	'content': binary
}

define 'authentication': {
	'type': union (
		'user' {
			'username': text
			'password': @protected text
		}
		'oauth' {
			'username': text
			'token': @protected text
		}
	)
}

define 'address': {
	'name': text
	'address': text
}

define 'message part': {
	'type': text
	'content': text
}

define 'message content': {
	'id': text
	'type': text
	'subtype': text
	'content': binary
}

define 'message attachment': {
	'filename': text
	'type': text
	'subtype': text
	'content': binary
}

define 'message': {
	'from': list 'address'
	'sender': optional 'address'
	'reply to': optional list 'address'
	'to': list 'address'
	'cc': optional list 'address'
	'bcc': optional list 'address'
	'subject': optional text
	'date': optional integer
	'message-id': optional text
	'headers': optional 'key value list'
	'content': {
		'parts': list 'message part'
		'content': optional list 'message content'
		'attachments': optional list 'message attachment'
	}
}

/* Parses and decorates text as 'message'.
 */
define 'parse network message': function ( binary ) : unsafe 'message' = "cb3038cf22544368256da4f4843f7751d930873e"

/* Serializes a 'network message' to text.
 */
define 'serialize network message': function (
	'message'
	$'include bcc': boolean
) : unsafe binary = "8991b5e7ace6a89c6c095509b1c9106f19c0656b"

/* Performs an HTTP(S) request.
 * Methods are mapped directly to their HTTP equivalent.
 * When provided, 'content' is send with the request.
 */
define 'http': function (
	$'server': text
	$'path': text
	$'authentication': optional 'authentication'
	$'request': 'request'
) : unsafe 'response' = "171a2a495168c8db61a75ea879759560a5a38db5"

/* Performs an FTP(S) request.
 * Method mapping:
 *  > 'get': Retrieves the content of 'path'. For this to work on directories, 'path' must end with a slash (/).
 *  > 'head': Retrieves the directory entries at 'path' without meta data. 'path' must be a directory, but does not need to end with a slash (/).
 *  > 'post': Stores 'content' in 'path'.
 *  > 'put': Identical to 'post'.
 *  > 'delete': Attempts to remove 'path'. Unlike other methods, this transmits 'path' directly to the server for interpretation.
 * Only when storing data, should content be set.
 */
define 'ftp': function (
	$'server': text
	$'path': text
	$'method': 'method'
	$'authentication': optional 'authentication'
	$'security': 'security'
	$'content': optional binary
) : unsafe optional binary = "25bd9db5582c68caabef54601bb1cef80fc24fe6"

/* Retrieves all messages matching 'criteria' with IMAP(S).
 * See RFC-3501 section 6.4.4 for valid values of 'criteria'.
 * All messages are parsed as if passed to `function 'parse network message'`.
 */
define 'imap': function (
	$'server': text
	$'path': text
	$'criteria': text
	$'authentication': optional 'authentication'
	$'security': 'security'
) : unsafe list 'message' = "f263bada59d75b01ecf6f5d620a9d090f5f12926"

/* Retrieves all messages matching 'criteria' with IMAP(S).
 * See RFC-3501 section 6.4.4 for valid values of 'criteria'.
 */
define 'imap list': function (
	$'server': text
	$'path': text
	$'criteria': text
	$'authentication': optional 'authentication'
	$'security': 'security'
	$'callback': function ( $'blob': binary ) : boolean
) : unsafe list boolean = "5833f6978cd3090249f0beac809961d9054d5a90"

/* Retrieves all messages after 'last uid' with IMAP(S).
 * Performs an optional UIDVALIDITY check with 'uid validity' when set.
 */
define 'imap list uid': function (
	$'server': text
	$'path': text
	$'uid validity': optional integer
	$'last uid': integer
	$'authentication': optional 'authentication'
	$'security': 'security'
	$'callback': function (
		$'uid': integer
		$'blob': binary
	) : boolean
) : unsafe optional integer = "c0c069849e5ec03a546b737c782a139678c2b695"

/* Retrieves all mailboxes with IMAP(S).
 */
define 'imap listing': function (
	$'server': text
	$'path': text
	$'authentication': optional 'authentication'
	$'security': 'security'
) : unsafe list text = "94c875f1b608ec08204f53bc677d7662168c71d8"

/* Send a message with SMTP(S).
 * The message is serialized as if passed to `function 'serialize network message'`.
 * When the transfer fails, this function throws an error.
 */
define 'smtp': function (
	$'server': text
	$'authentication': optional 'authentication'
	$'security': 'security'
	$'message': 'message'
) : unsafe boolean = "8d09c84214fc074d7d9cc8123e59da15605c01b2"

library
	/* Register webserver handlers.
	 * The handler is used as the path to respond to.
	 * Each handler is triggered by external web requests for the path.
	 */
	hook 'webserver'
		< T is node > (
			T
			$'request': 'request'
		) : 'response' = "dfc3f634b2167c0d7429a10ff11a00550a142354"

Plural

The plural library provides algorithms operating on sets.

/* Sorts a set based on a comparison function.
 */
define 'sort': function < T is plural E > (
	T
	$'compare': function (
		$'A': E
		$'B': E
	) throws : boolean
) throws : T = "bdf496ca369a4ba51e47122cd1ec8fb8bcf37898"

/* Selects a single entry in a set based on a comparison function.
 * This returns the entry for which `compare` results in true when the entry is `A` and false when `B` compared to all other entries in the set.
 * It fails when no such entry is found.
 */
define 'select': function < T is plural E > (
	T
	$'compare': function (
		$'A': E
		$'B': E
	) throws : boolean
) throws : unsafe E = "6f40c5cf50c67547e023cf4540da3f1b0c5af505"

/* Filters a set.
 * This returns a new set containing only the entries for which `filter` results in true.
 */
define 'filter': function < T is plural E > (
	T
	$'filter': function ( $'entry': E ) throws : boolean
) throws : T = "368b73f2ca1fc93c4d4df568ea0438e4c5a8211a"

/* Divide a set in several smaller sets (buckets).
 * The order of the entries is maintained.
 */
define 'split': function < T is plural E > (
	T
	$'bucket size': integer
) : list T = "a21f3c1edb32d67395b4f9a9c2150d5011d13d0b"

library

Unicode

The unicode library provides functions to manipulate text values.

define 'trim style': choice ( 'leading' 'trailing' 'both' 'none' )

define 'alignment': choice ( 'left' 'right' )

define 'message data': {
	'types': collection union (
		'calendar' integer
		'number' integer
		'text' text
	)
}

/* Imports text from binary data.
 * Text is converted from the specified encoding to the internal encoding.
 * When the specified encoding is not known, or no know conversion to the internal encoding is known, the function fails.
 */
define 'import': function (
	binary
	$'encoding': text
) : unsafe text = "9e980f69ab15906c61ceeb1ac1a2125d6fc03349"

/* Exports text to binary data.
 * Text is converted to the specified encoding from the internal encoding.
 * When the specified encoding is not known, or no know conversion from the internal encoding is known, the function fails.
 */
define 'export': function (
	text
	$'encoding': text
) : unsafe binary = "6493b6da20eb5dde5bfb9581543e09b3f1b0fc44"

/* Returns a text value as binary data.
 * The data is encoded in the internal encoding.
 */
define 'as binary': function ( text ) : binary = "6649c6455c721f73c26ce7916b6b705048b79e1b"

/* Replaces all occurrences of the key of the entries in the dictionary with their value.
 */
define 'escape': function (
	text
	$'dictionary': collection text
) : text = "397e23c87d21e3651a5a0341eb06957d6c11a922"

/* Removes all whitespace from a text value.
 */
define 'strip': function ( text ) : text = "b1ad934aa23db1551ece5ecef7e08d824efa660a"

/* Removes leading and/or trailing whitespace from a text value.
 */
define 'trim': function (
	text
	$'style': 'trim style'
) : text = "5652809fd18b42a7313d5793801f4c79f887c3d4"

/* Split a text into multiple fragments.
 * Empty fragments are automatically removed.
 */
define 'split': function (
	text
	$'style': 'trim style'
) : list text = "25e981050bc615be2930712c7c250365b2570345"

/* Adds whitespace to a text value.
 */
define 'pad': function (
	text
	$'align': 'alignment'
	$'length': integer
) : text = "be6f170dc45c3b8b120c5b5954cbd875ce7a7c48"

/* Match a text with a Regular Expression.
 * The whole input must match the pattern.
 */
define 'regex': function (
	text
	$'pattern': text
) : unsafe boolean = "e24486d2b0065ac06d1497e280838b4b3bb9f039"

/* Result of the template function.
 */
define 'template result': {
	'success': boolean /* Value indicating whether the template was transformed correctly or not. */
	'result': text /* On success contains the transformed template, on failure contains the error message. */
	'schema': text /* The schema inferred from the input data. */
}

/* Format a template with dynamic data.
 */
define 'template': function < T is node > (
	T
	$'template': text
	$'dictionary': collection text
	$'locale': text
) : unsafe 'template result' = "63a8bab4a6e2efa51c4d048fdbcde794df07c62a"

library

Data

The data library provides functions to manipulate binary values.

define 'base64 alphabet': choice ( 'base64' 'base64url' )

/* Converts text from one encoding to another.
 * Available encodings depend on the hosting systems.
 */
define 'convert': function (
	binary
	$'from': text
	$'to': text
) : unsafe binary = "e84c70582091283e8ef035eb15edd57b5e1bfa93"

/* Convert binary data to base64 text.
 * Alphabet as defined by RFC-4648.
 */
define 'base64 encode': function (
	binary
	$'alphabet': 'base64 alphabet'
) : text = "a28d688c547335308cefd17e61fd26e0bda09610"

/* Convert base64 text to binary data.
 * Alphabet as defined by RFC-4648.
 */
define 'base64 decode': function (
	text
	$'alphabet': 'base64 alphabet'
) : unsafe binary = "4c9ab1bc2636b3711553500ec4e153e8356e4778"

/* Loads an archive from data.
 * Archive format and any compression/encoding are automatically detected.
 */
define 'load archive': function ( binary ) : unsafe collection binary = "fba823216f0f6dc6bbb644fc0c2c0b21ae69f8c1"

library

Processor

Obscure data

Obscures all textual data during assignment in a consistent way.

'obscure data': stategroup (
	'yes' { [ obscure data ] }
	'no' { }
)

The internal library.

Allows defining reusable types.

'library': dictionary { [ define ]
	'type': [ : ] stategroup (
		'schema' {
			/* Define a schema type.
			 */
			'type': component 'schema complex type'
		}
		'pattern' { [ pattern ]
			/* Define a pattern.
			 */
			'rule': component 'pattern rule'
		}
		'function' { [ function ]
			/* Define a function.
			 */
			'signature': component 'signature'
			'type': stategroup (
				'implementation' {
					'implementation': [ => ] component 'function implementation'
				}
				'binding' {
					/* The function is part of a standard library.
					 * These functions are implemented by the connector runtime.
					 */
					'binds': [ = ] text
				}
			)
		}
	)
}

The type of the connector.

This is chosen externally, the archetype here must follow the external choice.

'archetype': stategroup (
	'scheduled provider' { [ provider ]
		/* The connector is an Alan Interface Provider.
		 * As a provider, the connector runs the main routine based on an external schedule.
		 * Optionally, command handlers can be provided.
		 */
		/* Main routine.
		 * Each run of the main routine must create a snapshot of the current state.
		 * As a result the current state must always be a complete dataset and not an update to a previous dataset.
		 */
		'main routine': component 'block statement'
		/* Dataset initialization.
		 * Before a provider can process commands and generate events, a dataset is required.
		 * When available, the last result of the main routine is used as dataset.
		 * Otherwise the initialization is used to determine the initial dataset.
		 */
		'initialization': [ init ] stategroup (
			'custom' {
				/* Use a custom routine to generate the initial dataset.
				 * This can be used to prevent additional runs of the main routine when this would exceed rate limits/query quotas.
				 */
				'routine': component 'block statement'
			}
			'main' { [ main ]
				/* Use the main routine to generate the initial dataset.
				 * This run of the main routine is in addition to runs caused by the external schedule.
				 */
			}
			'none' { [ none ]
				/* Do not generate an initial dataset.
				 * Received commands are ignored until the main routine is triggered.
				 */
			}
		)
		/* Command routines.
		 * These routines are run when the corresponding command is received.
		 * Commands can have multiple handlers. When the command is received, all handlers are executed in an undefined order.
		 * Received commands for which no handlers exist, are ignored.
		 */
		'routines': dictionary { [ routine ]
			'has next': stategroup = node-switch successor (
				| node = 'yes' { 'next' = successor }
				| none = 'no'
			)
			/* The path to the command the routine is bound to.
			 * The path becomes the initial scope of the routine.
			 */
			'binding': [ on root ] component 'interface named path'
			'type': stategroup (
				'execute' {
					/* Execute a statement.
					 * From this context Alan Interface events can be executed.
					 */
					'statement': component 'block statement'
				}
				'schedule' { [ schedule ]
					/* Schedule a run of the main routine immediately after the command.
					 * The main routine is run, and consumers are updated, regardless of the external schedule.
					 * This run of the main routine is in addition to runs caused by the external schedule.
					 * This fully ignores the binding path of the routine and even triggers properly when there is no dataset yet.
					 */
				}
			)
		}
		/* The hooks.
		 * See `Library Hooks` for more information.
		 */
		'hooks': set {
			'hook': component 'library hook'
		}
	}
	'consumer' { [ consumer ]
		/* The connector is an Alan Interface Consumer.
		 * As a consumer, the connector runs routines based on the nodes touched by updates or received events.
		 */
		/* The context keys on which to subscribe.
		 * The statement is restricted, see 'statement' for details.
		 */
		'context keys': component 'block statement'
		/* The routines.
		 * Each routine is bound to something in the interface.
		 * The routine is triggered based on the interface binding.
		 */
		'routines': dictionary { [ routine ]
			'has next': stategroup = node-switch successor (
				| node = 'yes' { 'next' = successor }
				| none = 'no'
			)
			/* The path to the interface data the routine is bound to.
			 * The path becomes the initial scope of the routine.
			 */
			'binding': [ on root ] component 'interface named path'
			/* From this context Alan Interface commands can be executed.
			 */
			'statement': component 'block statement'
		}
		/* The hooks.
		 * See `Library Hooks` for more information.
		 */
		'hooks': set {
			'hook': component 'library hook'
		}
	}
	'migration' { [ root = root as $ ]
		/* The connector is a migration.
		 * As a migration, the connector runs a single routine once.
		 */
		/* The routine mapping data from 'source' to 'target'
		 */
		'statement': component 'block statement'
	}
	'library' { [ library ]
		/* The connector is a library.
		 * Currently only standard libraries are supported.
		 */
		/* A library can expose hooks.
		 * Each hook is triggered by external events.
		 */
		'hooks': dictionary { [ hook ]
			'signature': component 'signature'
			'binds': [ = ] text
		}
	}
)

Internal error handler.

Allows specifying an error handler routine. Example:

consumer {
	( )
}
/* error-handler example
 *  this logs the error report
 */
on error {
	@log: $
}
'error handler': stategroup (
	'yes' {
		/* The routine to execute for uncaught errors.
		 * When a notification/command/event triggers multiple routines, the error reports from these are bundled and the handler is executed only once for the bundle.
		 * The statement is restricted, see 'statement' for details.
		 */
		'statement': [ on error ] component 'block statement'
	}
	'no' { }
)
'hook' { }
'library selector' {
	/* Select a type from a standard or the internal library.
	 */
	'library': stategroup (
		'dependency' {
			'dependency': [, / ] reference
		}
		'self' { }
	)
	'type': reference
}

Library Hook

The standard library defines hooks. When or how these hooks are triggered is defined by the individual hooks. Providers and consumers can add functions to these hooks. These functions are executed when the hook is triggered. Some hooks only trigger a subset of the added functions, based on the handler name.

Functions added to hooks can execute side-effects based on their context. Providers can execute events. Consumers can execute commands.

Example:

consumer {
	( )
}

/* Standard Library `network.lib`::`webserver` example
 *  this registers a webserver hook for the `/echo` path
 *  the original request is returned as JSON
 */
add-hook 'network'::'webserver' "/echo" {
	(
		'status' = 200
		'headers' = create [ "content-type" ] "application/json"
		'content' = call 'unicode'::'as binary' ( serialize $'request' as JSON )
	)
}
'library hook' {
	/* The standard library and hook to add the function to.
	 */
	'library': [ add-hook ] reference
	'hook': [ :: ] reference
	/* The handler name.
	 * Usages of this value is defined by the individual hooks.
	 */
	'name': text
	/* The implementation to run when the hook is triggered.
	 * The root node of the interface data is available in `$`.
	 * The arguments for the library hook defined parameters become the initial scope.
	 */
	'instance': component 'callable instance'
	'implementation': component 'function implementation'
}
'interface binding assignment' { [ (, ) ]
	'name': reference = first
	'named objects': [ as ] dictionary { [ $ ]
		'has successor': stategroup = node-switch successor (
			| node = 'yes' { }
			| none = 'no'
		)
	}
}
'interface type path' {
	'has step': stategroup (
		'yes' {
			'type': stategroup (
				'collection' { [ * ]
					/* Collection binding.
					 * The set of entries touched by an update (and only the touched entries) are made available.
					 * The value of each entry is an optional node.
					 * When an entry was created or updated, the node is set to the entry node.
					 * When an entry was removed, the node is unset.
					 */
				}
				'stategroup' {
					'state': [ ? ] reference
				}
				'attribute' {
					'attribute': [ . ] reference
				}
			)
			'tail': component 'interface type path'
		}
		'no' { }
	)
}
'interface named path' {
	'head': component 'interface type path'
	'has tail': stategroup (
		'yes' {
			'assignment': component 'interface binding assignment'
			'tail': component 'interface named path'
		}
		'no' { }
	)
}
'comparator' {
	'type': stategroup (
		'case folding' { [ case folding ]
			/* The case-folding comparator operates only on texts.
			 * It supports equality and relational comparisons.
			 */
		}
		'simple' {
			/* The simple comparator behaves depending on the it operates on.
			 *  > boolean: supports only equality comparisons
			 *  > integer: supports equality and relational comparisons
			 *  > binary : supports only equality comparisons, 2 binaries are considered equal when they are bitwise identical
			 *  > text   : supports equality and relational comparisons, 2 texts are compared based on their code-points, the values are not normalized
			 *  > file   : supports only equality comparisons, 2 files are considered equal when both token and extension are identical
			 */
		}
	)
}
'schema scalar type' {
	'type': stategroup (
		'boolean' { [ boolean ]
			/* A boolean type can hold the value `true` or `false`.
			 */
			'limits': stategroup (
				'yes' { [ where ]
					/* Limit the value.
					 */
					'must be': stategroup (
						'true' { [ true ] }
						'false' { [ false ] }
					)
				}
				'no' { }
			)
		}
		'number' {
			'type': stategroup (
				'integer' { [ integer ]
					/* An integer can hold numbers.
					 * Integers can not have a fraction.
					 * Parsing and serializing always uses base 10.
					 */
				}
				'decimal' { [ decimal ]
					'rule': component 'decimal import rule'
				}
			)
			'limits': stategroup (
				'yes' { [ where range (, ) ]
					/* Limit the value range.
					 * All limits are inclusive.
					 */
					'lower': stategroup (
						'yes' {
							/* Limit the minimum value.
							 * The value is evaluated after the decimal import rule.
							 */
							'value': integer
						}
						'no' { }
					)
					'upper': [ , ] stategroup (
						'yes' {
							/* Limit the maximum value.
							 * The value is evaluated after the decimal import rule.
							 */
							'value': integer
						}
						'no' { }
					)
				}
				'no' { }
			)
		}
		'binary' { [ binary ]
			/* A data can hold any type of binary data.
			 */
			'limits': stategroup (
				'yes' { [ where length (, ) ]
					/* Limit the binary length in bytes.
					 * All limits are inclusive.
					 */
					'lower': stategroup (
						'yes' {
							/* Limit the minimum length of the value.
							 * The length is measured in code-points.
							 */
							'length': integer
						}
						'no' { }
					)
					'upper': [ , ] stategroup (
						'yes' {
							/* Limit the maximum length of the value.
							 * The length is measured in code-points.
							 */
							'length': integer
						}
						'no' { }
					)
				}
				'no' { }
			)
		}
		'text' { [ text ]
			/* A text can hold Unicode data.
			 */
			'limits': stategroup (
				'yes' { [ where length (, ) ]
					/* Limit the text length in code-points.
					 * All limits are inclusive.
					 */
					'lower': stategroup (
						'yes' {
							/* Limit the minimum length of the value.
							 * The length is measured in code-points.
							 */
							'length': integer
						}
						'no' { }
					)
					'upper': [ , ] stategroup (
						'yes' {
							/* Limit the maximum length of the value.
							 * The length is measured in code-points.
							 */
							'length': integer
						}
						'no' { }
					)
				}
				'no' { }
			)
		}
		'choice' { [ choice ]
			/* A choice can hold a single value from a user defined set of values.
			 * A choice has a base type, this type only effects parsing and serialization.
			 * So a choice with an integer base type cannot be compared to an actual integer.
			 */
			'options': [ (, ) ] dictionary {
				'has next': stategroup = node-switch successor (
					| node = 'yes' { 'next' = successor }
					| none = 'no'
				)
				'base type': stategroup (
					'integer' {
						'value': [ : ] integer
					}
					'text' { }
				)
			}
		}
	)
}
'schema complex type' {
	'type': stategroup (
		'library' {
			/* Library type.
			 * This is not an actual type.
			 * The type of this data is the referred type.
			 */
			'selector': component 'library selector'
		}
		'union' { [ union ]
			/* Union type.
			 * The type of this data is conditional.
			 * Parsing tries each sub-type in order, the first sub-type that successfully parses becomes the selected sub-type.
			 */
			'types': [ (, ) ] dictionary {
				'has next': stategroup = node-switch successor (
					| node = 'yes' { 'next' = successor }
					| none = 'no'
				)
				'type': component 'schema complex type'
			}
			'first': reference = first
		}
		'optional' { [ optional ]
			/* Optional type.
			 * Wraps another type and allows it to have an explicit unset state.
			 * Parsing and serialization either expects the data to be completely omitted or have a special none value construct.
			 */
			'type': component 'schema complex type'
		}
		'node' {
			/* Node type.
			 */
			'form': stategroup (
				'ordinary' {
					/* Parsing and serialization expects the type information (property names) to be present in the data.
					 */
				}
				'headless' { [ headless ]
					/* Parsing and serialization expects the data to be ordered by the property order.
					 */
				}
			)
			'node': component 'schema node type'
		}
		'collection' { [ collection ]
			/* Set type.
			 * This holds a dynamic amount of data, or no data at all (empty set).
			 * Every entry has an implicit key that must be unique within the set.
			 * A comparator is used for matching the implicit keys.
			 */
			'comparator': component 'comparator'
			'type': component 'schema complex type'
		}
		'list' { [ list ]
			/* Set type.
			 * This holds a dynamic amount of data, or no data at all (empty set).
			 * Every entry automatically get a numeric key assigned that is unique within the set, the key holds no semantic meaning.
			 */
			'type': component 'schema complex type'
		}
		'table' { [ table ]
			/* Special set type.
			 * A table behaves as if it was a list of headless nodes, except for a special parsing/serialization specialization.
			 * The table expects the very first row in serialized from to be the header.
			 * This header must match all properties in the node, but can do so in any order.
			 * Unlike the headless node, where the property order is defined by the schema, the parsing/serialization order of all properties is defined by this header row.
			 * Every entry automatically get a numeric key assigned that is unique within the set, the key holds no semantic meaning.
			 */
			'node': component 'schema node type'
		}
		'template' {
			'argument': stategroup (
				'T' { [ T ] }
				'E' { [ E ] }
			)
		}
		'scalar' {
			'type': component 'schema scalar type'
		}
	)
}
'schema node type' {
	/* A node is a container containing a static set of named values, the properties.
	 * Each property itself can have a set of attributes, which must be text types, choices with text base types are also allowed.
	 * A property can be marked as optional, an optional property may be omitted or have a special none value construct in serialized form.
	 *
	 * A property can be marked as protected, protected property data is always excluded from data dumps.
	 * This is to prevent the connector leaking sensitive data.
	 * NOTE: Using @protected is no guarantee the relevant data is never included.
	 *       When the sensitive data is also present in unprotected data, it will still be readable in that data.
	 */
	'properties': [ {, } ] dictionary {
		'has next': stategroup = node-switch successor (
			| node = 'yes' { 'next' = successor }
			| none = 'no'
		)
		'has attributes': stategroup (
			'yes' { [ <, > ]
				'attributes': dictionary {
					'has next': stategroup = node-switch successor (
						| node = 'yes' { 'next' = successor }
						| none = 'no'
					)
					'type': [ : ] component 'schema scalar type'
				}
				'first': reference = first
			}
			'no' { }
		)
		'protect': [ : ] stategroup (
			'yes' { [ @protected ] }
			'no' { }
		)
		'type': component 'schema complex type'
	}
	'has properties': stategroup = node-switch .'properties' (
		| nodes = 'yes' { 'first' = first }
		| none  = 'no'
	)
}
'schema instance' {
	'type': component 'schema complex type'
}
'stack block' {
	/* A stack block is a group of `let` expressions.
	 * With `let` expressions it is possible to store a value in a name.
	 * This value can be retrieved at a later point.
	 *
	 * A `let` expression is not a variable.
	 * Unlike a variable, a `let` expression is always given a value when it is declared.
	 * It also can never be assigned a different value at a later point.
	 */
	'values': dictionary { [ let $ ]
		'has previous': stategroup = node-switch predecessor (
			| node = 'yes' { 'previous' = predecessor }
			| none = 'no'
		)
		'has next': stategroup = node-switch successor (
			| node = 'yes' { 'next' = successor }
			| none = 'no'
		)
		'type': stategroup (
			'explicit' {
				/* Target driven `let`.
				 * The type of this `let` is the type of the schema.
				 */
				'schema': [ : ] component 'schema instance'
			}
			'inferred' {
				/* Source driven `let`.
				 * The type of this `let` is inferred from the statement.
				 */
			}
		)
		'report unused': stategroup (
			'yes' { }
			'no' { [ @unused ] }
		)
		'statement': [ = ] component 'statement'
	}
}
'stack selector' {
	/* Navigate the stack.
	 * Each frame on the stack is created by a scope.
	 */
	'stack': stategroup (
		'non-empty' {
			'select': stategroup (
				'this frame' {
					/* Select a value in the selected frame.
					 */
					'value': [ $ ] reference
				}
				'parent frame' {
					/* Select a value in the parent frame.
					 */
					'tail': [ ^ ] component 'stack selector'
				}
			)
		}
	)
}
'type path step' {
	'has step': stategroup (
		'yes' {
			'type': stategroup (
				'collection' { [ * ] }
				'choice' {
					'state': [ ? ] reference
				}
				'node' {
					'attribute': [ . ] reference
				}
			)
			'tail': component 'type path step'
		}
		'no' { }
	)
}
'type path' {
	'root': stategroup (
		'interface' { [ interface ] }
		'schema' {
			'schema': component 'schema complex type'
		}
		'context' { [ context ] }
		'target' { [ target ] }
	)
	'steps': component 'type path step'
}
'type definition' {
	'type': stategroup (
		'file' { [ file ] }
		'function' {
			'signature': [ function ] component 'signature'
		}
		'type' {
			'path': component 'type path'
		}
	)
}
'signature' {
	/* The signature of a callable.
	 */
	'template': stategroup (
		'yes' { [ < T, > ]
			/* Add a template type to the signature.
			 * An optional constraint can be added to the type.
			 */
			'requirement': stategroup (
				'plural' { [ is plural E ] }
				'optional' { [ is optional E ] }
				'node' { [ is node ] }
				'none' { }
			)
		}
		'no' { }
	)
	'input': group { [ (, ) ]
		/* The implicit parameter definition.
		 */
		'context': stategroup (
			'yes' {
				'type': component 'type definition'
			}
			'no' { }
		)
		/* Parameter definition.
		 */
		'parameters': dictionary { [ $ ]
			'has next': stategroup = node-switch successor (
				| node = 'yes' { 'next' = successor }
				| none = 'no'
			)
			'type': [ : ] component 'type definition'
		}
		'has parameters': stategroup = node-switch .'parameters' (
			| nodes = 'yes' { 'first' = first }
			| none  = 'no'
		)
	}
	/* The exception generation capability.
	 */
	'exceptions': stategroup (
		'yes' { [ throws ] }
		'no' { }
	)
	/* The expression guarantee of the output.
	 */
	'guarantee': [ : ] stategroup (
		'yes' { }
		'no' { [ unsafe ] }
	)
	/* The type of the output.
	 */
	'result': stategroup (
		'explicit' {
			'result': component 'type definition'
		}
		'none' { [ none ] }
	)
}
'callable instance' {
	/* Instantiates a callable.
	 * Optional can set the template explicitly.
	 * Otherwise the template, if any, is inferred from the context.
	 */
	'template': stategroup (
		'explicit' { [ <, > ]
			'type': component 'type definition'
		}
		'implicit' { }
	)
	'object': component 'hook'
}
'function implementation' {
	/* The implementation of a function.
	 */
	'statement': component 'block statement'
}
'function call' { [ (, ) ]
	/* The arguments for a callable object.
	 */
	'with context': stategroup (
		'yes' {
			'value': component 'expression'
		}
		'no' { }
	)
	'values': dictionary { [ $ ]
		'statement': [ = ] component 'statement'
	}
}
'root selector' {
	'type': stategroup (
		'library' {
			'library': reference
			'tail': [ :: ] component 'root selector'
		}
		'this' { }
	)
}
'callable selector' {
	'type': stategroup (
		'function' {
			/* Select a function.
			 */
			'context': component 'root selector'
			'function': reference
			'instance': component 'callable instance'
		}
		'recurs' { [ self ]
			/* Select self.
			 * This represents the currently executing function.
			 * Only available in a function implementation.
			 */
		}
		'stored' {
			/* Retrieve a stored function.
			 */
			'value': component 'expression'
		}
	)
}
'decimal import rule' {
	'decimal places': integer
}
'pattern rule piece' {
	'type': stategroup (
		'pattern' {
			'capture': stategroup (
				'yes' { [ $ ] }
				'no' { }
			)
			'type': stategroup (
				'integer' { [ integer ] }
				'decimal' { [ decimal ]
					'rule': component 'decimal import rule'
					'locale': [ locale: ] text
				}
				'text' { [ text ] }
			)
			'match style': stategroup (
				'lazy' {
					/* Lazy matching seeks the smallest possible match. */
				}
				'greedy' { [ * ]
					/* Greedy matching seeks the largest possible match. */
				}
			)
			'repeat': stategroup (
				'yes' { [ {, } ]
					'lower': stategroup (
						'yes' {
							'min': integer
						}
						'no' { }
					)
					'upper': [ , ] stategroup (
						'yes' {
							'max': integer
						}
						'no' { }
					)
				}
				'no' { }
			)
		}
		'static' {
			'text': text
		}
	)
	'has tail': stategroup (
		'yes' {
			'tail': component 'pattern rule piece'
		}
		'no' { }
	)
}
'pattern rule' { [ (, ) ]
	/* Describes a custom pattern.
	 * A pattern is an ordered set of named parts, with each part an ordered set of pieces.
	 * Each part must capture a single piece. Only dynamic pieces can be captured.
	 * A pattern must contains at least a single part.
	 */
	'parts': dictionary {
		'has next': [ : ] stategroup = node-switch successor (
			| node = 'yes' { 'next' = successor }
			| none = 'no'
		)
		'pieces': component 'pattern rule piece'
	}
	'head': reference = first
}
'expression tail step' {
	'step': stategroup (
		'optional value' { [ get ]
			/* Retrieve the value of an optional value.
			 * This fails when the optional value is not set.
			 */
		}
		'entry lookup' {
			/* Retrieve the value of an entry in a collection.
			 * This fails when the provided key does not exist in the set.
			 */
			'key': [ [, ] ] component 'expression'
		}
		'file fetch' {
			/* Retrieve a property from a file.
			 */
			'field': stategroup (
				'token' { [ .token ] }
				'extension' { [ .extension ] }
			)
		}
		'node fetch' {
			/* Retrieve a property of a node.
			 * Optionally an attribute from the property can be retrieved.
			 */
			'property': [ . ] reference
			'sub property': stategroup (
				'yes' {
					'attribute': [ <, > ] reference
				}
				'no' { }
			)
		}
		'choice cast' {
			/* Retrieve the value of a specific state.
			 * This fails when the choice is set to another state.
			 */
			'state': [ ? ] reference
		}
		'plural size' { [ .size ]
			/* Retrieve the size of a set.
			 */
		}
	)
}
'expression tail' {
	'has step': stategroup (
		'yes' {
			'step': component 'expression tail step'
			'tail': component 'expression tail'
		}
		'no' { }
	)
}

Expression

An expression consists of an operation optionally followed by path steps. No all operations guarantee their result and can fail, indicated by the expression guarantee. For operations that can fail, either an alternative can be provided or the failure can be propagated to the parent context.

'expression' {
	'operation': stategroup (
		'variable' {
			/* Retrieves a variable from the instance-data.
			 */
			'variable': [ var ] reference
		}
		'static boolean' {
			/* Create a static boolean value.
			 */
			'value': stategroup (
				'true' { [ true ] }
				'false' { [ false ] }
			)
		}
		'static integer' {
			'type': stategroup (
				'integer' {
					/* Create a static integer value.
					 */
					'value': integer
				}
				'current time' { [ now ]
					/* Retrieves the current time.
					 * The time indicates when the current routine was started and does not change during the execution of the routine.
					 * This is the time as known by the hosting server.
					 */
				}
			)
		}
		'static text' {
			'type': stategroup (
				'text' {
					/* Create a static text value.
					 */
					'value': text
				}
				'line break' { [ line-break ]
					/* Create a static text value containing the line break value.
					 */
				}
				'guid' { [ guid ]
					/* Generate a new GUID.
					 * This GUID is a Version 4 UUID.
					 * The random data is obtained from a CSPRNG.
					 */
					'format': stategroup (
						'canonical' {
							/* The canonical textual representation of 8-4-4-4-12 groups of hexadecimal digits.
							 */
						}
						'base 16' { [ base16 ]
							/* Format as a big-endian base 16 encoded text.
							 */
						}
						'base 64' { [ base64 ]
							/* Format as a big-endian base 64 encoded text.
							 */
						}
					)
				}
			)
		}
		'static choice' {
			/* Select an option.
			 */
			'option': [ option ] reference
		}
		'function' { [ function ]
			/* Create a function.
			 */
			'signature': stategroup (
				'explicit' {
					'signature': component 'signature'
					'instance': component 'callable instance'
				}
				'inferred' { }
			)
			'implementation': [ => ] component 'function implementation'
		}
		'none' { [ none ]
			/* Returns none.
			 * This will convert to an empty set for plural values.
			 * This will convert to unset for optional values.
			 */
		}
		'stored' {
			/* Retrieve the value of a `let` expression.
			 */
			'selection': component 'stack selector'
		}
		'context' { [ $ ]
			/* Retrieve the current context (`$`).
			 */
		}
		'captured error' { [ error ]
			/* Retrieve the captured error.
			 * This is only valid inside a `catch` statement.
			 */
		}
		'entry key' { [ key ]
			/* Retrieve the key of the current entry.
			 * This is only valid inside a `walk` statement.
			 */
		}
		'parse' { [ parse ]
			'value': component 'expression'
			'as': [ as ] stategroup (
				'JSON' { [ JSON ]
					/* Parses JSON.
					 * On success it results in a document and must first be passed to a decorator before it is usable.
					 */
					'schema': [ <, > ] component 'schema instance'
				}
				'XML' { [ XML ]
					/* Parses a XML document.
					 * On success it results in a document and must first be passed to a decorator before it is usable.
					 */
					'schema': [ <, > ] component 'schema instance'
				}
				'CSV' { [ CSV ]
					/* Parses a CSV document.
					 * The CSV parser conforms to RFC 4180.
					 * Optionally the cell separator can be changed.
					 * On success it results in a document and must first be passed to a decorator before it is usable.
					 */
					'separator': stategroup (
						'custom' { [ separator: ]
							'value': [ (, ) ] component 'expression'
						}
						'default' { }
					)
					'schema': [ <, > ] component 'schema instance'
				}
				'choice' { [ choice ]
					/* Parses text to a choice.
					*/
					'schema': [ <, > ] component 'schema instance'
				}
				'ISO Date Time' { [ ISODateTime ]
					/* Parses an ISO-8601 date and time, however it must have at least Seconds accuracy. (source: https://en.wikipedia.org/wiki/ISO_8601)
					 * Combines an ISODate and ISOTime separated with T, but the Date and Time components must both be in the same format.
					 *  Calendar dates + Time: YYYYMMDDThhmmss[.sss] or YYYY-MM-DDThh:mm:ss[.sss]
					 *  Week dates + Time: YYYYWwwDThhmmss[.sss] or YYYY-Www-DThh:mm:ss[.sss]
					 *  Ordinal dates + Time: YYYYDDDThhmmss[.sss] or YYYY-DDDThh:mm:ss[.sss]
					 * In all cases the Time component may contain a Time Zone, when omitted local time is assumed.
					 * On success it results in an Alan DateTime.
					 */
				}
				'ISO Date' { [ ISODate ]
					/* Parses an ISO-8601 date, however it must have Day accuracy. (source: https://en.wikipedia.org/wiki/ISO_8601)
					 *  Calendar dates: YYYYMMDD or YYYY-MM-DD
					 *  Week dates: YYYYWwwD or YYYY-Www-D
					 *  Ordinal dates: YYYYDDD or YYYY-DDD
					 * On success it results in an Alan Date.
					 */
				}
				'ISO Time' { [ ISOTime ]
					/* Parses an ISO-8601 time, however it must have at least Seconds accuracy. (source: https://en.wikipedia.org/wiki/ISO_8601)
					 *  Time: hhmmss[.sss] or hh:mm:ss[.sss]
					 *   The fraction separated by a decimal point is allowed but discarded.
					 *  Time Zone: Z or ±hh or ±hhmm or ±hh:mm
					 *   The timezone is allowed but discarded.
					 * On success it results in the amount of seconds since midnight.
					 */
				}
				'integer' { [ integer ]
					/* Parses an integer value in base-10.
					 *  Integer: [-]d
					 *   Any number of digits can be provided, but the supported range is limited by the implementation.
					 *   An optional minus sign is allowed before the first digit to set the sign.
					 */
				}
				'decimal' { [ decimal ]
					/* Parses a decimal value according to a specific locale.
					 *  The format is locale dependent.
					 */
					'rule': component 'decimal import rule'
					'locale': [ locale: ] text
				}
				'pattern' { [ pattern ]
					/* Parses a custom pattern.
					 * On success it results in a node with all properties set to their respective captured value.
					 */
					'pattern': stategroup (
						'library' {
							'selector': component 'library selector'
						}
						'inline' {
							'rule': component 'pattern rule'
						}
					)
				}
			)
		}
		'serialize' { [ serialize ]
			'value': component 'expression'
			'as': [ as ] stategroup (
				'JSON' { [ JSON ] }
				'XML' { [ XML ] }
				'ISO Date Time' { [ ISODateTime ] }
				'ISO Date' { [ ISODate ] }
				'ISO Time' { [ ISOTime ] }
				'integer' { [ integer ] }
				'decimal' { [ decimal ]
					'integer digits': stategroup (
						'custom' {
							'digits': [, . ] integer
						}
						'default' { }
					)
					'rule': component 'decimal import rule'
					'locale': [ locale: ] text
				}
			)
		}
		'compare' {
			/* Compares two values.
			 * Both values must be of the same type and support the comparison.
			 */
			'type': stategroup (
				'equality' { [ is ] }
				'relational' {
					'operator': stategroup (
						'smaller' { [ less-than ] }
						'smaller equal' { [ less-than or is ] }
						'greater' { [ greater-than ] }
						'greater equal' { [ greater-than or is ] }
					)
				}
			)
			'comparator': component 'comparator'
			'values': [ (, ) ] group {
				'left': component 'expression'
				'right': [ , ] component 'expression'
			}
		}
		'arithmetic' {
			'operation': stategroup (
				'inversion' { [ - ]
					/* Invert the sign of an integer.
					 */
					'operand': [ (, ) ] component 'expression'
				}
				'sum' { [ sum ]
					/* Calculates the sum of a set of integers.
					 * It results in the sum of all values, or the additive identity when the set is empty.
					 */
					'values': [ (, ) ] component 'plural expression'
				}
				'product' { [ product ]
					/* Calculates the product of a set of integers.
					 * It results in the produce of all values, or the multiplicative identity when the set is empty.
					 */
					'values': [ (, ) ] component 'plural expression'
				}
				'division' { [ division (, ) ]
					/* Divide one integer by another integer.
					 * The resulting fraction, if any, is truncated.
					 * This fails when `denominator` is zero.
					 */
					'numerator': component 'expression'
					'denominator': [ , ] component 'expression'
				}
			)
		}
		'concatenation' { [ concat ]
			/* Concatenates a set of texts.
			 * Optionally a separator can be added between each element.
			 * The entries are joined in the order of the set.
			 */
			'values': [ (, ) ] component 'plural expression'
			'separator': stategroup (
				'yes' { [ separator: ]
					'value': [ (, ) ] component 'expression'
				}
				'no' { }
			)
		}
		'logic' {
			'operation': stategroup (
				'negation' { [ not ]
					/* Toggle the value of a boolean.
					 */
					'operand': [ (, ) ] component 'expression'
				}
				'and' { [ and (, ) ]
					/* Calculates the logical and of a set of booleans.
					 * It fails when the set is empty.
					 */
					'values': component 'plural expression'
				}
				'or' { [ or (, ) ]
					/* Calculates the logical or of a set of booleans.
					 * It fails when the set is empty.
					 */
					'values': component 'plural expression'
				}
			)
		}
		'reduce' { [ reduce ]
			/* Merge a set of values.
			 */
			'values': [ (, ) ] component 'plural expression'
			'to': [ to ] stategroup (
				'shared' { [ shared value ]
					/* Reduces a set of values to a single shared value.
					 * Shared values are detected with an equality check.
					 * It succeeds only when all values pass an equality check and the set contains at least one value.
					 */
					'comparator': component 'comparator'
				}
				'unique' { [ unique values ]
					/* Removes all duplicate values from a set of values.
					 * Duplicates are detected with an equality check.
					 * The order of the set is maintained, duplicates are placed at the first occurrence.
					 */
					'comparator': component 'comparator'
				}
			)
		}
		'partition' { [ partition ]
			/* Partition a set.
			 * It groups all entries with the `key` together in a bucket.
			 * The result is a set of key-value pairs, where the key is the shared `key` and the value is a set of the entries with that `key`.
			 * The order of buckets is undefined.
			 * The order of entries within a bucket is maintained.
			 */
			'value': [ (, ) ] component 'expression'
			'comparator': [ on ] component 'comparator'
			'key': [ (, ) ] component 'expression tail'
		}
		'file constructor' { [ (, ) ]
			/* Construct a file object.
			 */
			'token': [ token = ] component 'expression'
			'extension': [ extension = ] component 'expression'
		}
		'call' {
			/* Call a function.
			 */
			'selection': [ call ] component 'callable selector'
			'arguments': component 'function call'
		}
	)
	'tail': component 'expression tail'
	'alternative': component 'expression alternative'
}
'expression alternative' {
	'alternative': stategroup (
		'yes' {
			'type': [ || ] stategroup (
				'value' {
					/* Provide an alternative expression. */
					'value': component 'expression'
				}
				'throw' {
					/* Throw a new error. */
					'message': [ throw ] text
				}
			)
		}
		'no' { }
	)
}
'plural expression' {
	'value': component 'expression'
	'is plural': stategroup (
		'yes' { [ * ]
			'tail': component 'expression tail'
		}
		'no' { }
	)
	'has tail': stategroup (
		'yes' {
			'tail': [ , ] component 'plural expression'
		}
		'no' { }
	)
}

Safe Expression

This represents a top-level expression. A safe expression indicates that an expression guarantee of no cannot be propagated from the wrapped expression. The wrapped expression must always have an alternative with a guarantee of yes, or throw.

'safe expression' {
	'expression': component 'expression'
}
'target expression' {
	'type': stategroup (
		'explicit set' { [ set ]
			'statement': component 'statement'
		}
		'set' {
			'type': stategroup (
				'node' {
					/* Target a node.
					 * All mandatory properties must be specified.
					 * Optional properties may be omitted, which is identical to setting them to `unset`.
					 * When a property has attributes, all attributes must also be specified.
					 */
					'properties': [ (, ) ] dictionary {
						'has previous': stategroup = node-switch predecessor (
							| node = 'yes' { 'previous' = predecessor }
							| none = 'no'
						)
						'attributes': stategroup (
							'yes' {
								'attributes': [ <, > ] dictionary {
									'value': [ = ] component 'target expression'
								}
							}
							'no' { }
						)
						'obscure data': stategroup (
							'inherit' { }
							'exclude' { [ @exclude ] }
						)
						'statement': [ = ] component 'statement'
					}
					'has properties': stategroup = node-switch .'properties' (
						| nodes = 'yes' { 'last' = last }
						| none  = 'no'
					)
				}
				'entry' { [ create ]
					/* Create a new set with a single entry.
					 * When the set has implicit keys, a key must be provided.
					 */
					'implicit key': stategroup (
						'yes' {
							'value': [ [, ] ] component 'safe expression'
						}
						'no' { }
					)
					'target': component 'target expression'
				}
				'state' { [ create ]
					/* Set a choice to the specified option.
					 */
					'state': reference
					'target': component 'target expression'
				}
			)
		}
		'inferred' {
			/* Assign any value from an expression.
			*/
			'value': component 'safe expression'
		}
	)
}

Statement

A statement is responsible for the control flow of the processor. All statements produce a value that is return to the parent context.

Each statement is bound to an execution context. The execution context can be restricted when the statement is not allowed to fail. When the execution context is restricted, operations that (could) generate errors are disabled.

'statement' {
	'type': stategroup (
		'block' {
			'block': component 'block statement'
		}
		'guard' {
			/* Guard a statement and provide an alternative path.
			 * The execution context of the guarded statement is always free, the fallback statement inherits the current execution context.
			 * This catches all throws found in the guarded statement, including implicit throws generated by the runtime.
			 * When the guarded statement is successfully executed, the fallback statement is skipped.
			 */
			'guarded statement': [ try ] component 'statement'
			'optional assignment': [ catch ] stategroup (
				'yes' { [ as $ ] }
				'no' { }
			)
			'fallback statement': [ => ] component 'statement'
		}
		'switch' {
			'value': [ switch ] component 'expression'
			'type': stategroup (
				'boolean' { [ (, ) ]
					/* Execute a conditional branch.
					 * The condition is evaluated and based on the result, either the `true` or `false` case is executed.
					 */
					'on true': [ | true => ] component 'statement'
					'on false': [ | false => ] component 'statement'
				}
				'existence' { [ (, ) ]
					/* Execute a conditional branch.
					 * The expression without a guarantee of yes is evaluated.
					 * Based on whether or not the expression succeeded or failed, the corresponding case is executed.
					 * When the expression succeeds, the result is available in `$`.
					 */
					'on value': [ | value as $ => ] component 'statement'
					'on none': [ | none => ] component 'statement'
				}
				'choice' {
					/* Execute a conditional branch.
					 * The branch is determined based on a user defined choice.
					 * The value of the option is optionally available in `$`.
					 */
					'cases': [ (, ) ] dictionary { [ | ]
						'has next': stategroup = node-switch successor (
							| node = 'yes' { 'next' = successor }
							| none = 'no'
						)
						'optional assignment': stategroup (
							'yes' { [ as $ ] }
							'no' { }
						)
						'statement': [ => ] component 'statement'
					}
				}
			)
		}
		'walk' {
			'type': [ walk ] stategroup (
				'plural' {
					/* Execute a statement once for each entry in a set.
					 * The entry for which the statement is executed, is available in `$`.
					 */
					'value': [, as $ ] component 'safe expression'
					'statement': [ => ] component 'statement'
				}
				'range' {
					/* Execute a statement for a range of integers.
					 */
					'range': group { [ (, ) ]
						'begin': [ from ] component 'safe expression'
						'end': [ until ] component 'safe expression'
						'step': [ with ] component 'safe expression'
					}
					'statement': [ => ] component 'statement'
				}
			)
			/* The result is determined by the following:
			 *  - When the sub-statement results in a plural type, the results are merged into a single set.
			 *  - Otherwise a new list of the result type is created.
			 */
			'merge': component 'hook'
		}
		'log' {
			/* Write a message to the selected channel.
			 */
			'channel': stategroup (
				'functional error' { [ @error: ] }
				'information' { [ @log: ] }
			)
			'message': component 'safe expression'
		}
		'throw' {
			/* Throw a error.
			 * This can either be a new error, or when inside a `catch` statement it can rethrow the caught error.
			 */
			'type': stategroup (
				'captured error' { [ rethrow ] }
				'new error' { [ throw ]
					'message': text
				}
			)
		}
		'fail' { [ fail ]
			/* Completes a function without yielding a value.
			 */
		}
		'produce' {
			'type': stategroup (
				'explicit' {
					'path': [ (, ) ] component 'type path'
				}
				'inferred' { }
			)
			'target': component 'target expression'
		}
		'execute' {
			/* Execute a Alan Interface command or event.
			 */
			'context': [ execute ] component 'safe expression'
			'type': stategroup (
				'command' {
					'command': [ command ] reference
				}
				'event' {
					'event': [ event ] reference
				}
			)
			'statement': [ with ] component 'statement'
		}
	)
}
'statements' {
	'result': stategroup (
		'discard' { [ do ]
			/* Discard the result of the statement.
			 * The statement is evaluated pure for side-effects.
			 * The result does not participate in the result of the entire sequence.
			 */
		}
		'return' { }
	)
	'statement': component 'statement'
	'has more statements': stategroup (
		'yes' {
			'statement': component 'statements'
		}
		'no' { }
	)
}
'block statement' { [ {, } ]
	/* Start a new block.
	 * Optionally a block can introduce a new scope.
	 * A block allows multiple independent statements to be written in the same context.
	 * The result of the sequence of statements is determined by the following rules:
	 *  - Statements with a result of none, do not participate in the result of the entire sequence.
	 *  - When no statements participate, the result of the sequence is none.
	 *  - When only single statement participates, the result of the sequence is the result of that statement.
	 *  - When multiple statement participate, all must be of the same plural type and the individual values are merged, the sequence result is the result of the merge.
	 */
	'scope': stategroup (
		'yes' {
			'stack block': component 'stack block'
		}
		'no' { }
	)
	'statement': component 'statements'
}