If I broke the EBNF down a bit further such that:



By focusing on the lowest, deepest grouping (block_2), we get
" Inside nest 2 "
hi link trailer2 Number
syn match trailer2 "trailer2" skipwhite contained " end-group "
hi link header2 Identifier
syn match header2 "header2" skipwhite contained
\ nextgroup=trailer2
Then next up, block_1
" Inside nest 1 "
hi link trailer1 Number
syn match trailer1 "trailer1" skipwhite contained " end-group "
hi link block_2 Normal
syn region block_2 start="{" end="}" excludenl skipnl skipempty keepend contained
\ contains=header2,trailer2,test_any
\ nextgroup=trailer1
hi link header1 PreProc
syn match header1 "header1" skipnl skipempty skipwhite contained
\ nextgroup=
\ trailer1,
\ block_2, " block_2 is optional "
Finally, the top-level:
" top-level "
hi link trailer Identifier
syn match trailer "trailer" skipwhite contained
hi link block_1 Normal
"do not use `keepend` on top-level nest "
syn region block_1 start="{" end="}" skipnl skipwhite contained
\ contains=header1,trailer1,test_any
\ nextgroup=trailer
hi link header Statement
syn match header "^\s*header" skipwhite
\ nextgroup=
\ block_1,
\ trailer
and the end result is:

Tips
I used DrChip and this for troubleshooting
" Debugging pattern "
hi link test_any Error
syn match test_any "\v[a-zA-Z0-9\s\-\_\"]"
Of course, I could and have been be wrong, but here's what I've learned so far:
- always add '\v' to any OR-combo list like '\v(opt1|opt2|opt3)' in
syntax match
- always add '\v' to any OR-combo list like '\v[a-zA-Z0-9_]' in
syntax match
- place any 'contained' keyword at end of line (EOL) (readability)
- never use a '?' as a lone operator in
match statements
- '
contains=' ordering MATTERS in cluster statements
- '
region' seems to enjoy the 'keepend' option, but not at the top-level of nested regions.
- ordering between '
contains=' and 'nextgroup=' statements, first one wins in a match or a cluster statement (but not in a region)
- ordering between '
contains=' statements amongst themselves, first one wins
- ordering within '
contains=' statements, last one wins
- ordering within '
nextgroup=' statements, last one wins