processor
connector grammar version 35.2
model | 105 | application |
---|---|---|
interface | 20 | interface |
auto-webclient | xindi.1 | generator_settings |
generator_annotations | ||
phrases | ||
query | ||
translations | ||
parameters | ||
connector | 36.5 | processor |
variables | ||
datastore | 113 | consumed_interfaces_mapping |
provided_interface_implementation | ||
migration_mapping | ||
regular_expression_engine | ||
relational-database-bridge | 109 | database |
database_transformation | ||
sql-mirror | 109.1 | sql_mapping |
webclient | xindi.1.1 | views |
widget | ||
client bindings | ||
gui_model | ||
phrases | ||
translations | ||
settings | ||
parameters | ||
project-build-environment | 41 | wiring |
deployment |
- Changes
- The Standard Libraries
- Processor
- Examples
Changes
List of all major releases of the connector.
35.2
Fixed side-effect propagation
Fixed language issue to allow command/event execution nested statements.
35.1
Removed dataenv
The dataenv
directory is removed, all tools and scripts are packaged in the devenv
.
35.0
Changed locale handling
Locales are always represented as a text, example en_US
.
Removed message parse
The parse
function is removed.
The parse as pattern
instruction is still available and has improved locale support for decimal parsing.
Relaxed let
type constraint
The explicit type of a let
(let $'...' as <type> = ...
) is no longer constraint to just the node type.
Changed plural type
Plural type are always indexed, allowing look ups in lists.
The entity type is removed, instead the key can be obtained from the special value key
in a walk
.
Relaxed serialize as JSON
type constraint
The JSON serializer accepts all types.
Calendar constructor
Added a constructor to the calendar library with support for timezones.
Several small changes
- Allow all statements after entry/state creation.
- All conditional statements use the
switch
keyword. - Reintroduced the
list
keyword for the list constructor. - Added the
separator:
keyword to thejoin
separator to clarify its usage.
34.1
CSV separator option
Added an option to set a custom separator for the CSV parser.
34.0
Removed deprecated functionality
All functionality marked deprecated in version 33 is now removed.
Execution safety
Execution safety controls the available error handling features. This is to prevent routine aborting on uncaught errors where the runtine is not able to handle these errors. Currently only the context-key initializer of consumers and the new error handler use these restrictions.
Error Handler
The connector no longer consumes a message connection on which uncaught errors are reported. Instead the optional error handler runs for each error report.
Text and binary types
Text type is only for actual text only. For binary data, the binary type can be used.
Collection binding
Routines in a consumer can bind to a collection.
When the routine is executed, the context ($
) is set to all touched entries.
Routines can also bind to a collection entry deletion.
When the routine is executed, the context ($
) is set to key of the delete entry.
Text padding
Added function to left/right pad a text with spaces.
33.12
Text encoding conversions
Added support for text encoding conversions.
33.11
Plural split
Added support to split a plural value into a set of plural values of a max length.
Debug pause
Added function to temporarily pause execution.
Improved responsiveness
The runtime now reacts to external events (primarily the shutdown signal) while the VM is active. The interface link sends outgoing messages while the VM is active.
33.10
Verbose
Added curl verbose logging to runtime configuration options. Allowing changing this behavior without use of the debugger.
33.9
Runtime Configuration
Added support to fetch values from the runtime configuration.
Unicode Message Format
Added support for formatting template text.
This deprecates the format
instruction.
Added support for parsing template text.
33.8
CSV support
Added support for CSV documents.
A CSV document must be either a list headless
or a table
, the node must contain only text
or choice
properties.
schema scalar limits
Added option to limit the minimum length of a text. Added option to limit the range of an integer.
add pattern bounds
Pattern matcher supports optional lower and upper bounds.
regular expressions
Added the regex
function to the standard library unicode
.
SMTP address validation
Added validation to address passed to SMTP calls.
strip whitespace
Added the strip
function to the standard library unicode
.
33.7
text length limit
Added option to limit the length of a text.
33.6
guid format
Added to different formats to the guid
instruction.
The new formats encode the 128 bits as an big-endian integer.
base16
base64
33.5
boolean negation
Added support to toggle boolean values.
provider initialization
Added an option to initialize a provider. Initialization can be done with a custom routine or an additional run of the main routine.
network message headers
Added headers to network messages.
network message recipients extension
Added additional recipient fields to network messages.
33.4
Workaround for plural entity T
types in type paths.
33.3
command
/event
path steps
Target path steps and type path steps can step through interface commands and events.
33.2
union
type
union
types work like the any
type, but require that all possible types are listed at it definition.
In addition, unlike the any
type, union
types are valid targets when creating data.
Standard Library plural
The select
and filter
reduce operations are now available as standard library.
The sort
operations is added to the plural
standard library.
These use lambdas as parameters.
Standard Library unicode
The line parser (parse as lines
) is now available in the unicode
standard library.
The trim options are now available as separate operation in the unicode
standard library.
List constructor
The list constructor allows for the creation of dynamic lists (plural values) for a static list of promises.
Data protection
Properties in schema node types can be marked as @protected
.
The data of a protected property is always excluded from error reports.
Small changes
any
type is now deprecated, useunion
types instead. It will be removed in the next version.- list merge operations are now deprecated, use list constructor and reduce instead. These will be removed in the next version.
select
andfilter
reduce operations are now deprecated, use standard library instead. It will be removed in the next version.parse as lines
is now deprecated, use standard library instead. It will be removed in the next version.- Additional usage analysis added.
33.1
Extended Interface command handling
A providing connector can provide command handlers for any command in the interface.
The command routine can access all interface data through a interface named path
, identical to consumer routines.
Interface event generation
A providing connector can now generate interface events.
Standard Library hooks
Standard libraries can define hooks. These hooks can be trigger VM runs from external events.
Builtin WebServer
The builtin webserver functionality is returned (WebHooks). In addition to the return of the previous functionality, which allowed consumers to respond to HTTP requests, providers can now also use this feature. Where consumers can generate commands from these hooks, providers can generate events.
QUIRK: The new implementation can not access the functional error channel.
When the hook execution is aborted by a throw
, the error report is instead send as the body of HTTP response with a corresponding 500 error.
To prevent this, hooks should always catch any and all throws
and generate a proper error response on their own.
CAVEAT: No design- or runtime-validation is performed on the hook handler names! Registering handlers with names not valid HTTP paths (without protocol and host), results in the handlers never trigger. Registering handlers with duplicates names, valid or not, causes one handler to overrule all others. Which handler ‘wins’ is undefined.
Fixes
- The language properly constraints when command and event generation is allowed.
- Standard library symbol binding validates signature at load time, to prevent runtime crashes/failures when standard libraries signatures are changed.
33
Standard Libraries
Introduced standard libraries. These allow the interface of part of the connector functionality to be provided in the connector language.
Standard libraries introduced:
- All network instructions and types. See the
stdlib.network.*
examples. - Calendar conversions and type. See example.
Webhooks removed
Webhooks are now broken as a result of moving part of the connector functionality to standard libraries. The functionality is removed from this version, to be reintroduced in a later version.
Explicit unset
for optional
Properties marked as optional
can be left unset by omitting them from the target-node statement, example.
These properties can now also be explicitly left unset with the unset
keyword, example.
This allows conditionality before deciding whether or not an optional
property should be assigned a value or not.
Create data in lambda arguments
When calling a lambda or function with a complex type as parameter, data can be created in the arguments with the new
keyword.
In addition, parameters can also define new types.
table
schema type
The new table
type is a special type equal to list headless { ... }
, expect it expects the first record in the list to contain the property order of the headless
nodes.
This always the order of the headless
node to be dynamic at runtime when parsing data.
When serializing a header record is produced, but the connector retains its strict ordering of properties.
Note: the current list
and headless
XML conventions do not function properly when combined, as a result the XML decorator is unable to handle the table
type.
Several small changes
- Lambda call stack was removed, lambdas can only call themselves with
self
, example. - The
count
instruction is replaced with the.size
property onplural
values, example. - Reduce operations can no longer apply filters expect for the
filter
operation, which can only apply a single filter, example. - Added documentation to the processor grammar.
32.5
Added select
to reduce
instruction
With select
a single entry can be selected in a plural value.
It selects entry A
for which compare(A, B) && !compare(B, A)
is true for all other entries in the plural value.
32.4
Added support for attribute initialization
This allows initialization of attributes in a let
expression.
The constraint added in 32.3
is removed.
32.3
Added constraint to let
schema initializers
In the following construction, <type>
can no longer contain attributes.
let $'data' as <type> = <schema-initializer>
Remove <root>
element from XML serialization
When serializing data to XML, the implementation no longer writes the <root>
top level element.
This mirrors the behavior of the XML decorator.
32.2
Platform compatibility release of version 31.4.
32.1
Platform compatibility release of version 31.3.
32
Platform compatibility release of version 31.
31.4
Enabled optional
in headless
nodes
optional
properties are allowed in headless
nodes with some limitations.
All properties, optional
or not, must be present in the data, but optional
properties are allowed to have no value.
For JSON this catches null
values.
For XML this catches empty elements.
31.3
HTTPS response headers
The headers on a network type are now always available.
This allows inspecting headers from a HTTPS
instruction.
MIME support
Extended the support for generating MIME messages.
This removes some quirks, such as requiring angular brackets (<...>
) around email addresses, to be resolved.
The MIME type and sub-type of attachments must now be set.
MIME message can now be parsed. The parser extracts the following information:
- the subject as
optional text
- the from as
plural entity text
, the entity key is the display name and the value is the email address - the recipients as
plural entity text
, the entity key is the display name and the value is the email address, this includes all recipients of the message, the type (TO/CC/BCC) is discarded - the attachment as
plural entity text
, the entity key is the filename and the value is the decoded attachment content - the headers as
plural entity text
, the entity key is the header name and the value is the header value - the content as
text
When parsing a MIME message, the content is extracted from the tree.
For this the parser requires a preferred MIME sub-type to be provided, the type must always be text
. A value *
is allowed and matches any sub-type.
The parser supports the following MIME parts:
- attachments, these can be of any MIME type
multipart/mixed
, each sub-part is processed for attachments and the body, exactly one sub-part must be the body (or othermultipart
providing the body)multipart/related
, these are processed asmultipart/mixed
, any sub-parts related to the body are discardedmultipart/alternative
, each sub-part is processed in order but only the last sub-part (the most representative) acceptable as body is usedtext/<prefer>
, a part of MIME typetext
with a sub-type matching the preferred sub-type is accepted as bodytext/plain
, these are always accepted as body, as fallback when no alternativetext/<prefer>
part is present
While attachment of any MIME type are accepted, the decoded content of the attachment is always put in a text
type.
This will cause issue when the attachment is not text as binary data is currently not supported.
31.2
Integrated Webserver
In consumer
mode, the integrated webserver is enabled.
To use the webserver, a routine
can bind on webhook
, using its name as the path.
For this reason the name of the routine
is required to be a valid path, no design time validation is performed.
The behavior of webhooks is as follows:
- When the connector’s interface connection is down, it always replies with a “503 Service Unavailable”.
- When the
routine
fails, because of an uncaughtthrow
, it replies with a “500 Internal Server Error”. - When the
routine
succeeds, it replies with the produced response.
Statements in singular cardinality
It is now allowed to write a block with multiple statements in a context with a singular cardinality. When this is used, the last statement is the statement that binds to the target, while all preceding statements in the block bind to no target.
SMTP with attachments
Attachments can added to SMTP messages.
Changed internal handling of XML CDATA
The handling of text/CDATA nodes in XML documents is changed.
This causes multiple CDATA (and text) nodes to be considered as a single text node, allowing it to pass a decorate
as text
check and produce the correct value.
31.1
Reusable types
Reusable types can now be defined at the top of a processor.
Reusable types can be schema complex types
:
Reusable types can be patterns
:
Reusable types can be locale
, replacing the previous locale implementation.
Extended lambda
parameter types
lambda
now supports all valid types expect !'valid type'::'document'
.
For schema types, only reusable types are supported.
Changed result of the PUT
method for FTPS
instructions
PUT
now always result in the value true
, like the DELETE
method.
Added examples
All network oriented instructions, HTTPS
and FTPS
, are not tested by the test-set.
This instructions require a server to function.
For these instructions examples are provided instead of a test case.
VM Memory Manager
The VM implementation of the connector to use a refcounted memory manager instead of the stack based approach used in previous versions. This should case temporary values to cleaned up as soon as the are no longer needed, reducing peak memory usage. References can now also out live the scope that created them, preparing the implementation for lambda/functions with return values.
SMTP(s) support
Added the SMTPS
instruction. It expects to operate on a text containing the email content.
The headers and MIME part of the email are handled through additional fields and options.
Supported options:
subject:
[required]: the subject of the email, as textfrom:
[required]: the sender, as text; note: the email address must be wrapped in<...>
recipients:
[one required]: the recipient(s), eitherTO:
orCC:
followed by a text; note: the email address must be wrapped in<...>
authentication
[optional]: the username and password required to access the server@mime-type:
[optional]: the MIME type of content as text; it istext/plain
otherwise@allow-insecure
[optional]: to allow insecure connections to the server
IMAP(s) support
Added the IMAPS
instruction. It supports the GET
and SEARCH
methods.
Both the imap://
and the imaps://
protocols are supported, with imap://
it will require an upgrade to SSL/TLS unless the @allow-insecure
option is provided.
XML and CDATA
The XML decorator now accepts CDATA nodes. CDATA is implicitly converted to text.
now
instruction
The now
instruction produces the current date and time as a calendar object.
For the duration of a routine, the value produced by now
does not change and corresponds to the time the routine was started.
It accepts an optional timezone argument, otherwise the result is UTC.
Changed behavior of optional
Properties marked as optional
are no longer handled as a catch all for decorate error in child types.
For JSON optional
only catches non-existing object keys and object keys with a value of null
.
For XML optional
only catches non-existing elements and elements with no child nodes.
In addition to the behavior changes of the implementation, the language no longer allows optional
properties in headless
nodes.
31
FTP(S) delete
Unlike other FTPS
instructions, DELETE
send the path ‘as is’ to the server for interpretation while the other methods have their path interpreted by the ftp client.
This might lead to different behavior on different servers.
changed URI handling
The URI for both HTTPS and FTPS instructions is changed, the server part (protocol, host and optionally port) in expected separately from the path:
"/path/to/resource" => HTTPS ( "https://localhost:8080" ) GET ...
"/path/to/resource" => FTPS ( "ftps://localhost:9090" ) GET ...
removed HTTP mock server from tester
The HTTP mock server and related configuration files are removed from the tester. Removed usage of HTTP from all tests.
debugger with tab-completion and hints
The debugger has tab-completion at every level of instructions.
When only one completion remains, it is shown as a hint.
When the current context is invalid for the command, it is shown in a hint before running the command.
Use help
to display all available commands.
FTP(S) support
Added the FTPS
instruction. It supports the GET
, HEAD
, LIST
and PUT
methods.
The result is the file content as text.
Both the ftp://
and the ftps://
protocols are supported, with ftp://
it will require an upgrade to SSL/TLS unless the @allow-insecure
option is provided.
HTTPS/FTPS authentication
Both HTTPS
and FTPS
instructions support the authentication
option, allowing username password authentication.
For HTTPS
this enables the Basic, Digest and NTLM schemes.
Bug Fixes
- Fixed several instances where nested promises with a
throw
instruction caused memory leaks.
30
lambda
parameter can be another lambda
lambda
captures its context
Darwin and Windows ports.
The tester is now available in the dataenv package on all platforms.
A simple debugger is available in the tester allowing stepping through a processor.alan
one statement at a time, with support to inspect the VM state.
Added format
instruction with different types of padding and alignment.
XML support for serialize as ...
.
- NOTE: XML has no native support for numbers, the serializer converts these values on the fly. It uses decimal point translation where available.
- CAVEAT: interface and schema types have no locale information, as a result the serializer cannot respect the locale when converting numbers.
Added unique
instruction to reduce.
The unique
instruction removes all duplicates from a plural value, creating a new plural value where all values are unique within the set.
Added partition
instruction.
The partition
instruction divides a plural values into multiple buckets based on a shared value, creating a new plural value of entities with the entity value a plural value with all records which share the key.
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' as @API choice ( 'date' 'date-time' 'time' )
define 'weekday' as @API choice (
'Monday': 1
'Tuesday': 2
'Wednesday': 3
'Thursday': 4
'Friday': 5
'Saturday': 6
'Sunday': 7
)
define 'calendar' as @API {
'year': integer
'day of year': integer
'month': integer
'day of month': integer
'week': integer
'day of week': 'weekday'
'hour': integer
'minute': integer
'second': integer
}
define 'constructor' as @API {
'date': union (
'calendar' {
'year': integer
'month': integer @limit: { 1 , 12 }
'day': integer @limit: { 1 , 31 }
}
'week' {
'year': integer
'week': integer @limit: { 1 , 53 }
'day': 'weekday'
}
'ordinal' {
'year': integer
'day': integer @limit: { 1 , 366 }
}
)
'time': optional {
'hour': integer @limit: { 0 , 23 }
'minute': integer @limit: { 0 , 59 }
'second': integer @limit: { 0 , 59 }
}
}
library
/* 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.
*/
function 'convert'
< integer , 'calendar' >
(
$'source type': 'type'
$'timezone': optional text
)
binds: "a0be018eab9c5cfd1074a547f80c1cec4f85c21f"
/* 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.
*/
function 'construct'
< 'constructor', integer >
(
$'timezone': optional text
)
binds: "41f9e69859eefe632cc0a0777779d632eca606ff"
Calendar Examples
consumer ( )
routine 'test' on
do {
let $'source' as 'calendar'/'constructor' = (
'date' = create 'calendar' (
'year' = 2021
'month' = 2
'day' = 3
)
'time' = (
'hour' = 10
'minute' = 20
'second' = 30
)
)
let $'value' = $'source' => call 'calendar'::'construct' with ( $'timezone' = unset )
switch $'value' => is ( 212479064430 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'source' as 'calendar'/'constructor' = (
'date' = create 'calendar' (
'year' = 2021
'month' = 8
'day' = 4
)
'time' = (
'hour' = 10
'minute' = 20
'second' = 30
)
)
let $'value' = $'source' => call 'calendar'::'construct' with ( $'timezone' = unset )
switch $'value' => is ( 212494789230 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
Network
The network library provides functions to perform network request and the data structures to represent related objects.
define 'network method' as @API choice ( 'get' 'head' 'post' 'put' 'delete' )
define 'network security' as @API choice ( 'strict' 'preferred' 'none' )
define 'key value list' as @API collection case folding text
define 'network request' as @API {
'method': 'network method'
'parameters': optional 'key value list'
'headers': optional 'key value list'
'content': optional binary
}
define 'network response' as @API {
'status': integer
'headers': 'key value list'
'content': binary
}
define 'network authentication' as @API {
'username': text
'password': text
}
define 'network message part' as @API {
'mime type': text
'mime sub type': text
'content': binary
}
define 'network message' as @API {
'from': 'key value list'
'recipients': 'key value list'
'recipients extra': optional 'key value list'
'recipients hidden': optional 'key value list'
'subject': optional text
'headers': optional 'key value list'
'attachments': optional list {
'name': text
'part': 'network message part'
}
'body': 'network message part'
}
library
/* Parses and decorates text as 'network message'.
* Optionally a 'preferred mime subtype' of MIME type `text` can be provided.
* When a MIME part with this type is found, it becomes a candidate for the body.
* The MIME type `text/plain` is always considered as a candidate for the body.
*/
function 'parse network message'
< binary , unsafe 'network message' >
(
$'preferred mime subtype': text
)
binds: "8668f36c12865350be8e3134c2e91af9288be118"
/* Performs an HTTP(S) request.
* Methods are mapped directly to their HTTP equivalent.
* When provided, 'content' is send with the request.
*/
function 'http'
< boolean , unsafe 'network response' >
(
$'server': text
$'path': text
$'authentication': optional 'network authentication'
$'request': 'network request'
)
binds: "ce5b533753fae95564a6fc8f68c6b608b15552fe"
/* 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.
*/
function 'ftp'
< boolean , unsafe optional binary >
(
$'server': text
$'path': text
$'method': 'network method'
$'authentication': optional 'network authentication'
$'security': 'network security'
$'content': optional binary
)
binds: "8332f264938126aa6e0050f306f5b534ca24ced6"
/* 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'`.
*/
function 'imap'
< boolean , unsafe list 'network message' >
(
$'server': text
$'path': text
$'criteria': text
$'authentication': optional 'network authentication'
$'security': 'network security'
$'preferred mime subtype': text
)
binds: "5eed4101a885f2ce88026490ff199cb03ceffc86"
/* Send a message with SMTP(S).
* When the transfer fails, this function throws an error.
*/
function 'smtp'
< boolean , unsafe boolean >
(
$'server': text
$'authentication': optional 'network authentication'
$'security': 'network security'
$'message': 'network message'
)
binds: "92657e7ce62f93ac951b0709b118a74334946e7d"
/* 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'
on 'network response'
(
$'request': 'network request'
)
binds: "28d5a0a8bec41be6e4235c0b32ed0bafa13b7c34"
Network Examples
consumer ( )
/* Standard Library `network.lib`::`http` example
* this performs a simple HTTPS GET request and validates the response status
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
let $'response' = true => call 'network'::'http' with (
$'server' = "https://example.com"
$'path' = "/path/to/resource"
$'authentication' = unset
$'request' = new (
'method' = option 'get'
'parameters' = create ["parameter"] "value"
)
) || throw "http request failed"
switch $'response'.'status' => less-than ( 400 ) (
| true => no-op /* from here `$'response'.'content'` contains the resource data as `binary` */
| false => no-op /* from here `$'response'.'content'` contains the error response as `binary` */
)
}
consumer ( )
/* Standard Library `network.lib`::`http` example
* this performs a simple HTTPS PUT request and validates the response status
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
let $'response' = true => call 'network'::'http' with (
$'server' = "https://example.com"
$'path' = "/path/to/resource"
$'authentication' = unset
$'request' = new (
'method' = option 'put'
'headers' = create ["content-type"] "text/plain"
'content' = "hello world" => call 'unicode'::'as binary' with ( )
)
) || throw "http request failed"
switch $'response'.'status' => less-than ( 400 ) (
| true => no-op /* from here `$'response'.'content'` contains the resource data as `binary` */
| false => no-op /* from here `$'response'.'content'` contains the error response as `binary` */
)
}
consumer ( )
/* Standard Library `network.lib`::`http` example
* this performs a simple HTTPS GET request with authentication and validates the response status
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
let $'response' = true => call 'network'::'http' with (
$'server' = "https://example.com"
$'path' = "/path/to/resource"
$'authentication' = set new (
'username' = "username"
'password' = "password"
)
$'request' = new (
'method' = option 'get'
)
) || throw "http request failed"
switch $'response'.'status' => less-than ( 400 ) (
| true => no-op /* from here `$'response'.'content'` contains the resource data as `binary` */
| false => no-op /* from here `$'response'.'content'` contains the error response as `binary` */
)
}
consumer ( )
/* Standard Library `network.lib`::`ftp` example
* this performs a simple FTPS GET request
* note that different versions of security exists for ftp
* > implicit security uses the `ftps://` protocol
* > explicit security uses the `ftp://` protocol and `STARTTLS` command
* by default we fail when the connection cannot be secured, see the example `stdlib.network.ftp.insecure` for an alternative
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
let $'resource' = true => call 'network'::'ftp' with (
$'server' = "ftps://example.com"
$'path' = "/path/to/resource"
$'method' = option 'get'
$'authentication' = unset
$'security' = option 'strict'
$'content' = unset
) || throw "ftp request failed"
switch $'resource' get (
| value as $ => no-op /* from here `$` contains the resource data as `binary` */
| error => no-op
)
}
consumer ( )
/* Standard Library `network.lib`::`ftp` example
* this performs a simple FTPS GET request on a directory and parses the result
* note that different versions of security exists for ftp
* > implicit security uses the `ftps://` protocol
* > explicit security uses the `ftp://` protocol and `STARTTLS` command
* by default we fail when the connection cannot be secured, see the example `stdlib.network.ftp.insecure` for an alternative
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
let $'resource' = true => call 'network'::'ftp' with (
$'server' = "ftps://example.com"
$'path' = "/path/to/resource"
$'method' = option 'head'
$'authentication' = unset
$'security' = option 'strict'
$'content' = unset
) || throw "ftp request failed"
let $'file list' = $'resource' get => call 'unicode'::'import' with ( $'encoding' = "ASCII" ) => call 'unicode'::'split' with ( $'style' = option 'none' ) || throw "ftp response parse error"
walk $'file list' as $ => no-op /* from here `$` contains the file name relative to `$'path'` */
}
consumer ( )
/* Standard Library `network.lib`::`ftp` example
* this performs a simple FTPS PUT request
* note that different versions of security exists for ftp
* > implicit security uses the `ftps://` protocol
* > explicit security uses the `ftp://` protocol and `STARTTLS` command
* by default we fail when the connection cannot be secured, see the example `stdlib.network.ftp.insecure` for an alternative
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
let $'resource' = true => call 'network'::'ftp' with (
$'server' = "ftps://example.com"
$'path' = "/path/to/resource"
$'method' = option 'put'
$'authentication' = unset
$'security' = option 'strict'
$'content' = set "hello world" => call 'unicode'::'as binary' with ( )
) || throw "ftp request failed"
switch $'resource' get (
| value as $ => no-op /* from here `$` contains the resource data as `binary` */
| error => no-op
)
}
consumer ( )
/* Standard Library `network.lib`::`ftp` example
* this performs a simple FTPS DELETE request
* caveat:
* unlike other FTPS request, this sends the path to the server as-is
* as a result the behavior of this instruction can vary between servers
* note that different versions of security exists for ftp
* > implicit security uses the `ftps://` protocol
* > explicit security uses the `ftp://` protocol and `STARTTLS` command
* by default we fail when the connection cannot be secured, see the example `stdlib.network.ftp.insecure` for an alternative
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
let $'resource' = true => call 'network'::'ftp' with (
$'server' = "ftps://example.com"
$'path' = "/path/to/resource"
$'method' = option 'delete'
$'authentication' = unset
$'security' = option 'strict'
$'content' = unset
) || throw "ftp request failed"
switch $'resource' get (
| value as $ => no-op /* from here `$` contains the resource data as `binary` */
| error => no-op
)
}
consumer ( )
/* Standard Library `network.lib`::`ftp` example
* this performs a simple FTPS GET request
* note that different versions of security exists for ftp
* > implicit security uses the `ftps://` protocol
* > explicit security uses the `ftp://` protocol and `STARTTLS` command
* by default we fail when the connection cannot be secured, this example changes this behavior
* > for implicit security this option has no effect
* > for explicit security this option will suppress failures when we cannot secure the connection, it will NOT disable security when it is available
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
let $'resource' = true => call 'network'::'ftp' with (
$'server' = "ftps://example.com"
$'path' = "/path/to/resource"
$'method' = option 'get'
$'authentication' = unset
$'security' = option 'preferred' /*or 'none' to completely disable security*/
$'content' = unset
) || throw "ftp request failed"
switch $'resource' get (
| value as $ => no-op /* from here `$` contains the resource data as `binary` */
| error => no-op
)
}
consumer ( )
/* Standard Library `network.lib`::`imap` example
* this performs a simple IMAPS request to load all new messages
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
let $'messages' = true => call 'network'::'imap' with (
$'server' = "imaps://example.com"
$'path' = "/INBOX/"
$'criteria' = "NEW"
$'authentication' = unset
$'security' = option 'strict'
$'preferred mime subtype' = "html"
) || throw "imap request failed"
walk $'messages' as $ => no-op /* from here $ contains the message as `'network.lib'::'network message'` */
}
consumer ( )
/* Standard Library `network.lib`::`smtp` example
* this performs a simple SMTPS request to send an email
* note that different versions of security exists for smtp
* > implicit security uses the `smtps://` protocol
* > explicit security uses the `smtp://` protocol and `STARTTLS` command
* by default we fail when the connection cannot be secured, see the example `stdlib.network.ftp.insecure` for an alternative
* when the request fails, the `throw` contains additional information about the cause
*/
routine 'test' on
do {
switch true => call 'network'::'smtp' with (
$'server' = "smtps://example.com"
$'authentication' = unset
$'security' = option 'strict'
$'message' = new (
'from' = create ["Me"] "me@example.com"
'recipients' = create ["You"] "you@example.com"
'subject' = "example message"
'attachments' = create (
'name' = "hello.txt"
'part' = (
'mime type' = "text"
'mime sub type' = "plain"
'content' = "hello world" => call 'unicode'::'as binary' with ( )
)
)
'body' = (
'mime type' = "text"
'mime sub type' = "plain"
'content' = "hello world" => call 'unicode'::'as binary' with ( )
)
)
)
(
| value as $ => /* from here `$` always contains true as `boolean` */ no-op
| error => throw "smtp request failed"
)
}
Plural
The plural library provides algorithms operating on sets.
library
/* Sorts a set based on a comparison function.
*/
function 'sort'
< template plural T , plural T , plural T >
(
$'compare': lambda on boolean (
$'A': T
$'B': T
)
)
binds: "c57aca6145d1aaa7ceba52e40f8c5ef424dbdea8"
/* 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.
*/
function 'select'
< template plural T , plural T , unsafe T >
(
$'compare': lambda on boolean (
$'A': T
$'B': T
)
)
binds: "a7d524f804c5084e0d35454d97746e175ad80952"
/* Filters a set.
* This returns a new set containing only the entries for which `filter` results in true.
*/
function 'filter'
< template plural T , plural T , plural T >
(
$'filter': lambda on boolean (
$'entry': T
)
)
binds: "4e5f8e8b73220087fe0ec9e0d0a7ac84fadb3890"
/* Divide a set in several smaller sets (buckets).
* The order of the entries is maintained.
*/
function 'split'
< template plural T , plural T , plural [integer] plural T >
(
$'bucket size': integer
)
binds: "a7a514616a2bacca7c5ff05c3103e322b211f8ae"
Plural Examples
consumer ( )
routine 'test' on
do {
let $'data' as list boolean = {
create true
create false
create true
}
let $'value' = $'data' => call 'plural'::'filter' with (
$'filter' = lambda => $'entry'
)
switch $'value'.size => is ( 2 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'data' as list integer = {
create 5
create 2
create 5
}
let $'magic' = 5
let $'value' = $'data' => call 'plural'::'filter' with (
$'filter' = lambda => $'entry' => is ( ^ $'magic' )
)
switch $'value'.size => is ( 2 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
$'Context' =
do {
let $'object' = ^ $'Context'.'objects' => call 'plural'::'select' with (
$'compare' = lambda => $'A'.'value' => greater-than ( $'B'.'value' )
) || throw "no unique object to select"
switch $'object'.'key' => is ( "two" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
$'Context' =
do {
let $'object' = ^ $'Context'.'objects' => call 'plural'::'select' with (
$'compare' = lambda => $'A'.'value' => greater-than ( $'B'.'value' )
) || throw "no unique object to select"
switch $'object'.'key' => is ( "two" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
$'Context' =
do try {
let $'object' = ^ $'Context'.'objects' => call 'plural'::'select' with (
$'compare' = lambda => $'A'.'has value'?'yes'.'value' => greater-than ( $'B'.'has value'?'yes'.'value' ) || throw "comparison data missing"
) || throw "no selection"
throw "object selected"
}
catch as $ => switch $ => is ( "comparison data missing" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do {
let $'data' as list integer = {
create 16
create 42
create 39
}
let $'value' = $'data' => call 'plural'::'sort' with (
$'compare' = lambda => $'A' => less-than ( $'B' )
)
switch $'value' => join separator: ( "," ) ( => serialize as decimal ) => is ( "16,39,42" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'data' as list integer = {
create 16
create 42
create 39
create 17
create 43
create 38
create 18
create 44
create 37
}
let $'result' as list {
'bucket': list integer
} = walk $'data' => call 'plural'::'split' with ( $'bucket size' = 2 ) as $ => create (
'bucket' = walk $ as $ => create $
)
switch $'result' => serialize as JSON => is ( "[{\"bucket\":[16,42]},{\"bucket\":[39,17]},{\"bucket\":[43,38]},{\"bucket\":[18,44]},{\"bucket\":[37]}]" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
Unicode
The unicode library provides functions to manipulate text values. These functions require UTF-8 encoded data.
define 'trim style' as @API choice ( 'leading' 'trailing' 'both' 'none' )
define 'alignment' as @API choice ( 'left' 'right' )
define 'message data' as @API {
'types': collection union (
'calendar' integer
'number' integer
'text' text
)
}
library
/* 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.
*/
function 'import'
< binary , unsafe text >
(
$'encoding': text
)
binds: "5eda0a0995ac59d7760ecfa8fc914ff6be5510f0"
/* 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.
*/
function 'export'
< text , unsafe binary >
(
$'encoding': text
)
binds: "95c1f25e3ed8a0ef1cc5f3dbf9a63d825c4fca04"
/* Returns a text value as binary data.
* The data is encoded in the internal encoding.
*/
function 'as binary'
< text , binary >
( )
binds: "87c97c8e13179e8011a433263adba108b053ac5e"
/* Removes all whitespace from a text value.
*/
function 'strip'
< text , text >
( )
binds: "a255c15caaf00b4ef5284e5616c0ea5384cae258"
/* Removes leading and/or trailing whitespace from a text value.
*/
function 'trim'
< text , text >
(
$'style': 'trim style'
)
binds: "210540a08d6c7f2f7dc7b0f11d87ca0fb57f6bb2"
/* Split a text into multiple fragments.
* Empty fragments are automatically removed.
*/
function 'split'
< text , list text >
(
$'style': 'trim style'
)
binds: "f516f2ea99343dd9d1035d588484ba06fd57d580"
/* Adds whitespace to a text value.
*/
function 'pad'
< text , text >
(
$'align': 'alignment'
$'length': integer
)
binds: "1085bfb07ec00ef9b01984e3605f257d0dadbd1c"
/* Match a text with a Regular Expression.
* The whole input must match the pattern.
*/
function 'regex'
< text , unsafe boolean >
(
$'pattern': text
)
binds: "24ea697a55dedbc6a58b8ca00d80d5cd1bde4a2b"
/* Format a message with dynamic data.
* Pattern syntax:
* message = messageText (argument messageText)*
* argument = noneArg | simpleArg | complexArg
* complexArg = choiceArg | pluralArg | selectArg | selectordinalArg
*
* noneArg = '{' argNameOrNumber '}'
* simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}'
* choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}'
* pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}'
* selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}'
* selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}'
*
* choiceStyle: see ChoiceFormat
* pluralStyle: see PluralFormat
* selectStyle: see SelectFormat
*
* argNameOrNumber = argName | argNumber
* argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
* argNumber = '0' | ('1'..'9' ('0'..'9')*)
*
* argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration"
* argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText | "::" argSkeletonText
*
* For more information see: https://unicode-org.github.io/icu/userguide/format_parse/
*/
function 'format'
< text , unsafe text >
(
$'data': 'message data'
$'locale': text
)
binds: "00e5893ff46bce3d3278091734b1ca786f41fe83"
Unicode Examples
consumer ( )
routine 'test' on
do switch "ç" => is ( "ç" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do switch "Ç" => is case folding ( "ç" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do {
/* test vectors for format function */
let $'vectors' = list (
file ( token = "{nr,spellout}" extension = "vierduizendtweehonderd" ) ,
file ( token = "{nr,number,currency}" extension = "€ 4.200,00" ) ,
file ( token = "{nr,number}" extension = "4.200" ) ,
file ( token = "{nr}" extension = "4.200" ) ,
file ( token = "{tx}" extension = "hello" ) ,
file ( token = "{dt,date}" extension = "3 feb. 2021" )
)
let $'data' as 'unicode'/'message data' = (
'types' = {
create ["dt"] create 'calendar' 212479064430
create ["nr"] create 'number' 4200
create ["tx"] create 'text' "hello"
}
)
/* test each vector */
walk $'vectors' as $ => {
let $'pattern' = $
let $'message' = $'pattern'.token => call 'unicode'::'format' with (
$'data' = ^ $'data'
$'locale' = "nl_NL"
) || throw "error"
switch $'message' => is ( $'pattern'.extension ) (
| true => no-op // Vector tested successful
| false => throw "produced wrong value"
)
}
}
consumer ( )
routine 'test' on
do {
/* test vectors for trim function */
let $'vectors' as list {
'input': text
'result': text
'align': 'unicode'/'alignment'
'length': integer
} = {
create (
'input' = "hello"
'result' = "hello "
'align' = option 'left'
'length' = 10
)
create (
'input' = "hello"
'result' = " hello"
'align' = option 'right'
'length' = 10
)
create (
'input' = "hello"
'result' = "hello"
'align' = option 'left'
'length' = 2
)
create (
'input' = "à"
'result' = "à "
'align' = option 'left'
'length' = 2
)
}
/* test each vector */
walk $'vectors' as $ => {
let $'test' = $ .'input' => call 'unicode'::'pad' with (
$'align' = $ .'align'
$'length' = $ .'length'
)
switch $'test' => is ( $ .'result' ) (
| true => no-op // Vector tested successful
| false => throw "produced wrong value"
)
}
}
consumer ( )
routine 'test' on
do {
let $'match' = "abcde" => call 'unicode'::'regex' with ( $'pattern' = "[a-z]+" ) || throw "invalid pattern"
switch $'match' (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'match' = "abCde" => call 'unicode'::'regex' with ( $'pattern' = "[a-z]+" ) || throw "invalid pattern"
switch $'match' (
| true => throw "produced wrong value"
| false => no-op // Test successful
)
}
consumer ( )
routine 'test' on
do try {
let $'match' = "abcde" => call 'unicode'::'regex' with ( $'pattern' = "[a-z+" ) || throw "invalid pattern"
throw "valid pattern"
/* suppress unused warnings */
switch $'match' (
| true => no-op
| false => no-op
)
}
catch as $ => switch $ => is ( "invalid pattern" ) (
| true => no-op // Vector tested successful
| false => throw "produced wrong value"
)
provider (
'lines' = {
let $'lines' = "line-0
line-1
line-2" => call 'unicode'::'split' with ( $'style' = option 'both' )
walk $'lines' as $ => create (
'line' = $
)
}
)
consumer ( )
routine 'test' on
do {
/* test vectors for trim function */
let $'vectors' = list (
"line", // nothing to trim
" line", // leading spaces
"line ", // trailing spaces
" l i n e ", // internal spaces
" l i n e " // no-ASCII space
)
/* test each vector */
walk $'vectors' as $ => {
let $'line' = $
let $'trim' = $'line' => call 'unicode'::'strip' with ( )
switch $'trim' => is ( "line" ) (
| true => no-op // Vector tested successful
| false => throw "produced wrong value"
)
}
}
consumer ( )
routine 'test' on
do {
/* test vectors for trim function */
let $'vectors' = list (
"line", // nothing to trim
" line", // leading spaces
"line ", // trailing spaces
" line", // tabs
" line" // no-ASCII space
)
/* test each vector */
walk $'vectors' as $ => {
let $'line' = $
let $'trim' = $'line' => call 'unicode'::'trim' with ( $'style' = option 'both' )
switch $'trim' => is ( "line" ) (
| true => no-op // Vector tested successful
| false => throw "produced wrong value"
)
}
}
Processor
The internal library.
Allows defining reusable types.
'library': dictionary { [ define ]
'type': [ as ] stategroup (
'schema' {
/* Define a schema type.
* Optionally the `@API` option can be used to suppress usages analysis.
*/
'analysis': stategroup (
'API' { [ @API ] }
'full' { }
)
'type': component 'schema complex type'
}
'pattern' { [ pattern ]
/* Define a pattern.
*/
'analysis': stategroup (
'API' { [ @API ] }
'full' { }
)
'rule': component 'pattern rule'
}
)
}
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 'statement'
/* Dataset initialization.
* Before a provider can process command 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': stategroup (
'custom' { [ init ]
/* 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 'statement'
}
'main' { [ init 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' {
/* 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 command the routine binds to.
* The command data is available in `$`.
*/
'command': [ on command ] reference
/* The path to the node the command is defined at.
* The path becomes the initial scope of the routine.
*/
'named type': component 'interface named path'
'type': stategroup (
'execute' {
/* Execute a statement.
* From this context Alan Interface events can be executed.
*/
'statement': [ do ] component '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.
* These additional runs are scheduled regardless of the current dataset state.
*/
}
)
}
/* 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 for each key is restricted, see 'statement' for details.
* CAVEAT: The context keys are only evaluated once at startup.
*/
'context keys': [ (, ) ] dictionary {
'has next': stategroup = node-switch successor (
| node = 'yes' { 'next' = successor }
| none = 'no'
)
'value': [ = ] component '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'
)
'binding': [ on ] stategroup (
'event' {
/* The routine binds to an event.
* When the event is received, the routine is run.
* The event data is available in `$`.
*/
'event': [ event ] reference
}
'collection' {
/* The routine binds to an collection.
* When any entries in the collection are touched by an update, the routine is run.
* The set of touched entries (and only the touched entries) are available in `$`.
* The routine is only run after the update is fully processed.
* As a result, the routine always has a fully initialized dataset available, but is not informed which data was updated.
*/
'property': [ collection ] reference
}
'entry deletion' {
/* The routine binds to a collection.
* When an entry in the collection is deleted, the routine is run.
* The key of the delete entry is available in `$`.
* The routine is only run after the update is fully processed.
* As a result, the routine always has a fully initialized dataset available, but is not informed which data was updated.
*/
'property': [ collection, deletion ] reference
}
'node' {
/* The routine binds to a node.
* When the node is touched by an update, the routine is run.
* The routine is only run after the update is fully processed.
* As a result, the routine always has a fully initialized dataset available, but is not informed which data was updated.
*/
}
)
/* The path to the node the routine binds to or the event is defined at.
* The path becomes the initial scope of the routine.
*/
'named type': component 'interface named path'
/* From this context Alan Interface events can be executed.
*/
'statement': [ do ] component 'statement'
}
/* The hooks.
* See `Library Hooks` for more information.
*/
'hooks': set {
'hook': component 'library hook'
}
}
'library' { [ library ]
/* The connector is a library.
* Currently only standard libraries are supported.
*
* A standard library consist of a set of function signatures.
* These functions are implemented by the connector runtime.
*/
'functions': dictionary { [ function ]
'signature': component 'signature'
'binds': [ binds: ] text
}
/* A standard library can expose hooks.
* Each hook is triggered by external events.
*/
'hooks': dictionary { [ hook ]
'signature': component 'signature'
'binds': [ binds: ] text
}
}
)
Internal error handler.
Allows specifying an error handler routine. Example:
consumer ( )
/* error-handler example
* this logs the error report
*/
on error do @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 do ] component 'statement'
}
'no' { }
)
'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 lambdas to these hooks. These lambdas are executed when the hook is triggered. Some hooks only trigger a subset of the added lambdas, based on the handler name.
Lambdas added to hooks can execute side-effects based on there 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"
do (
'status' = 200
'headers' = create ["content-type"] "application/json"
'content' = $'request' => serialize as JSON => call 'unicode'::'as binary' with ( )
)
'library hook' {
/* The standard library and hook to add the lambda 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': [ do ] component 'lambda implementation'
}
'locale selector' {
'locale': stategroup (
'custom' {
'locale': [ locale: ] text
}
'default' { }
)
}
'interface type path' {
'has step': stategroup (
'yes' {
'property': [ . ] reference
'type': stategroup (
'collection' { [ [] ] }
'choice' {
'state': [ ? ] reference
}
'node' { }
)
'tail': component 'interface type path'
}
'no' { }
)
}
Interface Named Path
As the connector does not allow stepping from a child node to the parent, the path is segmented. Each segment has a name and specifies a part of the path. This name is then assigned that node. This allows a routine to access its parents by explicitly naming them.
'interface named path' {
'segments': dictionary { [ $ ]
'has previous': stategroup = node-switch predecessor (
| node = 'yes' { 'previous' = predecessor }
| none = 'no'
)
'has next': stategroup = node-switch successor (
| node = 'yes' { 'next' = successor }
| none = 'no'
)
'steps': [ = ] component 'interface type path'
}
'has segment': stategroup = node-switch .'segments' (
| nodes = 'yes' {
'first' = first
'last' = last
}
| none = '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
* > text : supports only equality comparisons, 2 texts are considered equal when they are bitwise identical
* > file : supports only equality comparisons, 2 files are considered equal when both text values are identical
*/
}
)
}
'schema scalar type' {
'type': stategroup (
'boolean' { [ boolean ]
/* A boolean type can hold the value `true` or `false`.
*/
}
'integer' { [ integer ]
/* An integer can hold numbers.
* Integers can not have a fraction.
*/
'limits': stategroup (
'yes' { [ @limit: {, } ]
/* Limit the value range.
* All limits are inclusive.
*/
'lower': stategroup (
'yes' {
/* Limit the minimum value.
* The value is evaluated before the decimal import rule.
*/
'value': integer
}
'no' { }
)
'upper': [ , ] stategroup (
'yes' {
/* Limit the maximum value.
* The value is evaluated before the decimal import rule.
*/
'value': integer
}
'no' { }
)
}
'no' { }
)
'rule': component 'decimal import rule'
}
'binary' { [ binary ]
/* A data can hold any type of binary data.
*/
}
'text' { [ text ]
/* A text can hold Unicode data.
*/
'limits': stategroup (
'yes' { [ @limit: {, } ]
/* Limit the text length.
* 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 is not able to determine the type, it must be explicitly give a type through a separate decorate step.
* Serialization does not include the type information and omits the value when the type was not yet determined.
*/
'types': [ (, ) ] dictionary {
'has next': stategroup = node-switch successor (
| node = 'yes' { 'next' = successor }
| none = 'no'
)
'type': component 'schema complex type'
}
}
'node' {
/* Node type.
* Parsing and serialization expects the type information (property names) to be present in the data.
*/
'node': component 'schema node type'
}
'headless' { [ headless ]
/* Node type.
* Unlike the normal node type, this expects the data to be ordered and uses the property order during parsing and serialization.
*/
'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 in the set is an `entity` with an implicit key and a value of `type`.
* 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 in the set is of `type`.
* Entries in a list have no unique identification.
*/
'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.
*/
'node': component 'schema node type'
}
'scalar' {
'type': component 'schema scalar type'
}
)
}
'schema node type' {
/* A node is a container containing a static set of named values, the properties.
* When the document type supports it, each property itself can have a set of attributes.
* These attributes can either be a text (to be retrieved and processed later) or a filter (to reduce the possible matches in serialized form).
* A property can be marked as optional, an optional property may be omitted or have a special no value construct in serialized form.
*
* A property can be marked as protected, an protected property's 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 that 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'
}
'filters': dictionary { [ where ]
'has next': stategroup = node-switch successor (
| node = 'yes' { 'next' = successor }
| none = 'no'
)
'value': [ is ] component 'value promise'
}
}
'no' { }
)
'protect': [ : ] stategroup (
'yes' { [ @protected ] }
'no' { }
)
'is optional': stategroup (
'yes' { [ optional ] }
'no' { }
)
'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 (
'inferred' {
/* Source driven `let`.
* The result of the promise is assigned to the name.
* The type of this `let` is inferred from the promise.
*/
'value': [ = ] component 'value promise'
}
'schema' {
/* Target driven `let`.
* The type of this `let` is the type of the schema.
* This executes the statement with the schema as target.
*/
'schema': [ as ] component 'schema complex type'
'statement': [ = ] component 'statement'
}
'lambda' {
/* Lambda `let`.
* The type of this `let` is a callable object.
*/
'lambda': [ = ] component 'lambda definition'
}
)
}
}
'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'
}
)
}
)
}
'target type path step' {
'has step': stategroup (
'yes' {
'type': stategroup (
'collection' { [ * ] }
'choice' {
'state': [ ? ] reference
}
'node' {
'property': [ . ] reference
}
'command' {
'command': [ command ] reference
}
'event' {
'event': [ event ] reference
}
)
'tail': component 'target type path step'
}
'no' { }
)
}
'target type path' {
'type': stategroup (
'none' { [ none ] }
'absolute' {
'root': stategroup (
'interface' { [ interface ] }
'schema' {
'selector': component 'library selector'
}
'boolean' { [ boolean ] }
'integer' { [ integer ] }
'text' { [ text ] }
)
'steps': component 'target type path step'
}
'relative' {
'steps': component 'target type path step'
}
)
}
'type path step' {
'has step': stategroup (
'yes' {
'type': stategroup (
'collection' { [ * ] }
'choice' {
'state': [ ? ] reference
}
'node' {
'property': [ . ] reference
}
'command' {
'command': [ command ] reference
}
'event' {
'event': [ event ] reference
}
)
'tail': component 'type path step'
}
'no' { }
)
}
'type path' {
'root': stategroup (
'interface' { [ interface ] }
'schema' {
'schema': component 'schema complex type'
}
'context' { [ context ] }
)
'steps': component 'type path step'
}
'type definition' {
'type': stategroup (
'file' { [ file ] }
'optional' { [ optional ]
'sub type': component 'type definition'
}
'plural' { [ plural ]
'index': stategroup (
'integer' { [ [integer] ] }
'text' { [ [text] ] }
'inferred' { }
)
'sub type': component 'type definition'
}
'lambda' {
'signature': [ lambda ] component 'signature'
}
'type' {
'path': component 'type path'
}
'template' { [ T ] }
)
}
'signature parameters' {
/* Parameter definition of a callable object.
*/
'parameters': [ (, ) ] dictionary { [ $ ]
'has next': stategroup = node-switch successor (
| node = 'yes' { 'next' = successor }
| none = 'no'
)
'type': [ : ] component 'type definition'
}
}
'signature promise' {
/* The signature of a callable object.
* This represents a callable object that must be called in a promise chain context.
*/
'template': [ < ] stategroup (
'yes' {
'requirement': [ template, T , ] stategroup (
'plural' { [ plural ] }
'optional' { [ optional ] }
'none' { }
)
}
'no' { }
)
/* The type of the input.
* When written inline, the input type can be inferred (implicit).
*/
'context': stategroup (
'explicit' {
'context': component 'type definition'
}
'implicit' { }
)
/* The promise guarantee of the callable object.
*/
'guarantee': [ , ] stategroup (
'yes' { }
'no' { [ unsafe ] }
)
/* The type of the output.
*/
'result': [, > ] component 'type definition'
/* The parameter definition.
*/
'parameters': component 'signature parameters'
}
'signature statement' {
/* The signature of a callable object.
* This represents a callable object that must be called as a statement.
*/
/* The target the callable object must be called on.
* When written inline, the target can be inferred.
*/
'target': stategroup (
'explicit' {
'path': [ on ] component 'target type path'
}
'inferred' { }
)
/* The parameter definition.
*/
'parameters': component 'signature parameters'
}
'signature' {
/* The signature of a callable object.
*/
'type': stategroup (
'promise' {
'signature': component 'signature promise'
}
'statement' {
'signature': component 'signature statement'
}
)
}
'callable instance' {
'template': stategroup (
'yes' { [ <, > ]
'type': component 'type definition'
}
'no' { }
)
}
'lambda implementation' {
/* The implementation of a lambda.
* Currently lambdas can only be statements.
*/
'type': stategroup (
'statement' {
'statement': component 'statement'
}
)
}
'lambda argument' {
'type': stategroup (
'optional' {
/* Set an optional parameter.
* Lambda arguments must always explicitly set or unset.
*/
'action': stategroup (
'set' { [ set ]
'argument': component 'lambda argument'
}
'unset' { [ unset ] }
)
}
'signature' { [ lambda => ]
/* Set the implementation of a lambda.
* The signature of the lambda is taken from the parameter.
*/
'lambda': component 'lambda implementation'
}
'choice' {
/* Set a choice.
* The available values of the choice are taken from the parameter.
*/
'option': [ option ] reference
}
'node' {
/* Set a node.
* This allows the creation of new data inline instead of retrieving stored data.
*/
'statement': [ new ] component 'statement'
}
'value' {
/* Set the value.
*/
'value': component 'value promise'
}
)
}
'lambda arguments' {
/* The arguments for a callable object.
* This creates a new scoped bound to the signature parameters.
* The created scope will be the initial scope of the callable object when called.
*/
'values': [ (, ) ] dictionary { [ $ ]
'argument': [ = ] component 'lambda argument'
}
}
'lambda definition' {
/* Defines a new lambda.
*/
'signature': [ lambda ] component 'signature'
'instance': component 'callable instance'
'lambda': [ => ] component 'lambda implementation'
}
'callable selector' {
'type': stategroup (
'function' {
/* Select a function from the standard library.
*/
'library': reference
'function': [ :: ] reference
'instance': component 'callable instance'
}
'recurs' { [ self ]
/* Select self.
* This represents the currently executing lambda.
* Only available in a lambda implementation.
*/
}
'new' {
/* Create and select a new lambda.
* This lambda is anonymous.
*/
'lambda': component 'lambda definition'
}
'stored' {
/* Retrieve a stored lambda.
*/
'value': component 'value promise'
}
)
}
'decimal import rule' {
/* Enable/Disable decimal point translation.
* Decimal point translation is only every applied during parsing and serialization.
* The translation is provided from the parsing point of view, during serialization the inverse is applied.
*/
'decimal point translation': stategroup (
'yes' {
'places': [ << (, ) ] component 'value promise'
}
'no' { }
)
}
'date expression' {
'year': component 'promise chain'
'style': stategroup (
'calendar' {
'month': [ - ] component 'promise chain'
'day': [ - ] component 'promise chain'
}
'week' { [ W ]
'week': component 'promise chain'
'day of week': stategroup (
'monday' { [ Monday ] }
'tuesday' { [ Tuesday ] }
'wednesday' { [ Wednesday ] }
'thursday' { [ Thursday ] }
'friday' { [ Friday ] }
'saturday' { [ Saturday ] }
'sunday' { [ Sunday ] }
)
}
'ordinal' {
'day': [ , ] component 'promise chain'
}
)
}
'time expression' {
'hour': component 'promise chain'
'minute': [ : ] component 'promise chain'
'second': [ : ] component 'promise chain'
}
'pattern rule piece' {
'type': stategroup (
'pattern' {
'capture': stategroup (
'yes' { [ $ ] }
'no' { }
)
'type': stategroup (
'decimal' { [ decimal ]
'locale': component 'locale selector'
'rule': component 'decimal import rule'
}
'text' { [ text ] }
)
'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 patterns 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
}
'promise path' {
'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 'promise'
}
'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' { }
)
}
'interface choice' {
/* 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.
*/
}
)
}
'promise chain' {
'has step': stategroup (
'yes' {
'step': stategroup (
'path' {
'step': component 'promise path'
}
'complex' { [ => ]
'type': stategroup (
'parse' {
'as': [ parse as ] stategroup (
'JSON' { [ JSON ]
/* Parses a JSON object.
* On success it results in a document and must first be passed to a decorator before it is usable.
*/
}
'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.
*/
}
'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 'promise'
}
'default' { }
)
}
'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.
*/
}
'decimal' { [ decimal ]
/* Parses a decimal value.
* Decimal: [+-]d[.f]
* Any number of digits can be provided, but the supported range is limited by the implementation.
* An optional minus or plus sign is allowed before the first digit to set the sign.
* Optionally no digits can be provided before the decimal point, which implies the value of 0.
* The fraction separated by a decimal point is allowed and the amount of digits may differ from the `rule`,
* but only the digits specified by `rule` are kept with possibly additional 0 digits introduced when insufficient precision was provided.
*/
'locale': component 'locale selector'
'rule': component 'decimal import rule'
}
'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' {
'as': [ serialize as ] stategroup (
'JSON' { [ JSON ] }
'XML' { [ XML ] }
'ISO Date Time' { [ ISODateTime ] }
'ISO Date' { [ ISODate ] }
'ISO Time' { [ ISOTime ] }
'decimal' { [ decimal ]
'locale': component 'locale selector'
'rule': component 'decimal import rule'
}
)
}
'decorate' {
'type': [ decorate as ] stategroup (
'source' {
/* Decorate parsed data according to a schema.
*/
'schema': component 'schema complex type'
}
'union' {
/* Decorate a union type.
* When the type is already know, this works as a type cast.
*/
'type': [ union ] reference
}
)
}
'make' { [ make ]
'type': stategroup (
'date' { [ date (, ) ]
/* Creates an Alan Date for individual components. */
'date': component 'date expression'
}
'date time' { [ date-time (, ) ]
/* Creates an Alan DateTime for individual components. */
'date': component 'date expression'
'time': [ T ] component 'time expression'
}
'time' { [ time (, ) ]
/* Creates a time in seconds for individual components. */
'time': component 'time expression'
}
)
}
'compare' {
/* Compares the current value with another value.
* 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'
'other': [ (, ) ] component 'promise'
}
'reduce' {
'merge': stategroup (
'value' {
/* Merge a set of values.
* The `for each` sub-chain is run for each value in the input set, the result must match the type required by the operator.
*/
'merge type': stategroup (
'shared' { [ shared ]
/* 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 ]
/* 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'
}
'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.
*/
}
'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.
*/
}
'join' { [ join ]
/* Concatenates a set of texts.
* Optionally a separator can be added between each element.
* The entries are joined in the order of the set.
*/
'separator': stategroup (
'yes' { [ separator: ]
'value': [ (, ) ] component 'promise'
}
'no' { }
)
}
'logical and' { [ and ]
/* Calculates the logical and of a set of booleans.
* It fails when the set is empty.
*/
}
'logical or' { [ or ]
/* Calculates the logical or of a set of booleans.
* It fails when the set is empty.
*/
}
)
'for each': [ (, ) ] component 'promise chain'
}
)
}
'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.
*/
'comparator': [ on ] component 'comparator'
'key': [ (, ) ] component 'promise chain'
}
)
}
'call' { [ => ]
/* Call a standard library function or lambda. */
'selection': [ call ] component 'callable selector'
'arguments': [ with ] component 'lambda arguments'
}
)
'tail': component 'promise chain'
}
'no' { }
)
}
Promise
A promise consists of a head
, an instruction producing a value without any input, and a chain
.
The chain of a promise is a set of ordered instructions which each take the output of the previous instruction as their input.
Each instruction in the chain, as well as the head, has a promise guarantee
. When this guarantee is yes
, the instruction will always succeed regardless of the input.
Likewise, when this guarantee is no
, the instruction may fail at runtime when the input does not meet the instruction’s requirements.
The individual instructions are separated with =>
. Instructions with sub-promises, wrap the sub-promise in parenthesis ( ... )
.
These sub-promises propagate their guarantee to the parent promise, allow the handling of possible runtime failures at the top-level promise. Although they can handle the failures on their own.
Just like sub-promises, instructions in a chain pass their guarantee to the next instruction.
At the end of a chain, when the guarantee is no
, an alternative must be provided.
An alternative is indicated with ||
. It provides an alternative promise to obtain a value when the previous promise fails at runtime.
When the end of the alternative promise chain the guarantee is again no
, an other alternative must be provided.
This pattern repeats itself until a promise is provided with a guarantee of yes
or the guarantee can be propagated to the parent.
Alternatively, when no such promise can be provided, the promise can be terminated with a throw
.
Throwing causes execution of the statements to be aborted until a try
statement is found.
All changes, if any, to the target since this try are undone an execution resumes at the corresponding catch
statement.
The promise guarantees safe execution. When an instruction fails at runtime, the remainder of the chain is aborted and the alternative is evaluated. As a result it is always safe the chain multiple instructions with a guarantee of no.
'promise' {
'head': stategroup (
/* fetch from key-value pair systems */
'variable' {
/* Retrieves a variable from the instance-data.
*/
'variable': [ var ] reference
}
'configuration' {
/* Retrieves a value from the system configuration.
*/
'key': [ conf ] text
'data type': stategroup (
'integer' { [ integer ] }
'text' { [ text ] }
)
}
/* static/hardcoded values */
'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.
*/
}
)
}
)
}
/* fetch from execution state */
'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.
*/
}
/* compute new values */
'list constructor' { [ list ]
/* Construct a list object.
* This allows for the conversion of a static list to a dynamic list.
* The order of entries is identical to the promise order.
*/
'promises': [ (, ) ] component 'promises'
}
'arithmetic' {
'operation': stategroup (
'inversion' { [ - ]
/* Invert the sign of an integer.
*/
'operand': [ (, ) ] component 'promise'
}
'division' { [ division (, ) ]
/* Divide one integer by another integer.
* When the resulting fraction, if any, is truncated.
* This fails when `denominator` is zero.
*/
'numerator': component 'promise'
'denominator': [ , ] component 'promise'
}
)
}
'logic' {
'operation': stategroup (
'negation' { [ not ]
/* Toggle the value of a boolean.
*/
'operand': [ (, ) ] component 'promise'
}
)
}
'file constructor' { [ file (, ) ]
/* Construct a file object.
*/
'token': [ token = ] component 'promise'
'extension': [ extension = ] component 'promise'
}
)
'chain': component 'promise chain'
'alternative': stategroup (
'yes' {
'type': [ || ] stategroup (
'value' {
/* Provide an alternative promise. */
'value': component 'promise'
}
'throw' {
/* Throw a new error. */
'message': [ throw ] text
}
)
}
'no' { }
)
}
'promises' {
'value': component 'promise'
'has tail': stategroup (
'yes' {
'tail': [ , ] component 'promises'
}
'no' { }
)
}
Value Promise
This represents a top-level promise. A value promise indicates that a promise guarantee of no cannot be propagated from the wrapped promise. The wrapped promise must always have an alternative with a guarantee of yes, or throw.
'value promise' {
'promise': component 'promise'
}
'target expression' {
'type': stategroup (
'unset' { [ unset ]
/* Explicitly set an optional value to unset.
* This allows the setting of an optional value to be dependent on some condition.
*/
}
'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 {
'attributes': stategroup (
'yes' {
'attributes': [ <, > ] dictionary {
'value': [ = ] component 'value promise'
}
}
'no' { }
)
'statement': [ = ] component 'statement'
}
}
'entry' { [ create ]
/* Create a new entry in a set.
* When the set has implicit keys, a key must be provided.
*/
'implicit key': stategroup (
'yes' {
'value': [ [, ] ] component 'value promise'
}
'no' { }
)
'statement': component 'statement'
}
'state' {
'type': stategroup (
'branch' { [ create ]
/* Set an Alan Interface `stategroup` or `union` to the specified state/type.
*/
'state': reference
'statement': component 'statement'
}
'leaf' {
/* Set a `schema scalar type` choice to the specified value.
*/
'option': [ option ] reference
}
)
}
'scalar' {
/* Assign a scalar the produced value.
*/
'value': component 'value promise'
}
)
}
)
}
Statement
A statement is responsible for the control flow of the processor. Statements operate on a target, the target specifies the cardinality of the statement. A cardinality of singular means that the target can only hold a single value, as a result statements which can produce zero or multiple values are not allowed. A cardinality of plural means that the target can hold any number of values.
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' { [ {, } ]
/* 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.
* Multiple statements are only allowed when the current target supports multiple values.
*/
'scope': stategroup (
'yes' {
'stack block': component 'stack block'
}
'no' { }
)
'statement': component 'statements'
}
'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.
* When a throw is caught, the changes to the target are reverted and the fallback statement is executed.
* 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 'promise'
'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 promise without a guarantee of yes is evaluated.
* Based on whether or not the promise succeeded or failed, the corresponding case is executed.
* When the promise succeeds, the result is available in `$`.
*/
'on value': [ | value as $ => ] component 'statement'
'on error': [ | error => ] component 'statement'
}
'choice' {
/* Execute a conditional branch.
* The branch is determined based on a user defined choice.
* When the choice is an Alan Interface `stategroup`, the state node 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' {
/* Execute a statement once for each entry in a set.
* The entry for which the statement is executed, is available in `$`.
*/
'value': [ walk, as $ ] component 'value promise'
'statement': [ => ] component 'statement'
}
'call' {
/* Call a standard library function or lambda. */
'selection': [ call ] component 'callable selector'
'arguments': [ with ] component 'lambda arguments'
}
'log operation' { [ @log: ]
/* Write a message to the debug channel.
*/
'message': component 'value promise'
}
'no operation' { [ no-op ]
/* No action.
* This is to terminate an execution path without doing anything.
* It is only allowed when the target allows no value.
*
* This statement allows for the creation of empty sets.
* Unlike all other targets, which define no default/initial value, sets are default empty.
*/
}
'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
}
)
}
'target' {
'target': component 'target expression'
}
'execute' {
/* Execute a Alan Interface command or event.
*/
'context': [ execute ] component 'value promise'
'type': stategroup (
'command' {
'command': [ command ] reference
}
'event' {
'event': [ event ] reference
}
)
'statement': [ with ] component 'statement'
}
)
}
'statements' {
'statement': component 'statement'
'has more statements': stategroup (
'yes' {
'statement': component 'statements'
}
'no' { }
)
}
Examples
consumer ( )
/* now example
* this example demonstrates the usages of the now instruction
*/
routine 'test' on
do {
let $'date-time' = now
no-op
/* suppress unused warnings */
@log: $'date-time' => serialize as ISODateTime
}
provider ( )
/* Provide on Command example
* this simply forces an immediate execution of the `provider` when command `'force run'` is received
*/
routine 'force run' on command 'force run' schedule
/* Provider Initialization
* this ensures that the connector always runs with a dataset
* it uses a custom routine to generate the initial dataset
*/
provider ( )
init ( )
/* Provider Initialization
* this ensures that the connector always runs with a dataset
* the dataset is provided by the main routine
*/
provider ( )
init main
consumer ( )
routine 'test' on
do {
let $'data' as {
'value': optional text
} = ( )
let $'value' = $'data'.'value' get || ""
switch $'value' => is ( "" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = $ .'choice'?'a'.'value' || "fallback value"
switch $'value' => is ( "chosen value" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = $ .'choice'?'a'.'value' || "fallback value"
switch $'value' => is ( "fallback value" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'message' = list ( "hello", "world" ) => join separator: ( " " ) ( )
switch $'message' => is ( "hello world" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
switch conf "test" integer => is ( 42 ) || throw "no config value" (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
switch conf "test" text => is ( "hello" ) || throw "no config value" (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do try {
throw "failure"
}
catch => switch $ .'value' => is ( "value 101" ) (
| true => no-op // Test successful
| false => throw "wrong context value"
)
consumer ( )
routine 'test' on
$'Context' =
do switch $'Context'.'custom' (
|'yes' as $ => throw "switch wrong case"
|'no' => switch $ .'default' => is ( "The default value" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
)
consumer ( )
routine 'test' on
$'Context' =
do switch $'Context'.'custom' (
|'yes' as $ => switch $ .'value' => is ( "A custom value" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
|'no' => throw "switch wrong case"
)
consumer ( )
routine 'test' on
do {
let $'data' as list integer = {
create 42
create 24
}
switch $'data' => sum ( ) => is ( 66 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'data' as integer = 42
switch $'data' => is ( 42 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'data' as text = "hello world"
switch $'data' => is ( "hello world" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do try {
let $'document' = "hello,world" => parse as CSV || throw "invalid CSV"
let $'object' = $'document' => decorate as list headless {
'key': text
} || throw "partial initialization"
throw "full initialization"
/* suppress unused warnings */
walk $'object' as $ =>
@log: $ .'key'
}
catch as $ => switch $ => is ( "partial initialization" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try {
let $'document' = "{}" => parse as JSON || throw "invalid JSON"
let $'object' = $'document' => decorate as {
'key': text
} || throw "partial initialization"
throw "full initialization"
/* suppress unused warnings */
@log: $'object'.'key'
}
catch as $ => switch $ => is ( "partial initialization" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try {
let $'document' = "<root></root>" => parse as XML || throw "invalid XML"
let $'object' = $'document' => decorate as {
'root': {
'key': text
}
} || throw "partial initialization"
throw "full initialization"
/* suppress unused warnings */
@log: $'object'.'root'.'key'
}
catch as $ => switch $ => is ( "partial initialization" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try {
let $'document' = "<root></root>" => parse as XML || throw "invalid XML"
let $'object' = $'document' => decorate as {
'root'<'key': text > : { }
} || throw "partial initialization"
throw "full initialization"
/* suppress unused warnings */
@log: $'object'.'root'<'key'>
}
catch as $ => switch $ => is ( "partial initialization" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
provider (
'objects' = try {
create (
'key' = "key"
)
create (
'key' = "key"
)
}
catch as $ => switch $ => is ( "collection uniqueness constraint violation" ) (
| true => no-op // Test successful
| false => throw "invalid error"
)
)
consumer ( )
routine 'test' on
do execute $ command 'command' with (
'message' = "Hello world!"
)
consumer ( )
routine 'test' on
do {
let $'calendar' = 212479064430 => call 'calendar'::'convert' with (
$'source type' = option 'date-time'
$'timezone' = unset
)
let $'value' = "{Year,number,::group-off integer-width/0000}-{Month,number,::integer-width/00}-{Day,number,::integer-width/00}T{Hour,number,::integer-width/00}:{Minute,number,::integer-width/00}:{Second,number,::integer-width/00}Z" => call 'unicode'::'format' with (
$'data' = new (
'types' = {
create ["Year"] create 'number' $'calendar'.'year'
create ["Month"] create 'number' $'calendar'.'month'
create ["Day"] create 'number' $'calendar'.'day of month'
create ["Hour"] create 'number' $'calendar'.'hour'
create ["Minute"] create 'number' $'calendar'.'minute'
create ["Second"] create 'number' $'calendar'.'second'
}
)
$'locale' = "C"
) || throw "format error"
switch $'value' => is ( "2021-02-03T10:20:30Z" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do try {
let $'object' as integer @limit: { , 4 } = 128
throw "length valid"
/* suppress unused warnings */
@log: $'object' => serialize as decimal
}
catch as $ => switch $ => is ( "integer value limit violation" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try {
let $'object' as integer @limit: { 128 , } = 4
throw "length valid"
/* suppress unused warnings */
@log: $'object' => serialize as decimal
}
catch as $ => switch $ => is ( "integer value limit violation" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do {
let $'document' = "{\"key\":3}" => parse as JSON || throw "invalid JSON"
let $'object' = $'document' => decorate as {
'key': integer @limit: { , 40 }
} || throw "value limit exceeded"
switch $'object'.'key' => is ( 3 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do try {
let $'document' = "{\"key\":40}" => parse as JSON || throw "invalid JSON"
let $'object' = $'document' => decorate as {
'key': text @limit: { , 4 }
} || throw "value limit exceeded"
throw "value valid"
/* suppress unused warnings */
@log: $'object'.'key'
}
catch as $ => switch $ => is ( "value limit exceeded" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on collection 'data'
do switch $ => sum ( .'val' ) => is ( 20 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on collection 'data' deletion
do switch $ => is ( "delete" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
provider ( )
routine 'test' on command 'command'
do switch $ .'message' => is ( "Hello world!" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
provider (
'state' = create 'hello' ( )
)
routine 'test' on command 'command'
$'Hello' = .'state'?'hello'
do switch $ .'message' => is ( "Hello world!" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
provider ( )
routine 'test' on command 'command'
$'Context' =
do execute $'Context' event 'event' with (
'message' = $ .'message'
)
consumer ( )
routine 'test' on event 'event'
do switch $ .'message' => is ( "Hello world!" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
provider (
'value' = {
try 0
catch as $ => switch $ => is ( "interface number bounds violation" ) (
| true => 1 // Test successful
| false => throw "produced wrong value"
)
}
)
consumer ( )
routine 'test' on
do try {
switch "\"hello world" => parse as CSV (
| value as $ => throw "parse of garbage resulted in valid CSV"
| error => throw "invalid CSV"
)
}
catch as $ => switch $ => is ( "invalid CSV" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try {
switch "hello,world
tester" => parse as CSV (
| value as $ => throw "parse of garbage resulted in valid CSV"
| error => throw "invalid CSV"
)
}
catch as $ => switch $ => is ( "invalid CSV" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try {
switch "hello world" => parse as JSON (
| value as $ => throw "parse of garbage resulted in valid JSON"
| error => throw "invalid JSON"
)
}
catch as $ => switch $ => is ( "invalid JSON" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try {
switch "hello world" => parse as XML (
| value as $ => throw "parse of garbage resulted in valid XML"
| error => throw "invalid XML"
)
}
catch as $ => switch $ => is ( "invalid XML" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
provider (
'items' = {
let $'key' = "A"
call lambda ( ) => create (
'key' = ^ $'key'
) with ( )
}
)
provider (
'branches' = {
let $'create branch' = lambda ( $'create leafs': lambda on * .'leafs' ( ) ) => {
let $'key' = "A"
create (
'key' = $'key'
'leafs' = call ^ $'create leafs' with ( )
)
}
let $'key' = ".A"
call $'create branch' with (
$'create leafs' = lambda => create (
'key' = ^ $'key'
)
)
}
)
provider (
'items' = {
let $'key' = "A"
let $'create' = lambda ( ) => create (
'key' = ^ $'key'
)
call $'create' with ( )
}
)
consumer ( )
routine 'test' on
do {
let $'greet' = lambda on interface command 'command' ( ) => (
'message' = "Hello world!"
)
execute $ command 'command' with call $'greet' with ( )
}
define 'type' as {
'items': list {
'value': text
}
}
provider (
'items' = {
let $'lambda' = lambda (
$'f': 'type'
) => walk $'f'.'items' as $ => create (
'key' = $ .'value'
)
call $'lambda' with (
$'f' = new (
'items' = {
create (
'value' = "a"
)
create (
'value' = "b"
)
}
)
)
}
)
provider (
'items' = {
let $'lambda' = lambda (
$'f': {
'items': list {
'value': text
}
}
) => walk $'f'.'items' as $ => create (
'key' = $ .'value'
)
call $'lambda' with (
$'f' = new (
'items' = {
create (
'value' = "a"
)
create (
'value' = "b"
)
}
)
)
}
)
provider (
'items' = {
let $'data' as list {
'value': optional text
} = {
create (
'value' = "a"
)
create ( )
}
walk $'data' as $ => call lambda ( $'f': optional text ) => switch $'f' get (
| value as $ => create (
'key' = $
)
| error => no-op
) with ( $'f' = $ .'value' )
}
)
provider (
'items' = {
let $'create' = lambda ( $'f': optional text ) => switch $'f' get (
| value as $ => create (
'key' = $
)
| error => no-op
)
call $'create' with ( $'f' = set "a" )
call $'create' with ( $'f' = unset )
}
)
provider (
'items' = {
let $'create' = lambda ( $'key': lambda on * .'key' ( ) ) => create (
'key' = call $'key' with ( )
)
call $'create' with ( $'key' = lambda => "A" )
}
)
provider (
'items' = {
let $'create' = lambda (
$'key': lambda on * .'key' (
$'val': text
)
) => create (
'key' = call $'key' with ( $'val' = "A" )
)
call $'create' with ( $'key' = lambda => $'val' )
}
)
provider (
'numbers' = {
let $'data' as {
'objects': collection {
'next': optional text
'value': integer
}
'first': text
} = (
'objects' = {
create ["one"] (
'next' = "two"
'value' = 1
)
create ["two"] (
'next' = "three"
'value' = 2
)
create ["three"] (
'value' = 3
)
}
'first' = "one"
)
call lambda ( $'key': text ) => {
let $'entry' = ^ ^ $'data'.'objects'[ ^ $'key'] || throw "invalid reference"
create (
'key' = ^ $'key'
'value' = $'entry'.'value'
)
switch $'entry'.'next' get (
| value as $ => call self with ( $'key' = $ )
| error => no-op // Test successful
)
} with ( $'key' = $'data'.'first' )
}
)
provider (
'items' = {
let $'create' = lambda ( $'key': text ) => create (
'key' = $'key'
)
call $'create' with ( $'key' = "A" )
}
)
define 'dataset' as list {
'key': text
'val': text
}
provider {
let $'data' as 'dataset' = {
create (
'key' = "A"
'val' = "hello"
)
create (
'key' = "B"
'val' = "bye"
)
}
let $'create' = lambda on .'items' ( $'line': 'dataset'* ) => create (
'key' = $'line'.'key'
'val' = $'line'.'val'
)
(
'items' = walk $'data' as $ => call $'create' with ( $'line' = $ )
)
}
define 'option' as choice ( 'hello' 'bye' )
consumer ( )
routine 'test' on
do {
let $'option' = "bye" => decorate as 'option' || throw "option could not be decorated"
switch $'option' (
|'hello' => throw "produced wrong value"
|'bye' => no-op // Test successful
)
}
define 'option' as choice ( 'hello' 'bye' )
consumer ( )
routine 'test' on
do {
let $'settings' = "{\"option\": \"bye\"}" => parse as JSON => decorate as {
'option': 'option'
} || throw "option could not be decorated"
switch $'settings'.'option' (
|'hello' => throw "produced wrong value"
|'bye' => no-op // Test successful
)
}
define 'option' as choice ( 'hello' 'bye' )
consumer ( )
routine 'test' on
do {
let $'option' as 'option' = option 'bye'
switch $'option' (
|'hello' => throw "produced wrong value"
|'bye' => no-op // Test successful
)
}
define 'text pattern' as pattern ( 'text': $ text )
consumer ( )
routine 'test' on
do {
let $'values' = "hello world" => parse as pattern 'text pattern' || throw "invalid format"
switch $'values'.'text' => is ( "hello world" ) (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
}
consumer ( )
routine 'test' on
do {
switch not ( 42 => is ( 24 ) ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do try {
let $'object' as { } = throw "expected"
throw "unexpected"
/* suppress unused warnings */
@log: $'object' => serialize as JSON
}
catch as $ => switch $ => is ( "expected" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
provider (
'objects' = {
let $'dataset' = "{\"objects\":[{\"name\":\"tester\",\"age\":42},{\"name\":\"jester\"}]}" => parse as JSON => decorate as {
'objects': list {
'name': text
'age': optional integer
}
} || throw "dataset could not be decorated"
walk $'dataset'.'objects' as $ => create (
'key' = $ .'name'
'age' = switch $ .'age' get (
| value as $ => create 'known' (
'age' = $
)
| error => create 'unknown' ( )
)
)
}
)
provider (
'objects' = {
let $'data' as list {
'name': text
'age': optional integer
} = {
create (
'name' = "tester"
'age' = 42
)
create (
'name' = "jester"
)
}
walk $'data' as $ => create (
'key' = $ .'name'
'age' = switch $ .'age' get (
| value as $ => create 'known' (
'age' = $
)
| error => create 'unknown' ( )
)
)
}
)
provider (
'objects' = {
let $'data' as list {
'name': text
'age': optional integer
} = {
create (
'name' = "tester"
'age' = 42
)
create (
'name' = "jester"
'age' = unset
)
}
walk $'data' as $ => create (
'key' = $ .'name'
'age' = switch $ .'age' get (
| value as $ => create 'known' (
'age' = $
)
| error => create 'unknown' ( )
)
)
}
)
consumer ( )
routine 'test' on
do {
let $'JSON' = "42" => parse as JSON => decorate as choice ( 'center': 16 'left': 42 'right': 24 ) || throw "invalid json"
switch $'JSON' (
|'center' => throw "invalid state `center` selected"
|'left' => no-op // Test successful
|'right' => throw "invalid state `right` selected"
)
}
consumer ( )
routine 'test' on
do {
let $'JSON' = "\"left\"" => parse as JSON => decorate as choice ( 'center' 'left' 'right' ) || throw "invalid json"
switch $'JSON' (
|'center' => throw "invalid state `center` selected"
|'left' => no-op // Test successful
|'right' => throw "invalid state `right` selected"
)
}
provider (
'objects' = {
let $'JSON' = "{\"hello\":24,\"world\":42}" => parse as JSON => decorate as collection integer || throw "invalid json"
walk $'JSON' as $ => create (
'key' = key
'val' = $
)
}
)
consumer ( )
routine 'test' on
do {
let $'JSON' = "42" => parse as JSON => decorate as integer || throw "invalid json"
switch $'JSON' => is ( 42 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
provider (
'objects' = {
let $'JSON' = "[\"hello\",\"world\"]" => parse as JSON => decorate as list text || throw "invalid json"
walk $'JSON' as $ => create (
'key' = $
)
}
)
provider (
'objects' = {
let $'JSON' = "[[\"key\",\"value\"],[\"hello\",12],[\"world\",42]]" => parse as JSON => decorate as table {
'key': text
'value': integer
} || throw "invalid json"
walk $'JSON' as $ => create (
'key' = $ .'key'
'value' = $ .'value'
)
}
)
provider (
'objects' = {
let $'JSON' = "[[\"value\",\"key\"],[12,\"hello\"],[42,\"world\"]]" => parse as JSON => decorate as table {
'key': text
'value': integer
} || throw "invalid json"
walk $'JSON' as $ => create (
'key' = $ .'key'
'value' = $ .'value'
)
}
)
consumer ( )
routine 'test' on
do {
let $'JSON' = "\"hello world\"" => parse as JSON => decorate as text || throw "invalid json"
switch $'JSON' => is ( "hello world" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
provider (
'objects' = {
let $'CSV' = "key,val
hello,tester
world,jester" => parse as CSV => decorate as table {
'key': text
'val': text
} || throw "invalid csv"
walk $'CSV' as $ => create (
'key' = $ .'key'
'val' = $ .'val'
)
}
)
provider (
'objects' = {
let $'CSV' = "key,val
hello,tester
world,jester
" => parse as CSV => decorate as table {
'key': text
'val': text
} || throw "invalid csv"
walk $'CSV' as $ => create (
'key' = $ .'key'
'val' = $ .'val'
)
}
)
provider (
'objects' = {
let $'CSV' = "hello,tester
world,jester" => parse as CSV => decorate as list headless {
'key': text
'val': text
} || throw "invalid csv"
walk $'CSV' as $ => create (
'key' = $ .'key'
'val' = $ .'val'
)
}
)
provider (
'objects' = {
let $'CSV' = "key;val
hello;tester,user
world;jester" => parse as CSV separator: (";") => decorate as table {
'key': text
'val': text
} || throw "invalid csv"
walk $'CSV' as $ => create (
'key' = $ .'key'
'val' = $ .'val'
)
}
)
consumer ( )
routine 'test' on
do {
let $'value' = "2021-02-03T10:20:30Z" => parse as ISODateTime || throw "invalid format"
switch $'value' => is ( 212479064430 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "2021-08-04T10:20:30Z" => parse as ISODateTime || throw "invalid format"
switch $'value' => is ( 212494789230 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "2021-02-03T10:20:30+04" => parse as ISODateTime || throw "invalid format"
switch $'value' => is ( 212479050030 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "2021-08-04T10:20:30+04" => parse as ISODateTime || throw "invalid format"
switch $'value' => is ( 212494774830 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "2021-02-03T10:20:30-04" => parse as ISODateTime || throw "invalid format"
switch $'value' => is ( 212479078830 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "2021-08-04T10:20:30-04" => parse as ISODateTime || throw "invalid format"
switch $'value' => is ( 212494803630 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "42" => parse as decimal || throw "invalid format"
switch $'value' => is ( 42 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "42" => parse as decimal << ( 2 ) || throw "invalid format"
switch $'value' => is ( 4200 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "42.42" => parse as decimal << ( 2 ) || throw "invalid format"
switch $'value' => is ( 4242 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = ".42" => parse as decimal << ( 2 ) || throw "invalid format"
switch $'value' => is ( 42 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "42,42" => parse as decimal locale: "nl_NL" << ( 2 ) || throw "invalid format"
switch $'value' => is ( 4242 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'JSON' = "[42,true]" => parse as JSON || throw "invalid JSON"
let $'test' = $'JSON' => decorate as headless {
'A': integer
'B': boolean
} || throw "invalid format"
switch $'test'.'B' (
| true => switch $'test'.'A' => is ( 42 ) (
| true => no-op // Test successful
| false => throw "incorrect value `A`"
)
| false => throw "incorrect value `B`"
)
}
consumer ( )
routine 'test' on
do try {
let $'JSON' = "[true,false]" => parse as JSON || throw "invalid JSON"
let $'test' = $'JSON' => decorate as headless {
'value': boolean
} || throw "invalid format"
switch $'test'.'value' (
| true => throw "parse of invalid headless node succeeded"
| false => throw "incorrect value"
)
}
catch as $ => switch $ => is ( "invalid format" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do {
let $'XML' = "<root><data>hello</data><data>world</data></root>" => parse as XML || throw "invalid XML"
let $'test' = $'XML' => decorate as {
'root': {
'data': headless {
'A': text
'B': text
}
}
} || throw "invalid format"
switch $'test'.'root'.'data'.'A' => is ( "hello" ) (
| true => switch $'test'.'root'.'data'.'B' => is ( "world" ) (
| true => no-op // Test successful
| false => throw "incorrect value `B`"
)
| false => throw "incorrect value `A`"
)
}
consumer ( )
routine 'test' on
do {
let $'json raw' = "{\"value\":null}"
let $'result' = $'json raw' => parse as JSON => decorate as {
'value': optional text
} || throw "could not parse JSON"
switch $'result'.'value' get (
| value as $ => throw "produced wrong value"
| error => no-op // Test successful
)
}
consumer ( )
routine 'test' on
do {
let $'values' = "hello world" => parse as pattern ( 'text': $ text ) || throw "invalid format"
switch $'values'.'text' => is ( "hello world" ) (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
}
consumer ( )
routine 'test' on
do {
let $'values' = "[]" => parse as pattern ( 'text': "[" $ text "]" ) || throw "invalid format"
switch $'values'.'text' => is ( "" ) (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
}
define 'test' as pattern ( 'full': $ text { 2 , 4 } )
consumer ( )
routine 'test' on
do {
let $'vectors' as list {
'value': text
'match': boolean
} = {
create (
'value' = "a"
'match' = false
)
create (
'value' = "ab"
'match' = true
)
create (
'value' = "abc"
'match' = true
)
create (
'value' = "abcd"
'match' = true
)
create (
'value' = "abcde"
'match' = false
)
}
walk $'vectors' as $ => {
let $'vector' = $
switch $'vector'.'value' => parse as pattern 'test' (
| value as $ => switch $'vector'.'match' (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
| error => switch $'vector'.'match' (
| true => throw "incorrect parse result"
| false => no-op // Test successful
)
)
}
}
define 'test' as pattern ( 'full': $ text { , 4 } )
consumer ( )
routine 'test' on
do {
let $'vectors' as list {
'value': text
'match': boolean
} = {
create (
'value' = "a"
'match' = true
)
create (
'value' = "ab"
'match' = true
)
create (
'value' = "abc"
'match' = true
)
create (
'value' = "abcd"
'match' = true
)
create (
'value' = "abcde"
'match' = false
)
}
walk $'vectors' as $ => {
let $'vector' = $
switch $'vector'.'value' => parse as pattern 'test' (
| value as $ => switch $'vector'.'match' (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
| error => switch $'vector'.'match' (
| true => throw "incorrect parse result"
| false => no-op // Test successful
)
)
}
}
define 'test' as pattern ( 'full': $ text { 2 , } )
consumer ( )
routine 'test' on
do {
let $'vectors' as list {
'value': text
'match': boolean
} = {
create (
'value' = "a"
'match' = false
)
create (
'value' = "ab"
'match' = true
)
create (
'value' = "abc"
'match' = true
)
create (
'value' = "abcd"
'match' = true
)
create (
'value' = "abcde"
'match' = true
)
}
walk $'vectors' as $ => {
let $'vector' = $
switch $'vector'.'value' => parse as pattern 'test' (
| value as $ => switch $'vector'.'match' (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
| error => switch $'vector'.'match' (
| true => throw "incorrect parse result"
| false => no-op // Test successful
)
)
}
}
consumer ( )
routine 'test' on
do {
let $'values' = "prefix/-1234567890/suffix" => parse as pattern ( 'id': "prefix/" $ decimal "/suffix" ) || throw "invalid format"
switch $'values'.'id' => is ( -1234567890 ) (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
}
provider {
let $'values' = "42::64" => parse as pattern ( 'a': $ decimal "::" 'b': $ decimal ) || throw "invalid format"
(
'a' = $'values'.'a'
'b' = $'values'.'b'
)
}
consumer ( )
routine 'test' on
do {
let $'values' = "prefix/value/suffix" => parse as pattern ( 'value': text "/" $ text "/" text ) || throw "invalid format"
switch $'values'.'value' => is ( "value" ) (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
}
consumer ( )
routine 'test' on
do {
let $'values' = "prefix/value" => parse as pattern ( 'value': text "/" $ text ) || throw "invalid format"
switch $'values'.'value' => is ( "value" ) (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
}
consumer ( )
routine 'test' on
do {
let $'values' = "value/suffix" => parse as pattern ( 'value': $ text "/" text ) || throw "invalid format"
switch $'values'.'value' => is ( "value" ) (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
}
consumer ( )
routine 'test' on
do {
let $'values' = "prefix/part/suffix" => parse as pattern ( 'type': "prefix/" $ text "/suffix" ) || throw "invalid format"
switch $'values'.'type' => is ( "part" ) (
| true => no-op // Test successful
| false => throw "incorrect parse result"
)
}
consumer ( )
routine 'test' on
do {
let $'xml raw' = "
<root>
<element><![CDATA[correct value]]></element>
</root>
"
let $'result' = $'xml raw' => parse as XML => decorate as {
'root': {
'element': text
}
} || throw "could not parse XML"
switch $'result'.'root'.'element' => is ( "correct value" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'xml raw' = "
<root>
<element><![CDATA[correct]]>_<![CDATA[value]]></element>
</root>
"
let $'result' = $'xml raw' => parse as XML => decorate as {
'root': {
'element': text
}
} || throw "could not parse XML"
switch $'result'.'root'.'element' => is ( "correct_value" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
provider {
let $'xml raw' = "
<data>
<one>first entry</one>
<two>second entry</two>
</data>
"
let $'result' = $'xml raw' => parse as XML => decorate as {
'data': collection text
} || throw "could not parse XML"
(
'data' = walk $'result'.'data' as $ => create (
'key' = key
'value' = $
)
)
}
consumer ( )
routine 'test' on
do {
let $'xml raw' = "
<root>
<element/>
</root>
"
let $'result' = $'xml raw' => parse as XML => decorate as {
'root': {
'element': optional text
}
} || throw "could not parse XML"
switch $'result'.'root'.'element' get (
| value as $ => throw "produced wrong value"
| error => no-op // Test successful
)
}
consumer ( )
routine 'test' on
do {
let $'xml raw' = "<?xml version=\"1.0\" encoding=\"utf-8\"?>
<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">
<soap:Body>correct value</soap:Body>
</soap:Envelope>
"
let $'result' = $'xml raw' => parse as XML => decorate as {
'Envelope': {
'Body': text
}
} || throw "could not parse XML"
switch $'result'.'Envelope'.'Body' => is ( "correct value" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'xml raw' = "
<root>
<element>wrong value</element>
<element id=\"correct\">correct value</element>
</root>
"
let $'result' = $'xml raw' => parse as XML => decorate as {
'root': {
'element'< where 'id' is "correct"> : text
}
} || throw "could not parse XML"
switch $'result'.'root'.'element' => is ( "correct value" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
provider (
'keys' = {
let $'data' as list {
'key': text
'name': text
} = {
create (
'key' = "tester"
'name' = "one"
)
create (
'key' = "tester"
'name' = "two"
)
create (
'key' = "developer"
'name' = "three"
)
}
let $'buckets' = $'data' => partition on ( .'key' )
walk $'buckets' as $ => create (
'key' = key
'names' = walk $ as $ => create (
'name' = $ .'name'
)
)
}
)
provider (
'keys' = {
let $'data' as list {
'key': integer
'name': text
} = {
create (
'key' = 1
'name' = "one"
)
create (
'key' = 2
'name' = "two"
)
create (
'key' = 2
'name' = "three"
)
}
let $'buckets' = $'data' => partition on ( .'key' )
walk $'buckets' as $ => create (
'key' = key => serialize as decimal
'value' = key
'names' = walk $ as $ => create (
'name' = $ .'name'
)
)
}
)
provider (
'number' = var 'integer'
'text' = var 'text'
'file' = file (
token = var 'file-token'
extension = var 'file-exten'
)
)
consumer ( )
routine 'test' on
$'Context' =
do {
let $'name' = ^ $'Context'.'objects' => join ( .'key' )
switch $'name' => is ( "onetwo" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
$'Context' =
do {
let $'name' = ^ $'Context'.'objects' => join separator: ( ", " ) ( .'key' )
switch $'name' => is ( "one, two" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
$'Context' =
do {
let $'value' = ^ $'Context'.'objects' => product ( .'value' )
switch $'value' => is ( 12 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
$'Context' =
do {
let $'name' = ^ $'Context'.'objects' => shared ( .'name' ) || throw "object name not shared"
switch $'name' => is ( "tester" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
$'Context' =
do {
let $'value' = ^ $'Context'.'objects' => sum ( .'value' )
switch $'value' => is ( 1999 ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
$'Context' =
do {
let $'names' = ^ $'Context'.'objects' => unique ( .'name' )
switch $'names' => join separator: ( "," ) ( ) => is ( "tester,developer" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
provider (
'objects' = {
create (
'key' = "static"
)
try {
create (
'key' = "dynamic"
)
/* this should undo all changes made in under the try, but no other changes */
throw "test collection restore"
}
catch => no-op
}
)
provider (
'objects' = {
let $'set' as list {
'key': optional text
} = {
create ( )
create (
'key' = "one"
)
create ( )
create (
'key' = "two"
)
create ( )
}
walk $'set' as $ => try create (
'key' = $ .'key' get || throw "no key"
)
catch => no-op
}
)
provider (
'objects' = {
let $'data' as collection { } = {
create ["static"] ( )
try {
create ["dynamic"] ( )
/* this should undo all changes made in under the try, but no other changes */
throw "test collection restore"
}
catch => no-op
}
walk $'data' as $ => create (
'key' = key
)
}
)
provider (
'objects' = {
let $'data' as list {
'key': text
} = {
create (
'key' = "static"
)
try {
create (
'key' = "dynamic"
)
/* this should undo all changes made in under the try, but no other changes */
throw "test list restore"
}
catch => no-op
}
walk $'data' as $ => create (
'key' = $ .'key'
)
}
)
consumer ( )
routine 'test' on
do {
let $'schema' as {
'data' <'attribute': text > : text
} = (
'data' <
'attribute' = "hello"
> = "world"
)
switch $'schema'.'data'<'attribute'> => is ( "hello" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = 42 => serialize as decimal
switch $'value' => is ( "42" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = 4242 => serialize as decimal << ( 2 )
switch $'value' => is ( "42.42" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = 4242 => serialize as decimal locale: "nl_NL" << ( 2 )
switch $'value' => is ( "42,42" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = -4242 => serialize as decimal locale: "nl_NL" << ( 2 )
switch $'value' => is ( "-42,42" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'data' as list headless {
'a': optional text
'b': optional text
} = {
create (
'a' = "hello"
)
create (
'b' = "bye"
)
}
switch $'data' => serialize as JSON => is ( "[[\"hello\",null],[null,\"bye\"]]" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
$'Context' =
do switch $'Context' => serialize as JSON => is ( "{\"data\":[{\"key\":\"one\",\"choice\":[\"yes\",{}],\"group1\":{\"value\":42,\"description\":\"test entry one\"},\"group2\":{\"file\":[\"one\",\".txt\"]}},{\"key\":\"two\",\"choice\":[\"yes\",{}],\"group1\":{\"value\":-42,\"description\":\"test entry two\"},\"group2\":{\"file\":[\"two\",\".txt\"]}}]}" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
$'Context' =
do switch $'Context' => serialize as XML => is ( "<?xml version=\"1.0\"?>
<root><group><data><key>one</key><choice state=\"yes\"/><value>42</value><description>test entry one</description><file><token>one</token><exten>.txt</exten></file></data><data><key>two</key><choice state=\"yes\"/><value>-42</value><description>test entry two</description><file><token>two</token><exten>.txt</exten></file></data></group></root>
" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do {
let $'value' = true => serialize as JSON
switch $'value' => is ( "true" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = 42 => serialize as JSON
switch $'value' => is ( "42" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = "hello world" => serialize as JSON
switch $'value' => is ( "\"hello world\"" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = file ( token = "hello" extension = ".txt" ) => serialize as JSON
switch $'value' => is ( "[\"hello\",\".txt\"]" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'value' = list ( 42 , 12 , 24 ) => serialize as JSON
switch $'value' => is ( "[42,12,24]" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'source' as collection integer = {
create ["a"] 42
create ["b"] 12
create ["c"] 24
}
let $'value' = $'source' => serialize as JSON
switch $'value' => is ( "{\"a\":42,\"b\":12,\"c\":24}" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'data' as list choice ( 'one': 1 'two': 2 ) = {
create option 'one'
create option 'two'
}
switch $'data' => serialize as JSON => is ( "[1,2]" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'data' as list choice ( 'one' 'two' ) = {
create option 'one'
create option 'two'
}
switch $'data' => serialize as JSON => is ( "[\"one\",\"two\"]" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'data' as collection {
'description': text
'value': integer
} = {
create ["one"] (
'description' = "test entry one"
'value' = 42
)
create ["two"] (
'description' = "test entry two"
'value' = -42
)
}
switch $'data' => serialize as JSON => is ( "{\"one\":{\"description\":\"test entry one\",\"value\":42},\"two\":{\"description\":\"test entry two\",\"value\":-42}}" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'schema' as {
'data': collection {
'description': text
'value': integer
}
} = (
'data' = {
create ["one"] (
'description' = "test entry one"
'value' = 42
)
create ["two"] (
'description' = "test entry two"
'value' = -42
)
}
)
switch $'schema' => serialize as XML => is ( "<?xml version=\"1.0\"?>
<data><one><description>test entry one</description><value>42</value></one><two><description>test entry two</description><value>-42</value></two></data>
" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'schema' as {
'description': text
'value': integer
} = (
'description' = "hello world"
'value' = 42
)
switch $'schema' => serialize as XML => is ( "<?xml version=\"1.0\"?>
<description>hello world</description><value>42</value>
" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
provider {
no-op
(
'message' = "hello world"
)
}
consumer ( )
routine 'test' on
do try {
let $'object' as text @limit: { , 4 } = "hello world"
throw "length valid"
/* suppress unused warnings */
@log: $'object'
}
catch as $ => switch $ => is ( "text length limit violation" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try {
let $'object' as text @limit: { 40 , } = "hello world"
throw "length valid"
/* suppress unused warnings */
@log: $'object'
}
catch as $ => switch $ => is ( "text length limit violation" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do {
let $'document' = "{\"key\":\"hello world\"}" => parse as JSON || throw "invalid JSON"
let $'object' = $'document' => decorate as {
'key': text @limit: { , 40 }
} || throw "length limit exceeded"
switch $'object'.'key' => is ( "hello world" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do try {
let $'document' = "{\"key\":\"hello world\"}" => parse as JSON || throw "invalid JSON"
let $'object' = $'document' => decorate as {
'key': text @limit: { , 4 }
} || throw "length limit exceeded"
throw "length valid"
/* suppress unused warnings */
@log: $'object'.'key'
}
catch as $ => switch $ => is ( "length limit exceeded" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try {
let $'document' = "<key>hello world</key>" => parse as XML || throw "invalid XML"
let $'object' = $'document' => decorate as {
'key': text @limit: { , 4 }
} || throw "length limit exceeded"
throw "length valid"
/* suppress unused warnings */
@log: $'object'.'key'
}
catch as $ => switch $ => is ( "length limit exceeded" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
consumer ( )
routine 'test' on
do try throw "produce trace-log"
catch as $ => switch $ => is ( "produce trace-log" ) (
| true => switch error => is ( "VM-Errors:
VM-Stack:
> guard::tail
> root::tail
" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
| false => throw "caught wrong value"
)
consumer ( )
routine 'test' on
do try {
let $'data' as {
'key': text
'value': @protected text
} = (
'key' = "one"
'value' = "super secret value"
)
throw "produce trace-log"
/* suppress unused warnings */
@log: $'data' => serialize as JSON
}
catch as $ => switch $ => is ( "produce trace-log" ) (
| true => switch error => is ( "VM-Errors:
VM-Stack:
> block::+0
$data = <schema:node>
'key': text = \"one\"
'value': ...
> guard::tail
> root::tail
" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
| false => throw "caught wrong value"
)
provider (
'objects' = {
let $'dataset' = "{\"objects\":{\"one\":{\"success\":true,\"data\":{\"name\":\"tester\",\"age\":42}},\"two\":{\"success\":false,\"data\":{\"language\":\"English\",\"message\":\"This object contains an error message.\"}}}}" => parse as JSON => decorate as {
'objects': collection {
'success': boolean
'data': union (
'object' {
'name': text
'age': integer
}
'error' {
'language': text
'message': text
}
)
}
} || throw "dataset could not be decorated"
walk $'dataset'.'objects' as $ => create (
'key' = key
'result' = switch $ .'success' (
| true => {
let $'data' = $ .'data' => decorate as union 'object' || throw "object data could not be decorated"
create 'success' (
'name' = $'data'.'name'
'age' = $'data'.'age'
)
}
| false => {
let $'error' = $ .'data' => decorate as union 'error' || throw "error could not be decorated"
create 'failure' (
'language' = $'error'.'language'
'message' = $'error'.'message'
)
}
)
)
}
)
consumer ( )
routine 'test' on
do {
let $'dataset' as collection {
'success': boolean
'data': union (
'object' {
'name': text
'age': integer
}
'error' {
'language': text
'message': text
}
)
} = {
create ["one"] (
'success' = true
'data' = create 'object' (
'name' = "tester"
'age' = 42
)
)
create ["two"] (
'success' = false
'data' = create 'error' (
'language' = "English"
'message' = "This object contains an error message."
)
)
}
switch $'dataset' => serialize as JSON => is ( "{\"one\":{\"success\":true,\"data\":{\"name\":\"tester\",\"age\":42}},\"two\":{\"success\":false,\"data\":{\"language\":\"English\",\"message\":\"This object contains an error message.\"}}}" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}
consumer ( )
routine 'test' on
do {
let $'inject' = "<element>Hello world!</element>"
let $'document' as {
'root': text
} = (
'root' = $'inject'
)
let $'text' = $'document' => serialize as XML
switch $'text' => is ( "<?xml version=\"1.0\"?>
<root><element>Hello world!</element></root>
" ) (
| true => no-op // Test successful
| false => throw "produced wrong value"
)
}