S12) The EBNF of BPscript

Formal Grammar of the Language

Key Productions

A language in active development has divergences between the spec and the code. This article honestly documents them — what is implemented, what is planned, what diverges.

Where does this article fit in?

Technical reference. For EBNF notation, see L3. For BP3’s EBNF, see B10. Full specification: BPSCRIPT_EBNF.md (v0.6).


The Root

 

scene = { directive | actor_directive | declaration | cv_instance
        | macro | backtick_orphan | comment }
        , subgrammar+ , [ template_section ] ;

 


Directives

 

directive      = "@" , directive_body ;
actor_directive = "@actor" , IDENT , actor_props+ ;
actor_props     = IDENT , ":" , actor_value ;
actor_value     = IDENT [ "(" , param_pairs , ")" ] ;

directive_body = IDENT                                    (* @core *)
               | "controls"                               (* @controls *)
               | "mode" , ":" , MODE                      (* @mode:random *)
               | IDENT , ":" , value                      (* @tempo:120 *)
               ;

value = INT | FLOAT | neg_number | IDENT | ratio ;
neg_number = "-" , ( INT | FLOAT ) ;
ratio = INT , "/" , INT ;

 

The @actor directive is the central concept (S3):

@actor sitar  alphabet:sargam  scale:sargam_22shruti  transport:webaudio

 

Known properties of an actor: alphabet (required), scale (range/degrees → pitch via temperament,
if the actor has pitches), sounds (definitions by terminal: timbre, percussions, samples), transport (required),
eval (backtick runtime; if omitted, the actor’s backticks are not evaluated — default null).


Subgrammars and Rules

 

subgrammar = rule+ , [ separator ] ;
separator  = "-----" , { "-" } ;

mode_directive = "@mode:" , MODE , [ "(" , mode_modifier , { "," , mode_modifier } , ")" ] ;
MODE = "ord" | "random" | "lin" | "sub1" | "sub" | "tem" | "poslong" ;

rule = [ guard ] , { context } , lhs , arrow , rhs
     , [ runtime_qualifier ] , { qualifier } ;

guard = "[" , flag_expr , "]" ;
flag_expr = IDENT | IDENT , comparator , value ;
arrow = "->" | "<-" | "<>" ;

 

The mode is a block directive @mode:X that applies to the following subgrammar, up to the
next ----- separator. It is not an inline qualifier of the rule.


Qualifiers — [] engine vs () runtime

 

engine_qualifier = "[" , engine_pair , { "," , engine_pair } , "]"
                 | "[" , tempo_op , "]" ;
engine_pair = ENGINE_KEY , ":" , raw_value | ENGINE_KEY ;

ENGINE_KEY = "mode" | "scan" | "speed" | "weight" | "on_fail"
           | "tempo" | "meter" | "scale" | "retro" | "rotate"
           | "keyxpand" | "repeat" | "failed" | "stop" | "goto"
           | "striated" | "smooth" ;

runtime_qualifier = "(" , runtime_pair , { "," , runtime_pair } , ")" ;
runtime_pair = RUNTIME_KEY , ":" , value ;

 

  • [] = BP3 engine, strictly reserved keys (ENGINE_KEY)
  • () = runtime/dispatcher (downstream, outside repository), keys defined by controls.json
  • [] like () are suffix on a RHS element — no prefix qualifier ([X]A is not supported; to position a flag between elements, use the instantaneous form ![X])

The RHS

 

rhs_element = symbol_call | rest | prolongation | undetermined_rest
            | polymetric | backtick_inline | flag_mutation
            | simultaneous | trigger_in | symbol_with_trigger_in
            | capture | homomorphism_var
            | context_positive | context_negative
            | template_master | template_slave | nil_string ;

 

Simultaneity — Infix Syntax

 

simultaneous = symbol_call , { "!" , sim_target } ;
sim_target = symbol | symbol_call ;

 

Sa!dha!spotlightSimultaneousGroup (primary + secondary). The ! is infix, not standalone,
and exclusively temporal: a secondary is always a symbol (never a flag mutation).
Flags go into the rule’s [] qualifiers.

Incoming Trigger — Standalone and Attached Form

 

trigger_in = "<!" , IDENT ;
symbol_with_trigger_in = symbol_call , "<!" , IDENT ;

 

Sa<!syncSymbolWithTriggerIn: a symbol that waits for an incoming trigger before activating.


Lexemes — Pitfalls

  • Hyphen - in IDENT: allowed in non-terminals (LHS) only. Pre-scan.
  • # in IDENT: alterations (C#4). Be careful with the flat alphabet.
  • _ in IDENT: EBNF allows it but BP3 rejects _ in the alphabet. Known blocking point.

Known Divergences

Topic Spec Parser Status
@+ Absent Accepted (legacy @controls) Legacy
Negative INT FLOAT alone has - Parser detects - before INT Spec to correct
@mode → subgrammar No formal link parseSubgrammars(initialMode) Spec to correct
Sa!dha ! standalone Infix SimultaneousGroup Spec to correct
on_fail, hooks, timeout In the spec Not implemented Planned
Cross-rule braces Absent annotateUnbalancedBraces To document

Two old divergences are now resolved in the spec: Sa<!sync (attached form
SymbolWithTriggerIn) and cv_instance (env1(...) = lib.type(...)) are now described
by the EBNF.


Key Takeaways

  1. @actor is the central directive — links alphabet, scale, sounds, transport
  2. [] = engine (strict keys), () = runtime (free keys via controls.json)
  3. ! is infix (Sa!dha), not standalone, and exclusively temporal (no flag mutation)
  4. @mode:X is a block directive, not an inline qualifier
  5. Documented divergences between spec and parser — the language is actively evolving

Glossary

  • EBNF: Extended Backus-Naur Form — standard notation for syntax (ISO 14977)
  • SimultaneousGroup: AST node for Sa!dha!spotlight (primary + secondary, temporal)
  • SymbolWithTriggerIn: AST node for Sa<!sync (symbol + attached incoming trigger)
  • ENGINE_KEY: Reserved key for engine qualifiers []
  • raw_value: Raw value (any text up to , or ]) for engine keys

Links in the Series

  • L3 — EBNF Notation
  • B10 — BP3’s EBNF
  • S2 — The Tokens
  • S3 — The @actor Directive
  • S10 — The Compiler that Implements this Grammar
  • S13 (upcoming) — The AST produced by the parser

Prerequisites: S2, L3
Reading time: 10 min
Tags: #BPscript #EBNF #formal-grammar #specification


Next article: S13 (coming soon) — BPscript’s AST


Back to index