Adapt parser and tests to new sompyler AST slot names
Sompyler commit c3b51bc renamed slots across the board: - offset.stem_note → line.stem_note (parentSlot 'offset' → 'line') - motif.stem_note → line.stem_note (parentSlot 'motif' → 'line') - clause.note/pause/stack → seq.note/pause/stack (parentSlot 'clause' → 'seq') - stem_note.chain is now a real hierarchical node (depth 04) between line.stem_note and chain.clause; clauses are now its children, not direct children of stem_note Also adds: - offset.motifs[] for line.motif invocations at tick positions - stem_note.writeToName from stem_note.write_to positional - adjacent prop already stored; new tests verify True/False/absent PaneFO.js: show offset motif invocations inline (label + chord) test-parser.mjs: update synthetic fixtures to new depths/slot names; add tests for line.motif, stem_note.write_to, adjacent prop
This commit is contained in:
106
test-parser.mjs
106
test-parser.mjs
@@ -434,10 +434,11 @@ section('Motif parsing — dynamic motif');
|
||||
const DYN_MOTIF = `00 bar '001P1L1M1'
|
||||
01 bar.voice 'pi'
|
||||
02 voice.motif label='oct'
|
||||
03 motif.stem_note pitch=0
|
||||
04 chain.clause 0 repeat=1
|
||||
05 clause.note letter='o' shift=0 length=1 netlength=1
|
||||
05 clause.note letter='o' shift=12 netlength=1 length=1
|
||||
03 line.stem_note pitch=0
|
||||
04 stem_note.chain _tmp_string='oo'
|
||||
05 chain.clause 0 repeat=1
|
||||
06 seq.note letter='o' shift=0 length=1 netlength=1
|
||||
06 seq.note letter='o' shift=12 netlength=1 length=1
|
||||
`;
|
||||
const dynBar = buildModel(parseAstLog(DYN_MOTIF)).bars[0];
|
||||
const dynVoice = dynBar.voices['pi'];
|
||||
@@ -452,9 +453,10 @@ section('Motif parsing — static motif');
|
||||
const STAT_MOTIF = `00 bar '001P1L1M1'
|
||||
01 bar.voice 'pi'
|
||||
02 voice.motif label='cadence'
|
||||
03 motif.stem_note pitch='C4'
|
||||
04 chain.clause 0
|
||||
05 clause.note letter='o' shift=0 length=1 netlength=1
|
||||
03 line.stem_note pitch='C4'
|
||||
04 stem_note.chain _tmp_string='o'
|
||||
05 chain.clause 0
|
||||
06 seq.note letter='o' shift=0 length=1 netlength=1
|
||||
`;
|
||||
const statVoice = buildModel(parseAstLog(STAT_MOTIF)).bars[0].voices['pi'];
|
||||
ok('static motif isStatic=true', statVoice.motifs[0].isStatic === true);
|
||||
@@ -464,14 +466,15 @@ section('Motif parsing — pause and stack in clause');
|
||||
const PS_FIXTURE = `00 bar '001P1L1M1'
|
||||
01 bar.voice 'pi'
|
||||
02 voice.offset tick=0
|
||||
03 offset.stem_note pitch='C4'
|
||||
04 chain.clause 0
|
||||
05 clause.note letter='o' shift=0 length=1 netlength=1
|
||||
05 clause.pause length=1
|
||||
04 chain.clause 1
|
||||
05 clause.stack length=2 netlength=2
|
||||
06 stack.note letter='o' shift=0
|
||||
06 stack.note letter='e' shift=0
|
||||
03 line.stem_note pitch='C4'
|
||||
04 stem_note.chain _tmp_string='o.oe'
|
||||
05 chain.clause 0
|
||||
06 seq.note letter='o' shift=0 length=1 netlength=1
|
||||
06 seq.pause length=1
|
||||
05 chain.clause 1
|
||||
06 seq.stack length=2 netlength=2
|
||||
07 stack.note letter='o' shift=0
|
||||
07 stack.note letter='e' shift=0
|
||||
`;
|
||||
const psVoice = buildModel(parseAstLog(PS_FIXTURE)).bars[0].voices['pi'];
|
||||
const psSn = psVoice.offsets[0].stemNotes[0];
|
||||
@@ -484,13 +487,14 @@ section('Motif parsing — nested chain ignored gracefully');
|
||||
const NC_FIXTURE = `00 bar '001P1L1M1'
|
||||
01 bar.voice 'pi'
|
||||
02 voice.offset tick=0
|
||||
03 offset.stem_note pitch='C4'
|
||||
04 chain.clause 0
|
||||
05 clause.note letter='o' shift=0 length=1 netlength=1
|
||||
05 clause.chain length=2
|
||||
06 chain.clause 0
|
||||
07 clause.stack length=1 netlength=1
|
||||
08 stack.note letter='o' shift=0
|
||||
03 line.stem_note pitch='C4'
|
||||
04 stem_note.chain _tmp_string='o(...)'
|
||||
05 chain.clause 0
|
||||
06 seq.note letter='o' shift=0 length=1 netlength=1
|
||||
06 seq.chain length=2
|
||||
07 chain.clause 0
|
||||
08 seq.stack length=1 netlength=1
|
||||
09 stack.note letter='o' shift=0
|
||||
`;
|
||||
const ncSn = buildModel(parseAstLog(NC_FIXTURE)).bars[0].voices['pi'].offsets[0].stemNotes[0];
|
||||
ok('nested chain: only 1 top-level clause', ncSn.clauses.length === 1);
|
||||
@@ -501,8 +505,8 @@ section('Depth-jump guard (abort)');
|
||||
const DJ_FIXTURE = `00 bar '001P1L1M1'
|
||||
01 bar.voice 'pi'
|
||||
02 voice.motif label='oct'
|
||||
03 motif.stem_note pitch='C4'
|
||||
06 chain.clause 0
|
||||
03 line.stem_note pitch='C4'
|
||||
07 chain.clause 0
|
||||
`;
|
||||
let djThrew = false;
|
||||
try { parseAstLog(DJ_FIXTURE); } catch (e) { djThrew = true; }
|
||||
@@ -516,6 +520,56 @@ let nsThrew = false;
|
||||
try { parseAstLog(NS_FIXTURE); } catch (e) { nsThrew = true; }
|
||||
ok('missing slot throws', nsThrew);
|
||||
|
||||
section('line.motif invocation at offset');
|
||||
const LM_FIXTURE = `00 bar '001P1L1M1'
|
||||
01 bar.voice 'pi'
|
||||
02 voice.offset tick=0
|
||||
03 line.stem_note pitch='C4'
|
||||
04 stem_note.chain _tmp_string='o'
|
||||
05 chain.clause 0
|
||||
06 seq.note letter='o' shift=0 length=1 netlength=1
|
||||
03 line.motif 'coct'
|
||||
03 line.motif 'oct' chord='C2'
|
||||
`;
|
||||
const lmOffset = buildModel(parseAstLog(LM_FIXTURE)).bars[0].voices['pi'].offsets[0];
|
||||
ok('line.motif: offset has 1 stem note', lmOffset.stemNotes.length === 1);
|
||||
ok('line.motif: offset.motifs has 2 entries', lmOffset.motifs.length === 2);
|
||||
ok('line.motif: first motif label', lmOffset.motifs[0].label === 'coct');
|
||||
ok('line.motif: second motif label', lmOffset.motifs[1].label === 'oct');
|
||||
ok('line.motif: second motif chord', lmOffset.motifs[1].chord === 'C2');
|
||||
ok('line.motif: first motif chord null', lmOffset.motifs[0].chord === null);
|
||||
|
||||
section('stem_note.write_to and chainText');
|
||||
const WT_FIXTURE = `00 bar '001P1L1M1'
|
||||
01 bar.voice 'pi'
|
||||
02 voice.offset tick=0
|
||||
03 line.stem_note pitch='C2'
|
||||
04 stem_note.write_to 'coct'
|
||||
04 stem_note.chain _tmp_string='o=o+3*4'
|
||||
05 chain.clause 0 repeat=3
|
||||
06 seq.note letter='o' shift=0 netlength=1 length=1
|
||||
06 seq.note letter='o' shift=3 netlength=1 length=1
|
||||
`;
|
||||
const wtSn = buildModel(parseAstLog(WT_FIXTURE)).bars[0].voices['pi'].offsets[0].stemNotes[0];
|
||||
ok('write_to: writeToName', wtSn.writeToName === 'coct');
|
||||
ok('write_to: chainText from _tmp_string', wtSn.chainText === 'o=o+3*4');
|
||||
ok('write_to: clause parsed', wtSn.clauses.length === 1);
|
||||
ok('write_to: clause repeat', wtSn.clauses[0].repeat === 3);
|
||||
ok('write_to: clause notes', wtSn.clauses[0].notes.length === 2);
|
||||
|
||||
section('adjacent prop on stem_note');
|
||||
const ADJ_FIXTURE = `00 bar '001P1L1M1'
|
||||
01 bar.voice 'pi'
|
||||
02 voice.offset tick=0
|
||||
03 line.stem_note pitch='C4'
|
||||
03 line.stem_note pitch='D4' adjacent=False
|
||||
03 line.stem_note pitch='E4' adjacent=True
|
||||
`;
|
||||
const adjOffset = buildModel(parseAstLog(ADJ_FIXTURE)).bars[0].voices['pi'].offsets[0];
|
||||
ok('adjacent: null when absent', adjOffset.stemNotes[0].adjacent === null);
|
||||
ok('adjacent: false when False', adjOffset.stemNotes[1].adjacent === false);
|
||||
ok('adjacent: true when True', adjOffset.stemNotes[2].adjacent === true);
|
||||
|
||||
section('Full fixture integration');
|
||||
ok('fixture parses without error (checked above)', raw && raw.slot === 'root');
|
||||
ok('fixture has 432 bars', model.bars.length === 432);
|
||||
@@ -523,6 +577,10 @@ const voicesWithMotifs = model.bars.flatMap(b => Object.values(b.voices)).filter
|
||||
ok('fixture voices have motif objects', voicesWithMotifs.every(v => v.motifs.every(m => typeof m === 'object' && 'label' in m)));
|
||||
const offsetsWithStemNotes = model.bars.flatMap(b => Object.values(b.voices)).flatMap(v => v.offsets).filter(o => o.stemNotes.length > 0);
|
||||
ok('fixture offsets have stem note objects', offsetsWithStemNotes.every(o => o.stemNotes.every(sn => 'pitch' in sn && 'clauses' in sn)));
|
||||
const offsetsWithMotifInvocations = model.bars.flatMap(b => Object.values(b.voices)).flatMap(v => v.offsets).filter(o => o.motifs?.length > 0);
|
||||
ok('fixture offset motif invocations are objects', offsetsWithMotifInvocations.every(o => o.motifs.every(m => 'label' in m)));
|
||||
const stemNotesWithChainText = offsetsWithStemNotes.flatMap(o => o.stemNotes).filter(sn => sn.chainText);
|
||||
ok('fixture stem notes with chain text have clauses', stemNotesWithChainText.every(sn => sn.clauses.length > 0));
|
||||
|
||||
// ── Summary ────────────────────────────────────────────────────────────────
|
||||
console.log(`\n══ ${pass} passed, ${fail} failed ══\n`);
|
||||
|
||||
Reference in New Issue
Block a user