Skip to content

RCL Formal Specification

Rich Communication Language (RCL) - Formal Specification

Section titled “Rich Communication Language (RCL) - Formal Specification”

This document provides the formal specification for the Rich Communication Language (RCL) using an Extended Backus-Naur Form (EBNF)-like notation.

RCL is a data language designed to express any JSON-representable structure through a human-friendly syntax. It provides a generic Section/Attribute model where sections correspond to objects and can represent any hierarchical data. The language emphasizes simplicity, readability, and extensibility.

  • Generic Data Language: Any JSON structure can be expressed using Sections (objects) and Attributes (properties)
  • Indentation-based: Blocks are defined by indentation (like Python/YAML), no explicit end keywords
  • Extensible: New section types can be added without changing the core syntax
  • Type-aware: Built-in type tags for common data types with validation
  • RuleName ::= Definition : Defines a rule
  • 'literal' : Literal string or keyword (e.g., 'agent', ':')
  • TERMINAL_NAME : Terminal symbol/token (e.g., IDENTIFIER, STRING)
  • RuleName : Non-terminal symbol reference
  • A | B : Alternative (A or B)
  • (A B) : Sequence of A followed by B
  • A? : Zero or one occurrence (optional)
  • A* : Zero or more occurrences
  • A+ : One or more occurrences
  • // comment : Explanatory comments

RCL uses indentation-based blocks (Python-style) without explicit end keywords.

