Skip to content

Syntax Reference

Every formal grammar and production in this section is normative for the exact Standard revision. This section collects the lexical grammars referenced by the normative prose. Acceptance by a grammar establishes syntax only; the value remains subject to type, scope, structure, resolution, and deployment validation.

The grammars use ABNF from RFC 5234 as updated by RFC 7405. Terminals in quotation marks are case-sensitive.

ALPHA, DIGIT, HEXDIG, SP, and HTAB are the RFC 5234 core rules. ws admits only spaces and horizontal tabs; YAML folding and escape processing occur before these grammars are applied.

ws = *( SP / HTAB ) ; req:syntax.production.ws
rws = 1*( SP / HTAB ) ; req:syntax.production.rws
name = ( ALPHA / "_" ) *( ALPHA / DIGIT / "_" ) ; req:syntax.production.name
lower-name = %x61-7A *( %x61-7A / DIGIT / "_" ) ; req:syntax.production.lower-name

Where prose imposes a value-domain restriction in addition to ABNF, both apply. A parser MUST consume the complete string presented to a production.

Alternatives use longest-token matching; thus 1d is one duration token rather than an integer followed by a name.

interpolation = "{{" ws expression ws "}}" ; req:syntax.production.interpolation

The opening {{ begins an interpolation wherever the containing position permits one. The closing }} is the first such pair outside a string literal and outside balanced map braces within the expression.

Braces inside strings and escaped characters do not affect delimiter matching. An empty or unterminated interpolation is invalid.

The rules under Interpolation determine whole-value versus string interpolation; this production does not apply to mapping keys.

The validation requirements in this subsection are validator obligations of wf.template-rendering; Core implementations that do not claim that capability remain subject to the recognition, inference, and incomplete-result requirements under Templates and wf.render.

