S13) L’AST de BPscript
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 auxQualPair 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"
target: string // paramètre ciblé ("filter", "pan", "gain")
transport: string // runtime cible ("sc", "webaudio")
lib: string | null // lib source ("filter", 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
}
Exemples :
env1(filter, sc) = filter.adsr(10, 100, 0.7, 200)
→ { name:"env1", target:"filter", transport:"sc", lib:"filter", objectType:"adsr", args:[10,100,0.7,200], namedArgs:{} }
lfo1(pan, webaudio) = filter.lfo(rate:4, depth:50)
→ { name:"lfo1", target:"pan", transport:"webaudio", lib:"filter", 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 AST | BP3 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
ActorDirectivecapture le rattachement complet (alphabet,scale,sounds,transport,eval)Symbol.actor:null= implicite,"sitar"= explicite — les qualifiers ne sont pas des champs du Symbol, ils sont portés parsuffixQualifiers([]comme()sont suffixe)- Qualificateurs scindés :
Qualifier([], moteur) etRuntimeQualifier((), runtime — pairs nues, portée déduite de la position) Control: contrôle BP3 dans le flux ;SymbolWithTriggerIn: symbole en attente de<!CVInstance: objets CV déclarés en tête de scène (ADSR, LFO, ramp) —objectType,argspositionnels,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 (à venir) — Sons, specs et effets