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.
Notation and ABNF Conventions
Section titled “Notation and ABNF Conventions”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.wsrws = 1*( SP / HTAB ) ; req:syntax.production.rwsname = ( ALPHA / "_" ) *( ALPHA / DIGIT / "_" ) ; req:syntax.production.namelower-name = %x61-7A *( %x61-7A / DIGIT / "_" ) ; req:syntax.production.lower-nameWhere 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 Grammar
Section titled “Interpolation Grammar”interpolation = "{{" ws expression ws "}}" ; req:syntax.production.interpolationThe 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.
Template Grammar
Section titled “Template Grammar”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-interpolationtemplate-each-open = "{{" ws "#each" rws expression rws "as" rws name ws "}}" ; req:syntax.production.template-each-opentemplate-if-open = "{{" ws "#if" rws expression ws "}}" ; req:syntax.production.template-if-opentemplate-else = "{{" ws "else" ws "}}" ; req:syntax.production.template-elsetemplate-each-close = "{{" ws "/each" ws "}}" ; req:syntax.production.template-each-closetemplate-if-close = "{{" ws "/if" ws "}}" ; req:syntax.production.template-if-closetemplate-include = "{{" ws ">" ws template-include-path ws "}}" ; req:syntax.production.template-includetemplate-include-path = 1*( %x21-7B / %x7D-10FFFF ) ; req:syntax.production.template-include-pathtemplate-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 Grammar
Section titled “Expression Grammar”expression = conditional ; req:syntax.production.expressionconditional = or-expr [ rws "if" rws or-expr rws "else" rws conditional ] ; req:syntax.production.conditionalor-expr = and-expr *( rws "or" rws and-expr ) ; req:syntax.production.or-exprand-expr = equality *( rws "and" rws equality ) ; req:syntax.production.and-exprequality = relational [ ( ws eq-op ws / rws membership-op rws ) relational ] ; req:syntax.production.equalityeq-op = "==" / "!=" ; req:syntax.production.eq-opmembership-op = "in" / ( "not" rws "in" ) ; req:syntax.production.membership-oprelational = additive [ ws rel-op ws additive ] ; req:syntax.production.relationalrel-op = "<=" / ">=" / "<" / ">" ; req:syntax.production.rel-opadditive = multiplicative *( ws add-op ws multiplicative ) ; req:syntax.production.additiveadd-op = "+" / "-" ; req:syntax.production.add-opmultiplicative = unary *( ws mul-op ws unary ) ; req:syntax.production.multiplicativemul-op = "*" / "//" / "/" / "%" ; req:syntax.production.mul-opunary = ( "not" rws unary ) / ( "-" unary ) / filtered ; req:syntax.production.unaryfiltered = postfix *( ws filter ) ; req:syntax.production.filteredfilter = "|" ws filter-name [ "(" ws [ arg-list ] ws ")" ] ; req:syntax.production.filterfilter-name = name [ "." name ] ; req:syntax.production.filter-namearg-list = expression *( ws "," ws expression ) ; req:syntax.production.arg-listpostfix = primary *( member / index ) ; req:syntax.production.postfixmember = "." name ; req:syntax.production.memberindex = "[" ws expression ws "]" ; req:syntax.production.indexprimary = literal / binding-ref / list-lit / map-lit / group ; req:syntax.production.primarybinding-ref = name ; req:syntax.production.binding-refgroup = "(" ws expression ws ")" ; req:syntax.production.grouplist-lit = "[" ws [ expression *( ws "," ws expression ) ] ws "]" ; req:syntax.production.list-litmap-lit = "{" ws [ map-entry *( ws "," ws map-entry ) ] ws "}" ; req:syntax.production.map-litmap-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.literalbool-lit = "true" / "false" ; req:syntax.production.bool-litnumber-lit = float-lit / int-lit ; req:syntax.production.number-litint-lit = "0" / ( %x31-39 *DIGIT ) ; req:syntax.production.int-litfloat-lit = 1*DIGIT "." 1*DIGIT [ exponent ] / 1*DIGIT exponent ; req:syntax.production.float-litexponent = ( "e" / "E" ) [ "+" / "-" ] 1*DIGIT ; req:syntax.production.exponentsigned-int-string = ["-"] int-lit ; req:syntax.production.signed-int-stringsigned-float-string = ["-"] ( float-lit / int-lit ) ; req:syntax.production.signed-float-string
string-lit = DQUOTE *dq-char DQUOTE / "'" *sq-char "'" ; req:syntax.production.string-litdq-char = %x20-21 / %x23-5B / %x5D-10FFFF / escape ; req:syntax.production.dq-charsq-char = %x20-26 / %x28-5B / %x5D-10FFFF / escape ; req:syntax.production.sq-charescape = %x5C ( %x5C / DQUOTE / "'" / "n" / "r" / "t" / unicode-esc ) ; req:syntax.production.escapeunicode-esc = "u{" 1*6HEXDIG "}" ; req:syntax.production.unicode-escA 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.
Regular Expression Grammar
Section titled “Regular Expression Grammar”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.regexpalternation = concatenation *( "|" concatenation ) ; req:syntax.production.alternationconcatenation = *repetition ; req:syntax.production.concatenationrepetition = atom [ quantifier [ "?" ] ] ; req:syntax.production.repetitionquantifier = "*" / "+" / "?" / "{" 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-groupassertion = "^" / "$" / "\A" / "\z" / "\b" / "\B" ; req:syntax.production.assertioncapture = "(" alternation ")" ; req:syntax.production.capturenoncapture = "(?:" alternation ")" ; req:syntax.production.noncaptureflag-change = "(?" flag-spec ")" ; req:syntax.production.flag-changeflag-group = "(?" flag-spec ":" alternation ")" ; req:syntax.production.flag-groupflag-spec = set-flags [ "-" clear-flags ] / "-" clear-flags ; req:syntax.production.flag-specset-flags = 1*( "i" / "m" / "s" ) ; req:syntax.production.set-flagsclear-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.classclass-item = class-atom [ "-" class-atom ] ; req:syntax.production.class-itemclass-atom = class-literal / class-escape ; req:syntax.production.class-atomclass-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 Semantics
Section titled “Matching Semantics”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 Grammar
Section titled “Duration Grammar”duration-lit = 1*duration-part ; req:syntax.production.duration-litduration-part = 1*DIGIT duration-unit ; req:syntax.production.duration-partduration-unit = "ms" / "s" / "m" / "h" / "d" ; req:syntax.production.duration-unitComponent 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 Grammar
Section titled “Timestamp Grammar”timestamp = full-date "T" full-time ; req:syntax.production.timestampfull-date = date-year "-" date-month "-" date-day ; req:syntax.production.full-datedate-year = 4DIGIT ; req:syntax.production.date-yeardate-month = 2DIGIT ; req:syntax.production.date-monthdate-day = 2DIGIT ; req:syntax.production.date-dayfull-time = time-hour ":" time-minute ":" time-second [ fraction ] offset ; req:syntax.production.full-timetime-hour = 2DIGIT ; req:syntax.production.time-hourtime-minute = 2DIGIT ; req:syntax.production.time-minutetime-second = 2DIGIT ; req:syntax.production.time-secondfraction = "." 1*DIGIT ; req:syntax.production.fractionoffset = "Z" / ( ( "+" / "-" ) time-hour ":" time-minute ) ; req:syntax.production.offsetThe 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 Grammar
Section titled “Operation-Key Grammar”operation-key = action-ref / construct-ref ; req:syntax.production.operation-keyaction-ref = catalog-namespace "." name ; req:syntax.production.action-refconstruct-ref = "wf." construct ; req:syntax.production.construct-refconstruct = "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-Expression Grammar
Section titled “Type-Expression Grammar”type-expr = "string" / "int" / "float" / "bool" / "timestamp" ; req:syntax.production.type-expr / "duration" / "file" / "json" / enum-type / list-type / map-typeenum-type = "enum[" ws enum-member *( ws "," ws enum-member ) ws "]" ; req:syntax.production.enum-typeenum-member = string-lit ; req:syntax.production.enum-memberlist-type = "list[" ws type-expr ws "]" ; req:syntax.production.list-typemap-type = "map[" ws "string" ws "," ws type-expr ws "]" ; req:syntax.production.map-typeEach 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.
Names, Paths, and Codes
Section titled “Names, Paths, and Codes”declared-name = name ; req:syntax.production.declared-namecatalog-namespace = lower-name ; req:syntax.production.catalog-namespace
step-path = step-path-segment *( "." step-path-segment ) ; req:syntax.production.step-pathstep-path-segment = declared-name [ occurrence-index ] / reserved-segment ; req:syntax.production.step-path-segmentreserved-segment = "(" lower-name ")" [ occurrence-index ] ; req:syntax.production.reserved-segmentoccurrence-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-segmentconnector-code-prefix-pattern = connector-code-segment *( "." connector-code-segment ) ".*" ; req:syntax.production.connector-code-prefix-patternhttp-status-pattern = DIGIT "xx" ; req:syntax.production.http-status-patternvendor-auth-scheme = lower-name 1*( "." lower-name ) ; req:syntax.production.vendor-auth-schemeauthor-failure-code = declared-name ; req:syntax.production.author-failure-codestandard-error-code = lower-name 1*( "." lower-name ) ; req:syntax.production.standard-error-codeAn 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.