A template processor scans template content from left to right. Text continues until {{; every {{ begins one of the tags below, and the tag ends at the closing }} determined by the interpolation delimiter rules.

template-interpolation = "{{" ws expression ws "}}" ; req:syntax.production.template-interpolation
template-each-open = "{{" ws "#each" rws expression rws "as" rws name ws "}}" ; req:syntax.production.template-each-open
template-if-open = "{{" ws "#if" rws expression ws "}}" ; req:syntax.production.template-if-open
template-else = "{{" ws "else" ws "}}" ; req:syntax.production.template-else
template-each-close = "{{" ws "/each" ws "}}" ; req:syntax.production.template-each-close
template-if-close = "{{" ws "/if" ws "}}" ; req:syntax.production.template-if-close
template-include = "{{" ws ">" ws template-include-path ws "}}" ; req:syntax.production.template-include
template-include-path = 1*( %x21-7B / %x7D-10FFFF ) ; req:syntax.production.template-include-path

template-include-path excludes whitespace and }; it remains subject to the project-content path rules and therefore does not independently make an escaping or unresolvable path valid.

Tag kind is recognized before an embedded expression is parsed. A recognized tag containing an expression that violates the Expression Grammar is invalid with syntax.expression; a {{...}} sequence that matches no tag form is invalid with document.template.

Each template-each-open or template-if-open MUST have the corresponding close tag, tags MUST be properly nested, template-else can occur at most once directly within an open if block, and it MUST NOT occur elsewhere; a violation is invalid with document.template.

To produce literal {{ in template text, an author interpolates a string literal containing those characters, as in {{ '{{' }}.

expression = conditional ; req:syntax.production.expression
conditional = or-expr [ rws "if" rws or-expr rws "else" rws conditional ] ; req:syntax.production.conditional
or-expr = and-expr *( rws "or" rws and-expr ) ; req:syntax.production.or-expr
and-expr = equality *( rws "and" rws equality ) ; req:syntax.production.and-expr
equality = relational [ ( ws eq-op ws / rws membership-op rws ) relational ] ; req:syntax.production.equality
eq-op = "==" / "!=" ; req:syntax.production.eq-op
membership-op = "in" / ( "not" rws "in" ) ; req:syntax.production.membership-op
relational = additive [ ws rel-op ws additive ] ; req:syntax.production.relational
rel-op = "<=" / ">=" / "<" / ">" ; req:syntax.production.rel-op
additive = multiplicative *( ws add-op ws multiplicative ) ; req:syntax.production.additive
add-op = "+" / "-" ; req:syntax.production.add-op
multiplicative = unary *( ws mul-op ws unary ) ; req:syntax.production.multiplicative
mul-op = "*" / "//" / "/" / "%" ; req:syntax.production.mul-op
unary = ( "not" rws unary ) / ( "-" unary ) / filtered ; req:syntax.production.unary
filtered = postfix *( ws filter ) ; req:syntax.production.filtered
filter = "|" ws filter-name [ "(" ws [ arg-list ] ws ")" ] ; req:syntax.production.filter
filter-name = name [ "." name ] ; req:syntax.production.filter-name
arg-list = expression *( ws "," ws expression ) ; req:syntax.production.arg-list
postfix = primary *( member / index ) ; req:syntax.production.postfix
member = "." name ; req:syntax.production.member
index = "[" ws expression ws "]" ; req:syntax.production.index
primary = literal / binding-ref / list-lit / map-lit / group ; req:syntax.production.primary
binding-ref = name ; req:syntax.production.binding-ref
group = "(" ws expression ws ")" ; req:syntax.production.group
list-lit = "[" ws [ expression *( ws "," ws expression ) ] ws "]" ; req:syntax.production.list-lit
map-lit = "{" ws [ map-entry *( ws "," ws map-entry ) ] ws "}" ; req:syntax.production.map-lit
map-entry = ( string-lit / name ) ws ":" ws expression ; req:syntax.production.map-entry
literal = string-lit / duration-lit / number-lit / bool-lit / "null" ; req:syntax.production.literal
bool-lit = "true" / "false" ; req:syntax.production.bool-lit
number-lit = float-lit / int-lit ; req:syntax.production.number-lit
int-lit = "0" / ( %x31-39 *DIGIT ) ; req:syntax.production.int-lit
float-lit = 1*DIGIT "." 1*DIGIT [ exponent ] / 1*DIGIT exponent ; req:syntax.production.float-lit
exponent = ( "e" / "E" ) [ "+" / "-" ] 1*DIGIT ; req:syntax.production.exponent
signed-int-string = ["-"] int-lit ; req:syntax.production.signed-int-string
signed-float-string = ["-"] ( float-lit / int-lit ) ; req:syntax.production.signed-float-string
string-lit = DQUOTE *dq-char DQUOTE / "'" *sq-char "'" ; req:syntax.production.string-lit
dq-char = %x20-21 / %x23-5B / %x5D-10FFFF / escape ; req:syntax.production.dq-char
sq-char = %x20-26 / %x28-5B / %x5D-10FFFF / escape ; req:syntax.production.sq-char
escape = %x5C ( %x5C / DQUOTE / "'" / "n" / "r" / "t" / unicode-esc ) ; req:syntax.production.escape
unicode-esc = "u{" 1*6HEXDIG "}" ; req:syntax.production.unicode-esc

A binding-ref MUST NOT equal null, true, false, not, and, or, in, if, or else; those spellings are reserved expression keywords. This restriction does not apply when name is used for a member, filter-name component, or unquoted map-literal key.

A Unicode escape MUST identify a Unicode scalar value; a surrogate or a value above 10FFFF is invalid. Literal integer and float ranges are governed by Scalar Types.

A leading minus is always unary negation. Map-literal keys MUST be distinct after string escape processing.

Equality and relational expressions are non-associative, so a second comparison at the same level is invalid without grouping.

The following grammar defines the complete regular-expression language used by built-in filters and field-declaration pattern constraints. A construct not admitted by this grammar is invalid; in particular, the language has no lookaround, backreferences, named captures, Unicode property escapes, POSIX classes, octal escapes, class-set operations, or U flag.

regexp = alternation ; req:syntax.production.regexp
alternation = concatenation *( "|" concatenation ) ; req:syntax.production.alternation
concatenation = *repetition ; req:syntax.production.concatenation
repetition = atom [ quantifier [ "?" ] ] ; req:syntax.production.repetition
quantifier = "*" / "+" / "?" / "{" repeat-count "}" ; req:syntax.production.quantifier
/ "{" repeat-count "," [ repeat-count ] "}"
repeat-count = 1*DIGIT ; req:syntax.production.repeat-count
atom = regexp-literal / regexp-escape / class / "." ; req:syntax.production.atom
/ assertion / capture / noncapture / flag-change / flag-group
assertion = "^" / "$" / "\A" / "\z" / "\b" / "\B" ; req:syntax.production.assertion
capture = "(" alternation ")" ; req:syntax.production.capture
noncapture = "(?:" alternation ")" ; req:syntax.production.noncapture
flag-change = "(?" flag-spec ")" ; req:syntax.production.flag-change
flag-group = "(?" flag-spec ":" alternation ")" ; req:syntax.production.flag-group
flag-spec = set-flags [ "-" clear-flags ] / "-" clear-flags ; req:syntax.production.flag-spec
set-flags = 1*( "i" / "m" / "s" ) ; req:syntax.production.set-flags
clear-flags = 1*( "i" / "m" / "s" ) ; req:syntax.production.clear-flags
regexp-escape = "\" ( regexp-meta / "n" / "r" / "t" / hex-scalar ; req:syntax.production.regexp-escape
/ "d" / "D" / "w" / "W" / "s" / "S" )
regexp-meta = "." / "[" / "]" / "(" / ")" / "{" / "}" ; req:syntax.production.regexp-meta
/ "*" / "+" / "?" / "|" / "^" / "$" / "-" / "\"
hex-scalar = "x{" 1*6HEXDIG "}" ; req:syntax.production.hex-scalar
class = "[" [ "^" ] 1*class-item "]" ; req:syntax.production.class
class-item = class-atom [ "-" class-atom ] ; req:syntax.production.class-item
class-atom = class-literal / class-escape ; req:syntax.production.class-atom
class-escape = "\" ( regexp-meta / "n" / "r" / "t" / hex-scalar ; req:syntax.production.class-escape
/ "d" / "D" / "w" / "W" / "s" / "S" )

regexp-literal is one Unicode scalar value other than an ASCII control character or .\[](){}*+?|^$. class-literal is one Unicode scalar value other than an ASCII control character, \, ], or -; SPACE and otherwise-special regular-expression characters are literals in a class.

