Aller au contenu
roomi fields

BPscript · S13 · Référence

L'AST de BPscript

Comprendre la structure des nœuds et la traduction vers la grammaire BP3

Publié le 21 mai 2026 · Read in English

Anatomie du code intermédiaire

ActorDirective, Symbol.actor, CVInstance

Le parser produit un arbre. L’encoder le traduit en grammaire BP3. Entre les deux, l’AST capture tout : acteurs, symboles, qualificateurs, CV.

Où se situe cet article ?

Référence technique. Pour l’AST en général, voir L4. Pour la grammaire que le parser implémente, voir S12. Spécification complète : BPSCRIPT_AST.md (v0.7).


Le nœud racine : Scene

Scene {
  type: "Scene"
  directives: Directive[]
  actors: ActorDirective[]         // @actor directives
  scenes: SceneDirective[]         // @scene directives (scènes filles)
  exposes: ExposeDirective[]       // @expose (flags visibles au parent)
  maps: MapDirective[]             // @map (correspondances E/S CC/OSC ↔ flags/triggers)
  aliases: AliasDirective[]        // @alias (points d'E/S nommés)
  labels: LabelDirective[]         // @label (déclarations de label)
  declarations: Declaration[]
  macros: Macro[]
  cvInstances: CVInstance[]
  backticks: BacktickOrphan[]
  subgrammars: Subgrammar[]
  templates: TemplateEntry[] | null  // section @templates (optionnelle)
}

La racine collecte tout le niveau supérieur de la scène : les rattachements (actors), l’orchestration multi-scènes (scenes, exposes, maps, aliases, labels), les déclarations, les macros, les objets CV, et les sous-grammaires qui contiennent les règles.


ActorDirective — le nœud central

ActorDirective {
  type: "ActorDirective"
  name: string                     // "sitar", "tabla", "lights"
  properties: {
    alphabet: string               // référence vers alphabets.json ("sargam")
    scale: string | null           // gamme/degrés → hauteur via tempérament (null = pas de hauteur)
    sounds: string | null          // définitions par terminal: timbre, percussions, échantillons
    transport: TransportRef        // destination de rendu
    eval: string | null            // clé d'eval pour backticks (null = aucun REPL)
  }
  line: number
}

TransportRef {
  type: "TransportRef"
  key: string                      // "webaudio", "midi", "osc", "dmx"
  params: { [key: string]: any }   // { ch: 10 }, { port: 57110 }, {}
}

Exemple :

@actor sitar  alphabet:sargam  scale:sargam_22shruti  transport:webaudio
→ { name:"sitar", properties: { alphabet:"sargam", scale:"sargam_22shruti", sounds:null,
                                transport:{key:"webaudio", params:{}}, eval:null } }

La propriété scale porte la gamme (degrés → hauteur via le tempérament) ; sounds porte les définitions par terminal (timbre, percussions, échantillons) pour les acteurs sans hauteur ou à timbre spécifique. Un acteur sans scale n’a pas de hauteur résolue (ex: un acteur percussif).


Symbol — avec acteur

Symbol {
  type: "Symbol"
  name: string                     // "Sa", "C4", "tin"
  actor: string | null             // "sitar" (explicite) ou null
  line: number
}
  • actor: null → résolution implicite (le compilateur cherche l’unique acteur qui contient ce symbole), ou non-terminal (qui n’a pas d’acteur)
  • actor: "sitar" → résolution explicite via notation pointée (sitar.Sa)

Le Symbol ne porte pas ses qualificateurs comme champs propres. Tout élément RHS (donc le Symbol) peut porter des qualificateurs via deux champs communs (voir ci-dessous).


Qualificateurs portés par les éléments RHS

Les qualificateurs ne sont pas des champs spécifiques au Symbol : tout RhsElement peut porter des qualificateurs moteur [] et/ou runtime (), toujours en suffixe (collés à droite de l’élément, sans espace), via un seul champ commun :

