Skip to content

EBNF Grammar

This page presents the full formal grammar for Experience Notation, written in Extended Backus–Naur Form (EBNF). It defines the allowed structure and nesting of all constructs in a .expn file.

Experience Notation is whitespace-sensitive, with structure indicated through indentation. This grammar supports both human readability and precise programmatic parsing.


Grammar Version

  • EBNF-Version: 1.1
  • Syntax-Version: 1.0

Overview

The grammar below defines the syntax rules for:

  • File headers
  • Events and steps
  • Personas and actions
  • Conditional logic
  • Disruptions and environment
  • Tagging, metadata, and referencing

You can use this grammar to:

  • Build parsers or validators
    Implement tooling that programmatically parses, validates, or converts .expn files.

  • Understand the structure of Experience Notation documents
    The EBNF acts as a definitive reference for authors, reviewers, and tool developers working with the DSL.

  • Guide large language models (LLMs)
    Use the grammar as a structured prompt to help LLMs generate syntactically valid .expn journeys from natural language descriptions.


EBNF Definition

# Metadata:
#   Title: Experience Notation
#   EBNF-Version: 1.1
#   Syntax-Version: 1.0 
#   Author: Nikolaos Maniatis
#   Copyright © 2025 The Cato Bot Company Limited
#   Licence: Apache 2.0
#   Repository: https://github.com/context-notation/experience-notation


journey             = grammar_version , header , { blank | top_level } ;
# A journey document starts with the grammar version and a header,
# followed by any number of blank lines or top-level components (events or global personas).

grammar_version     = "EBNF-Version:" , wsp , version , nl ;
# Specifies which version of the Experience Notation grammar this document uses.

header              = "Syntax-Version:" , wsp , version , nl ,
                      "Journey-Title:" , wsp , text , nl ,
                      "Journey-Description:" , wsp , text , nl ;
# Declares the syntax version for this specific journey instance,
# and provides a title and description for human readability.

top_level           = event | persona_global ;
# Top-level elements can be Events or globally defined Personas.

# ----- EVENTS ---------------------------------------------
event               = "Event:" , wsp , text , nl , { indented_event_child } ;
# Each event begins with a title and can include indented children:
# steps, personas, metadata, tags, conditionals, etc.

indented_event_child = ind1 , ( id_line | tag_line | env_factors | parallel_events |
                                disruption | step | persona_block | conditional |
                                reference | comment ) ;
# Event children must be indented and may include metadata, environmental context,
# steps, personas, branching logic, references, or comments.

env_factors         = "Environmental-Factors:" , wsp , text , nl ;
# Describes environmental or contextual factors affecting the event.

parallel_events     = "Parallel-Events:" , wsp , csv(text) , nl ;
# Lists other events that occur concurrently with this one.

disruption          = "Disruption:" , wsp , text , nl ;
# Describes any interruption, exception, or breakdown within the journey.

# ----- STEPS ----------------------------------------------
step                = ind1 , "Step:" , wsp , text , nl ,
                      { ind2 , ( id_line | tag_line | user_role | action |
                                 ui_element | conditional | persona_block | reference ) } ;
# A step is a sequence within an event.
# It contains further indented items such as user actions, roles, conditions, and personas.
# Step must include at least one user_role and one action.

user_role           = "User:" , wsp , text , nl ;
# Declares the user or actor performing this step.

action              = "Action:" , wsp , text , nl ;
# Describes what the user or system does.

ui_element          = "UI-Element:" , wsp , text , nl ;
# Specifies the part of the interface involved in the step.

# ----- PERSONA (inline, context-specific) -----------------
persona_block       = ind2? , "Persona:" , wsp , text , nl ,
                      { ind3 , ( id_line | meta_line | experience |
                                 metrics | interaction | adaptation |
                                 reference | external_source_line ) } ;
# Defines a persona relevant to this event or step.
# May include structured metadata, behavioural traits, metrics, context, or an external reference.

experience          = "Experience:" , wsp , text , nl ;
interaction         = "Interaction:" , wsp , text , nl ;
adaptation          = "Adaptation:" , wsp , text , nl ;
# Narrative descriptions of how the persona engages, responds, or adapts during this point.

metrics             = "Metrics:" , wsp , "{" , wsp? , metric_pairs , wsp? , "}" , nl ;
# Captures performance or behavioural metrics in key=value format.

# ----- IDENTIFIERS & TAGS ---------------------------------
id_line             = "ID:" , wsp , identifier , nl ;
tag_line            = "Tag:" , wsp , csv(identifier) , nl ;
# Used to uniquely identify or categorise events, steps, or personas.

# ----- META BLOCK -----------------------------------------
meta_line           = "Meta:" , wsp , "{" , wsp? , meta_pairs , wsp? , "}" , nl ;
meta_pairs          = meta_pair , { "," , wsp? , meta_pair } ;
meta_pair           = identifier , "=" , text ;
# Provides structured, arbitrary metadata using key=value pairs.

# ----- METRICS -------------------------------------------
metric_pairs        = metric_pair , { "," , wsp? , metric_pair } ;
metric_pair         = metric_key , "=" , metric_value ;
metric_key          = identifier ;
metric_value        = number | text ;
# Metrics can be textual or numeric.

# ----- CONDITIONALS ---------------------------------------
conditional         = "Conditional:" , wsp , conditional_expr , nl ;
conditional_expr    = if_then | if_then_else ;
if_then             = "IF" , wsp , text , wsp , "THEN" , wsp , text ;
if_then_else        = if_then , wsp , "ELSE" , wsp , text ;
# Support for basic branching logic with optional ELSE clause.

# ----- REFERENCES ----------------------------------------
reference           = "Ref:" , wsp , identifier , nl ;
# Allows referencing other elements by their ID.

# ----- EXTERNAL REFERENCES --------------------------------
external_source_line = "External-Source:" , wsp , text , nl ;
# Specifies an external URI or identifier for a persona definition.
# This allows linking to persona data managed outside the current .expn document.

# ----- GLOBAL PERSONAS ------------------------------------
persona_global      = "Persona:" , wsp , text , nl ,
                      { ind1 , ( id_line | meta_line | experience |
                                 metrics | interaction | adaptation |
                                 reference | external_source_line ) } ;
# Allows personas to be declared globally, not just within specific steps or events.

# ----- LEXICAL STRUCTURE ----------------------------------
identifier          = alpha , { alpha | digit | "_" } ;
alpha               = "A"..."Z" | "a"..."z" ;
digit               = "0"..."9" ;
any_char            = ? any valid character except control characters ? ;
version             = digit , "." , digit ;
# A basic version format, e.g., 1.0

text                = { any_char - nl } ;
# Any string excluding newlines.

number              = [ "-" ] , digit , { digit } , [ "." , digit , { digit } ] ;
# Numeric values for metrics and other quantifiable data.

csv(X)              = X , { "," , wsp? , X } ;
# Helper function for comma-separated lists of values.

ind1                = 2*" " ;
ind2                = 4*" " ;
ind3                = 6*" " ;
# Indentation levels (2, 4, 6 spaces) to structure hierarchy.

wsp                 = " " | "\t" ;
nl                  = "\n" | "\r\n" ;
blank               = nl ;

Notes

  • Indentation is significant and should be consistent (spaces, not tabs)
  • Comments start with # and may appear on any line
  • String values can be quoted or unquoted, unless whitespace or symbols are used
  • Reserved keywords (like Event:, Step:, IF:) must appear exactly as defined

For structural constraints and field-level types, refer to the JSON Schema Reference.