hex-scalar MUST identify a Unicode scalar value; a surrogate or value above 10FFFF is invalid. Each flag occurs at most once in each side of a flag-spec and MUST NOT occur on both sides.

A flag change applies through the end of its enclosing group; a flag group applies only within that group. In counted repetition, each count MUST be at most 1000 and the first count MUST NOT exceed a present second count.

Both endpoints of a class range MUST denote exactly one Unicode scalar value. A quantifier MUST NOT quantify an assertion, flag change, or another quantifier.

Captures are numbered from one by the source order of their opening parentheses; noncapturing and flag groups do not receive numbers. Regular-expression text is parsed only after its containing serialization and expression layers.

YAML escape and folding rules are applied first. For a filter argument, expression-string escape processing is then applied, and the resulting string is parsed by this grammar.

Thus the expression source matches('^\d+$') is invalid because \d is not an expression-string escape; it must be written matches('^\\d+$'). The containing YAML quoting style can require additional YAML-level escaping.

A field-declaration pattern is a serialization string rather than an expression string, so only its YAML processing precedes this grammar.

Matching operates on Unicode scalar values and selects the leftmost-first match. At the selected start position, an earlier alternative is preferred; a greedy quantifier prefers more repetitions and a lazy quantifier prefers fewer.

matches and field-declaration pattern perform an unanchored search. ^ and $ match only the beginning and end of input unless m is enabled, in which case they additionally match immediately after and before U+000A LF.

\A and \z always match only the beginning and end of input. . matches every Unicode scalar value except U+000A LF unless s is enabled.

The shorthand classes have fixed ASCII meanings: \d is [0-9], \w is [0-9A-Z_a-z], and \s contains exactly U+0009, U+000A, U+000C, U+000D, and U+0020; their uppercase forms are the respective complements. \b matches a boundary between a \w scalar and the beginning, end, or a non-\w scalar; \B matches every other position.

The i flag applies Unicode 17.0.0 simple case folding. An implementation MAY use a native engine mode, translate the pattern, or use another matching implementation only when the result is observably equivalent to these syntax and matching rules and the pinned Unicode version.

extract selects the first match and returns capture group 1 when the pattern contains a capture, otherwise the whole match. A group 1 that does not participate yields the empty string; only absence of a match yields null.

extract_all applies the same selection to each non-overlapping match. After a nonempty match, its next search begins at the match end.

After an empty match, the next search begins one Unicode scalar value later; an empty match at end of input is included once. For a fixed valid pattern, matching MUST run in time linear in the input length.

An implementation-specific pattern or input limit MUST be documented and is governed by deployment.limit when known before execution and flow.limit when exceeded by runtime input.

Implementation note. Conformance does not require a bespoke regular-expression engine.

The grammar deliberately excludes non-regular constructs such as backreferences and lookaround so that the linear-time requirement is achievable. An implementation can use an RE2-class engine, a host engine with a guaranteed non-backtracking mode, or parse the grammar defined by this specification to an internal representation and translate accepted constructs to an observably equivalent engine, rewriting shorthand classes, dot and anchor behavior, flags, and other constructs whose host meaning differs.

A per-engine translation table belongs with implementation guidance or a reference engine rather than defining additional standard semantics.

duration-lit = 1*duration-part ; req:syntax.production.duration-lit
duration-part = 1*DIGIT duration-unit ; req:syntax.production.duration-part
duration-unit = "ms" / "s" / "m" / "h" / "d" ; req:syntax.production.duration-unit

Component order and uniqueness are governed by Timestamps and Durations. The sign rule and expression-level negation behavior are defined under Timestamps and Durations.

Each component and the combined millisecond value MUST be representable as an int. Canonical duration syntax omits zero-valued leading components, uses the largest exact units in descending order, and represents zero as 0ms.