RhsElement {
  ...                                          // propriétés spécifiques au type
  suffixQualifiers: (Qualifier | RuntimeQualifier)[] | null  // [] ou () collés à droite : A[weight:50], A(vel:80)
}

Le tokenizer marque chaque token avec spaceBefore : un [ ou ( sans espace avant s’attache comme suffixe à l’élément précédent. Le parser ne produit pas de qualificateur préfixe ; pour positionner un flag entre deux éléments, on utilise la forme instantanée ![X].

Qualifier {
  type: "Qualifier"
  pairs: QualPair[]
  tempoOp: TempoOp | null          // [/2], [*3]... — mutuellement exclusif avec pairs
}

QualPair {
  type: "QualPair"
  key: string                      // ENGINE_KEY : mode, scan, weight, tempo, scale...
  value: string | number | boolean // "random", 50, "1/2", "inf", true (clé nue)
  decrement: number | null         // pour weight:50-12
}

RuntimeQualifier {
  type: "RuntimeQualifier"
  pairs: { key: string, value: string | number | boolean }[]   // [{key:"vel", value:120}]
}

Sa(vel:120)[speed:2] produit, sur le nœud Symbol("Sa") :

  • suffixQualifiers: [{ type:"RuntimeQualifier", pairs:[{key:"vel", value:120}] }, { type:"Qualifier", pairs:[{key:"speed", value:2}] }]

Les pairs runtime sont des objets nus { key, value } — pas de champ type (contrairement aux QualPair du Qualifier moteur). La portée (symbole / règle / groupe / instantané) n’est pas stockée sur le nœud : elle est déduite de la position dans l’AST par l’encodeur. Les RuntimeQualifiers compilent en _script(CT n) dans la control table.


Control — contrôle BP3 dans le flux

Control {
  type: "Control"
  name: string        // vel, tempo, goto, striated, smooth, destru, stop...
  args: string[]      // ["120"], ["2","1"] ; [] pour un contrôle sans argument
}

Forme parsée d’un contrôle BP3 écrit directement dans le flux : vel(120) → { name:"vel", args:["120"] }, goto(2,1) → { name:"goto", args:["2","1"] }, striated → { name:"striated", args:[] }. Distinct de l’InstantControl (!(...)) et du RuntimeQualifier ((...) suffixe d’un symbole).


SymbolWithTriggerIn — symbole en attente de trigger

SymbolWithTriggerIn {
  type: "SymbolWithTriggerIn"
  symbol: Symbol           // le symbole porteur
  triggers: TriggerIn[]    // un ou plusieurs trigger-in attachés
}

Émis pour Sa<!sync1 : un symbole qui attend un trigger entrant (<!) avant de se déclencher.


CVInstance

CVInstance {
  type: "CVInstance"
  name: string                      // "env1", "lfo1"
  lib: string | null                // lib source ("mod", null pour backtick)
  objectType: string                // type d'objet ("adsr", "lfo", "ramp", "backtick")
  args: (number | string)[]         // arguments positionnels
  namedArgs: { [key: string]: any } // arguments nommés (attack:10, rate:4)
  code: string | null               // code backtick (si objectType == "backtick")
  line: number
}

La déclaration est descriptive : le nœud ne porte plus ni cible ni transport. Le branchement se fait au point de paramètre, (cutoff: env1), porté par un RuntimeQualifier (champ subject) sur la note.

Exemples :

  • cv env1 : mod.adsr(attack:10, decay:100, sustain:0.7, release:200) → { name:"env1", lib:"mod", objectType:"adsr", args:[], namedArgs:{attack:10, decay:100, sustain:0.7, release:200} }
  • cv lfo1 : mod.lfo(rate:4, depth:50) → { name:"lfo1", lib:"mod", objectType:"lfo", args:[], namedArgs:{rate:4, depth:50} }

Subgrammar et Rule

Subgrammar {
  type: "Subgrammar"
  rules: Rule[]
  index: number
  mode: string | null              // "random", "ord"... (de @mode:X)
  modifiers: ModeModifier[] | null // directives de sous-grammaire (@mode:X(destru, mm:60))
}

Rule {
  type: "Rule"
  guard: Guard | Guard[] | null    // un ou plusieurs guards (AND)
  contexts: Context[]
  lhs: LhsElement[]
  arrow: "->" | "<-" | "<>"
  rhs: RhsElement[]
  flags: FlagExpr[]                // mutations collectées, émises en fin de règle : /phase=2/ /Atrans/
  qualifiers: Qualifier[]          // [mode:random, scan:left] en fin de règle (moteur [])
  runtimeQualifier: RuntimeQualifier | null  // suffixe () sur la règle : S -> C4 D4 (vel:80)
  line: number
}

Guard {
  type: "Guard"
  flag: string
  operator: "==" | "!=" | ">" | "<" | ">=" | "<=" | "+" | "-" | null  // null = bare flag
  value: number | string | null    // null pour bare flag [Ideas]
  mutates: boolean
}

Traduction vers BP3

Nœud ASTBP3 compilé
ActorDirective(pas de BP3 — produit le terminalActorMap)
Symbol("Sa", actor:"sitar")bolSa (nom opaque)
RuntimeQualifier({vel:120})_script(CT 0) + controlTable[CT0]={vel:120}
Qualifier("speed", 2){2, ...} ou /2
Control("vel", ["120"])contrôle BP3 dans le flux
Guard("phase", "==", 1)/phase=1/
FlagExpr("phase", "=", 2)/phase=2/
Subgrammar(mode:"random")RND en mode_line
Polymetric([v1, v2]){v1, v2}

Les terminaux sont des noms simples préfixés bol. Le resolver en aval (hors dépôt) fait correspondre noms → fréquences via les acteurs.


Ce qu’il faut retenir

  1. ActorDirective capture le rattachement complet (alphabet, scale, sounds, transport, eval)
  2. Symbol.actor : null = implicite, "sitar" = explicite — les qualifiers ne sont pas des champs du Symbol, ils sont portés par suffixQualifiers ([] comme () sont suffixe)
  3. Qualificateurs scindés : Qualifier ([], moteur) et RuntimeQualifier ((), runtime — pairs nues, portée déduite de la position)
  4. Control : contrôle BP3 dans le flux ; SymbolWithTriggerIn : symbole en attente de <!
  5. CVInstance : objets CV déclarés en tête de scène (ADSR, LFO, ramp) — objectType, args positionnels, namedArgs

Glossaire

  • AST : Abstract Syntax Tree — représentation arborescente du code après parsing
  • ActorDirective : Nœud déclarant un acteur avec ses propriétés (alphabet, scale, sounds, transport…)
  • Qualifier : Qualificateur [] pour le moteur BP3 (speed, weight, tempo…)
  • RuntimeQualifier : Qualificateur () pour le runtime (vel, pan, wave…) — compilé en _script(CTn)
  • Control : Contrôle BP3 écrit directement dans le flux (vel(120), goto(2,1))
  • SymbolWithTriggerIn : Symbole attendant un trigger entrant <! avant de se déclencher
  • CVInstance : Déclaration d’un objet CV en tête de scène (enveloppe, LFO, ramp)
  • terminalActorMap : Dictionnaire {terminal BP3 → acteur} émis par l’encoder

Liens dans la série

  • L4 — Qu’est-ce qu’un AST
  • S12 — L’EBNF que le parser implémente
  • S10 — Le compilateur (tokenizer → parser → encoder → cet AST)
  • S3 — Les acteurs

Prérequis : S12, L4 Temps de lecture : 10 min Tags : #BPscript #AST #compilateur #code-intermédiaire


Prochain article : S14 — Sons, specs et effets


← Retour à l’index