INDENT ::= // Increase in indentation level
DEDENT ::= // Decrease in indentation level
WS ::= /[ ]+/ // Whitespace (spaces/tabs), not newlines
NL ::= /[
]+/ // Newlines
SL_COMMENT ::= /#.*/ // Single-line comments
IDENTIFIER ::= /[A-Z]([A-Za-z0-9-_]|(\s(?=[A-Z0-9]))*/ // Title Case with spaces
ATTRIBUTE_KEY ::= /[a-z][a-zA-Z0-9_]*/ // lowerCamelCase
SECTION_TYPE ::= /[a-z][a-zA-Z0-9]*/ // lowercase section types
VARIABLE ::= /@[a-zA-Z_][a-zA-Z0-9_]*/ // @variable
PROPERTY_ACCESS ::= VARIABLE ('.' ATTRIBUTE_KEY)* // @obj.property
ATOM ::= /:[a-zA-Z_][a-zA-Z0-9_]*/ // :symbol
STRING ::= /"(\.|[^"\])*"/ // Double-quoted strings
TRIPLE_STRING_DELIM ::= /"""/ // Triple quote delimiter
NUMBER ::= /[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?/ // Numbers with optional decimals/exponents
ISO_DURATION ::= /(P((\d+Y)|(\d+M)|(\d+W)|(\d+D)|(T((\d+H)|(\d+M)|(\d+(\.\d+)?S))+))+)|([0-9]+(\.[0-9]+)?s)/
SPREAD ::= /\.\.\./ // Spread operator
INTERPOLATION_START ::= /#\{/
INTERPOLATION_END ::= /\}/
EMBEDDED_CODE ::= /\$((js|ts)?>)\s*[^
]*/ // $js> code or $> expr
MULTI_LINE_CODE_START ::= /\$((js|ts)?)>>>/ // $js>>> or $>>>
MULTI_LINE_CODE_END ::= /<\$/ // End marker
MULTILINE_STR_CLEAN ::= /\|\s*$/ // | (clean)
MULTILINE_STR_TRIM ::= /\|-\s*$/ // |- (trim trailing)
MULTILINE_STR_PRESERVE ::= /\+\|\s*$/ // +| (preserve leading)
MULTILINE_STR_PRESERVE_ALL ::= /\+\|\+\s*$/ // +|+ (preserve all)
MULTILINE_STR_END ::= /^\s*\|$/ // Line with only |
TYPE_TAG_NAME ::= /[a-zA-Z]+/ // email, phone, url, etc.
BooleanLiteral ::= 'True' | 'Yes' | 'On' | 'False' | 'No' | 'Off'
NullLiteral ::= 'Null' | 'None' | 'Void'
PrimitiveValue ::=
STRING
| MultiLineString
| NUMBER
| BooleanLiteral
| NullLiteral
| ATOM
| TypeTag
InterpolatedContent ::=
PlainText
| (INTERPOLATION_START (VARIABLE | PROPERTY_ACCESS | Value) INTERPOLATION_END)
TypeTag ::=
'<' TYPE_TAG_NAME (STRING | NUMBER | IDENTIFIER | ISO_DURATION) ('|' STRING)? '>'
Value ::=
PrimitiveValue
| IDENTIFIER
| VARIABLE
| PROPERTY_ACCESS
| List
| Dictionary
| EmbeddedCode
ContextualizedValue ::=
Value ('with' ParameterList)?
Parameter ::=
ATTRIBUTE_KEY ':' Value
| Value // Positional parameter
ParameterList ::=
Parameter (',' Parameter)*
List ::=
ParenthesesList
| InlineList
| BlockList
ParenthesesList ::= '(' (Value (',' Value)*)? ')'
InlineList ::= Value (',' Value)+ // 2+ items, parentheses optional
BlockList ::=
INDENT
(BlockListItem)+
DEDENT
BlockListItem ::= '-' Value
Dictionary ::=
BraceDictionary
| BlockDictionary
BraceDictionary ::= '{' (DictEntry (',' DictEntry)*)? '}'
BlockDictionary ::=
INDENT
(DictEntry)+
DEDENT
DictEntry ::= (ATTRIBUTE_KEY | STRING) ':' Value

MultiLineString ::= ( // Pipe-style (MULTILINE_STR_CLEAN | MULTILINE_STR_TRIM | MULTILINE_STR_PRESERVE | MULTILINE_STR_PRESERVE_ALL) INDENT StringContent DEDENT MULTILINE_STR_END ) | ( // Triple-quote style TRIPLE_STRING_DELIM (InterpolatedContent)* TRIPLE_STRING_DELIM )

StringContent ::= // Raw text content with proper indentation handling

EmbeddedCode ::=
SingleLineCode
| MultiLineCode
SingleLineCode ::= EMBEDDED_CODE
MultiLineCode ::=
MULTI_LINE_CODE_START
INDENT
CodeContent
DEDENT
MULTI_LINE_CODE_END
CodeContent ::= // Raw code content
Section ::=
SECTION_TYPE IDENTIFIER? ParameterList?
(INDENT
(SpreadDirective | Attribute | Section | MatchBlock | Value)*
DEDENT)?
SpreadDirective ::= SPREAD IDENTIFIER
Attribute ::= ATTRIBUTE_KEY ':' Value
MatchBlock ::=
'match' Value
INDENT
(MatchCase)+
DEDENT
MatchCase ::=
(STRING | NUMBER | ATOM) '->' Consequence
| ':default' '->' Consequence
Consequence ::=
ContextualizedValue
| FlowInvocation
| FlowTermination
FlowInvocation ::=
'start' IDENTIFIER ('with' ParameterList)?
('on' FlowResult '->' ResultHandler)*
FlowResult ::= ':end' | ':cancel' | ':error'
ResultHandler ::=
ContextOperation? TargetReference
ContextOperation ::=
'append' 'result' 'to' ContextVariable
| 'set' ContextVariable 'to' 'result'
| 'merge' 'result' 'into' ContextVariable
ContextVariable ::= VARIABLE | PROPERTY_ACCESS
TargetReference ::=
IDENTIFIER
| VARIABLE
| PROPERTY_ACCESS
| FlowTermination
FlowTermination ::= ':end' | ':cancel' | ':error'
RclFile ::=
(ImportStatement)*
(Section)*
ImportStatement ::=
'import' ImportPath ('as' IDENTIFIER)?
ImportPath ::= IDENTIFIER ('/' IDENTIFIER)*

IDENTIFIER (Title Case):

  • Must start with uppercase letter (A-Z)
  • Can contain letters, numbers, hyphens, underscores
  • Spaces allowed between Title Case words
  • Examples: Welcome Message, User-Profile, Order ID 123

ATTRIBUTE_KEY (lowerCamelCase):

  • Must start with lowercase letter (a-z)
  • Can contain letters, numbers, underscore
  • Examples: displayName, messageType, user_id

SECTION_TYPE (lowercase):

  • Must start with lowercase letter
  • Examples: agent, config, flow, messages, on
TypeAliasesExamplesNotes
email-<email user@example.com>
phonemsisdn<phone +1234567890>
url-<url https://example.com>
timet<time 4pm | UTC>Default UTC
datetimedate, dt<datetime 2024-07-26>, <datetime +5m>Relative times supported
zipcodezip<zip 94103>, <zip 25585-460 | BR>
durationttl<duration P1D>, <ttl 3600s>ISO 8601 or seconds
money-<money 3.50>, <money 10.99 | USD>Default USD
  • Files may contain imports followed by sections
  • All semantic validation happens at a higher level
  • RCL is purely a data representation language
  • Sections are generic containers mapping to objects
  • Section type is just a string identifier
  • Implicit ID: If no ID provided, title-case the section type
  • No reserved names at the syntax level
  • Any value can be contextualized with with keyword
  • Creates a value-context pair for semantic processing
  • Context is a map of key-value parameters
  • ...ID includes all attributes from referenced section
  • Works across imports with proper scoping
  • Applied before local attributes (can be overridden)
  • Variables (@name) reference runtime context
  • Property access (@obj.prop) navigates context objects
  • Context provided by runtime environment
  • start FlowName initiates execution of another flow
  • Parameters passed with with clause become flow's parameter context
  • Flow invocation pushes current state onto execution stack
  • Execution continues in the invoked flow's initial state
  • Three flow termination outcomes: :end, :cancel, :error
  • :end indicates successful completion and returns flow context
  • :cancel indicates user cancellation, no context returned
  • :error indicates execution error, optional error context returned
  • append result to @var - Adds result to array (creates if needed)
  • set @var to result - Overwrites variable with result
  • merge result into @var - Merges result object into existing object
  • Operations applied after flow completion before transitioning

Variable resolution follows precedence order:

  1. Flow parameters (highest priority)
  2. Flow context (local to current flow)
  3. Conversation context (global, persistent)
  • Flows terminate explicitly using :end, :cancel, or :error
  • Termination returns control to parent flow's result handler
  • Context isolation maintained between flows
# Import example
import Common/Config as BaseConfig
# Any section type with any attributes
agent Coffee Shop
displayName: "Quick Coffee"
brandName: "QuickCo"
config
...BaseConfig # Spread from import
description: "Order coffee for pickup"
phoneNumber: <phone +1-555-0123>
# Nested sections
flow Main Flow
start: Welcome
on Welcome
match @reply.text
"Order" -> Menu
"Hours" -> Info
:default -> Welcome
# Section with various content types
messages Messages
text Welcome "Welcome to #{@shopName}!"
suggestions
reply "Order Coffee"
reply "Store Hours"
# Lists
items: ("coffee", "tea", "juice")
prices: 3.50, 4.00, 2.50 # Parentheses optional
# Block list
menu:
- "Espresso"
- "Cappuccino"
- "Latte"
# Dictionary
order: {item: "Latte", size: "Large", price: <money 5.50>}
# Block dictionary
customer:
name: "John Doe"
phone: <phone +1234567890>
member: True
# Triple quotes
message: """
Thank you for your order!
Your #{@item} will be ready in 5 minutes.
"""
# Pipe notation with control
description: |
This is a multi-line
description with clean formatting
|
code: +|+
def process_order(item):
return f"Processing {item}"
|
flow MainFlow
on Welcome
match @reply.text
"Start Order" -> start OrderFlow
on :end -> append result to @orders -> ConfirmOrders
on :cancel -> Welcome
on :error -> ErrorHandler
flow PaymentFlow
on SelectPayment
"Credit Card" -> start PaymentProcessing with
amount: @totalPrice,
method: "card"
on :end -> set @receipt to result -> ShowReceipt
on :cancel -> SelectPayment
on :error -> PaymentFailed
flow OrderFlow
on SelectItems
"Add Coffee" -> start CoffeeFlow
on :end -> start PaymentFlow with items: result
on :end -> merge result into @order -> CompleteOrder
on :cancel -> SelectItems
on :cancel -> SelectItems
flow CoffeeFlow
on Confirmation
match @reply.text
"Confirm" -> :end # Return flow context
"Cancel" -> :cancel # Cancel without changes
"Error" -> :error # Error termination
flow MultiItemOrder
on AddItem
"Add Coffee" -> start ItemFlow
on :end -> append result to @cartItems -> AddItem
"Add Food" -> start FoodFlow
on :end -> append result to @cartItems -> AddItem
"Checkout" -> merge @cartItems into @order -> ProcessOrder