timestamp = full-date "T" full-time ; req:syntax.production.timestamp
full-date = date-year "-" date-month "-" date-day ; req:syntax.production.full-date
date-year = 4DIGIT ; req:syntax.production.date-year
date-month = 2DIGIT ; req:syntax.production.date-month
date-day = 2DIGIT ; req:syntax.production.date-day
full-time = time-hour ":" time-minute ":" time-second [ fraction ] offset ; req:syntax.production.full-time
time-hour = 2DIGIT ; req:syntax.production.time-hour
time-minute = 2DIGIT ; req:syntax.production.time-minute
time-second = 2DIGIT ; req:syntax.production.time-second
fraction = "." 1*DIGIT ; req:syntax.production.fraction
offset = "Z" / ( ( "+" / "-" ) time-hour ":" time-minute ) ; req:syntax.production.offset

The result MUST also be a valid RFC 3339 date-time: calendar dates and offset components MUST be in range. Hour 24 is invalid; the prohibition on second 60 is defined under Timestamps and Durations.

The offset is mandatory. Lowercase t and z are not accepted.

A timestamp can contain any positive number of fractional-second digits. Fractional-second decoding uses the truncation rule defined for the date-time format.

Canonical timestamp formatting is defined under Timestamps and Durations.

operation-key = action-ref / construct-ref ; req:syntax.production.operation-key
action-ref = catalog-namespace "." name ; req:syntax.production.action-ref
construct-ref = "wf." construct ; req:syntax.production.construct-ref
construct = "value" / "render" / "call" / "route" / "choose" ; req:syntax.production.construct
/ "for_each" / "parallel" / "repeat" / "wait"
/ "wait_for" / "stop" / "fail" / "run" / "agent"

An action reference’s first name is a connector namespace and its second is an action name. Trigger references use the same two-name shape but occur only in trigger positions.

Versions are resolution inputs and MUST NOT appear in an operation key. The wf. alternatives are closed for the Standard revision. wf.run is reserved but invalid in this revision, and wf.agent activates wf.agent-execution.

type-expr = "string" / "int" / "float" / "bool" / "timestamp" ; req:syntax.production.type-expr
/ "duration" / "file" / "json"
/ enum-type / list-type / map-type
enum-type = "enum[" ws enum-member *( ws "," ws enum-member ) ws "]" ; req:syntax.production.enum-type
enum-member = string-lit ; req:syntax.production.enum-member
list-type = "list[" ws type-expr ws "]" ; req:syntax.production.list-type
map-type = "map[" ws "string" ws "," ws type-expr ws "]" ; req:syntax.production.map-type

Each enum member is the string value obtained by decoding its string literal under the Expression Grammar. Decoded enum-member distinctness is governed by Type Expressions.

The separation between named object schemas and type expressions is defined under Type Expressions. The YAML representation of a type expression is governed by Type Expressions.

declared-name = name ; req:syntax.production.declared-name
catalog-namespace = lower-name ; req:syntax.production.catalog-namespace
step-path = step-path-segment *( "." step-path-segment ) ; req:syntax.production.step-path
step-path-segment = declared-name [ occurrence-index ] / reserved-segment ; req:syntax.production.step-path-segment
reserved-segment = "(" lower-name ")" [ occurrence-index ] ; req:syntax.production.reserved-segment
occurrence-index = "[" ( "0" / ( %x31-39 *DIGIT ) ) "]" ; req:syntax.production.occurrence-index
connector-code = ( ALPHA / DIGIT ) ; req:syntax.production.connector-code
*( ALPHA / DIGIT / "_" / "." / "-" )
connector-code-segment = ( ALPHA / DIGIT ) *( ALPHA / DIGIT / "_" / "-" ) ; req:syntax.production.connector-code-segment
connector-code-prefix-pattern = connector-code-segment *( "." connector-code-segment ) ".*" ; req:syntax.production.connector-code-prefix-pattern
http-status-pattern = DIGIT "xx" ; req:syntax.production.http-status-pattern
vendor-auth-scheme = lower-name 1*( "." lower-name ) ; req:syntax.production.vendor-auth-scheme
author-failure-code = declared-name ; req:syntax.production.author-failure-code
standard-error-code = lower-name 1*( "." lower-name ) ; req:syntax.production.standard-error-code

An http-status-pattern matches a connector code consisting of exactly three decimal digits when its first digit is the pattern’s digit. A connector-code-prefix-pattern matches any connector code that begins with the pattern text before *; the included dot makes this a dotted-prefix match.

step-path defines the complete Standard path form. Parentheses distinguish a reserved segment from every author-declared name.

A Standard profile allocates a new reserved segment name only through the Reserved Step-Path Segment Registry. The use of author-failure-code by wf.fail is defined under wf.fail.