@forwardimpact/libsyntheticgen 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dsl/parser.js ADDED
@@ -0,0 +1,728 @@
1
+ /**
2
+ * DSL Parser — recursive-descent parser that produces a UniverseAST.
3
+ *
4
+ * @typedef {object} UniverseAST
5
+ * @property {string} name
6
+ * @property {string} domain
7
+ * @property {string} industry
8
+ * @property {number} seed
9
+ * @property {object[]} orgs
10
+ * @property {object[]} departments
11
+ * @property {object[]} teams
12
+ * @property {object} people
13
+ * @property {object[]} projects
14
+ * @property {object[]} scenarios
15
+ * @property {object} snapshots
16
+ * @property {object} framework
17
+ * @property {object[]} content
18
+ * @property {object[]} datasets
19
+ * @property {object[]} outputs
20
+ */
21
+
22
+ /**
23
+ * Parse a token stream into a UniverseAST.
24
+ * @param {import('./tokenizer.js').Token[]} tokens
25
+ * @returns {UniverseAST}
26
+ */
27
+ export function parse(tokens) {
28
+ let pos = 0;
29
+
30
+ function peek() {
31
+ return tokens[pos];
32
+ }
33
+ function advance() {
34
+ return tokens[pos++];
35
+ }
36
+
37
+ function expect(type, value) {
38
+ const t = advance();
39
+ if (t.type !== type || (value !== undefined && t.value !== value)) {
40
+ throw new Error(
41
+ `Expected ${type}${value ? ` '${value}'` : ""} but got ${t.type} '${t.value}' at line ${t.line}`,
42
+ );
43
+ }
44
+ return t;
45
+ }
46
+
47
+ function expectKeyword(kw) {
48
+ return expect("KEYWORD", kw);
49
+ }
50
+
51
+ function parseStringOrIdent() {
52
+ const t = peek();
53
+ if (t.type === "STRING") return advance().value;
54
+ if (t.type === "IDENT") return advance().value;
55
+ if (t.type === "KEYWORD") return advance().value;
56
+ throw new Error(
57
+ `Expected string or identifier at line ${t.line}, got ${t.type} '${t.value}'`,
58
+ );
59
+ }
60
+
61
+ function parseStringValue() {
62
+ return expect("STRING").value;
63
+ }
64
+
65
+ function parseNumberValue() {
66
+ const t = advance();
67
+ if (t.type === "NUMBER") return Number(t.value);
68
+ if (t.type === "PERCENT") return Number(t.value);
69
+ throw new Error(`Expected number at line ${t.line}`);
70
+ }
71
+
72
+ function parseDateValue() {
73
+ const t = advance();
74
+ if (t.type === "DATE") return t.value;
75
+ if (t.type === "STRING") return t.value;
76
+ throw new Error(`Expected date at line ${t.line}`);
77
+ }
78
+
79
+ function parseArray() {
80
+ expect("LBRACKET");
81
+ const items = [];
82
+ while (peek().type !== "RBRACKET") {
83
+ const t = peek();
84
+ if (t.type === "STRING") items.push(advance().value);
85
+ else if (t.type === "IDENT") items.push(advance().value);
86
+ else if (t.type === "KEYWORD") items.push(advance().value);
87
+ else if (t.type === "NUMBER") items.push(Number(advance().value));
88
+ else throw new Error(`Unexpected ${t.type} in array at line ${t.line}`);
89
+ if (peek().type === "COMMA") advance();
90
+ }
91
+ expect("RBRACKET");
92
+ return items;
93
+ }
94
+
95
+ function parseOrg() {
96
+ const id = parseStringOrIdent();
97
+ expect("LBRACE");
98
+ const org = { id };
99
+ while (peek().type !== "RBRACE") {
100
+ const kw = advance();
101
+ if (kw.value === "name") org.name = parseStringValue();
102
+ else if (kw.value === "location") org.location = parseStringValue();
103
+ else
104
+ throw new Error(`Unexpected '${kw.value}' in org at line ${kw.line}`);
105
+ }
106
+ expect("RBRACE");
107
+ return org;
108
+ }
109
+
110
+ function parseTeam(departmentId) {
111
+ const id = parseStringOrIdent();
112
+ expect("LBRACE");
113
+ const team = { id, department: departmentId };
114
+ while (peek().type !== "RBRACE") {
115
+ const kw = advance();
116
+ if (kw.value === "name") team.name = parseStringValue();
117
+ else if (kw.value === "size") team.size = parseNumberValue();
118
+ else if (kw.value === "manager") {
119
+ const t = advance();
120
+ team.manager = t.type === "AT_IDENT" ? t.value : t.value;
121
+ } else if (kw.value === "repos") team.repos = parseArray();
122
+ else
123
+ throw new Error(`Unexpected '${kw.value}' in team at line ${kw.line}`);
124
+ }
125
+ expect("RBRACE");
126
+ return team;
127
+ }
128
+
129
+ function parseDepartment() {
130
+ const id = parseStringOrIdent();
131
+ expect("LBRACE");
132
+ const dept = { id, _children: [] };
133
+ const teams = [];
134
+ while (peek().type !== "RBRACE") {
135
+ const kw = advance();
136
+ if (kw.value === "name") dept.name = parseStringValue();
137
+ else if (kw.value === "parent") dept.parent = parseStringOrIdent();
138
+ else if (kw.value === "headcount") dept.headcount = parseNumberValue();
139
+ else if (kw.value === "team") {
140
+ const team = parseTeam(id);
141
+ teams.push(team);
142
+ } else
143
+ throw new Error(
144
+ `Unexpected '${kw.value}' in department at line ${kw.line}`,
145
+ );
146
+ }
147
+ expect("RBRACE");
148
+ return { dept, teams };
149
+ }
150
+
151
+ function parsePeople() {
152
+ expect("LBRACE");
153
+ const people = {};
154
+ while (peek().type !== "RBRACE") {
155
+ const kw = advance();
156
+ if (kw.value === "count") people.count = parseNumberValue();
157
+ else if (kw.value === "names") people.names = parseStringValue();
158
+ else if (kw.value === "distribution") {
159
+ expect("LBRACE");
160
+ people.distribution = {};
161
+ while (peek().type !== "RBRACE") {
162
+ const level = parseStringOrIdent();
163
+ const pct = parseNumberValue();
164
+ people.distribution[level] = pct;
165
+ }
166
+ expect("RBRACE");
167
+ } else if (kw.value === "disciplines") {
168
+ expect("LBRACE");
169
+ people.disciplines = {};
170
+ while (peek().type !== "RBRACE") {
171
+ const disc = parseStringOrIdent();
172
+ const pct = parseNumberValue();
173
+ people.disciplines[disc] = pct;
174
+ }
175
+ expect("RBRACE");
176
+ } else
177
+ throw new Error(
178
+ `Unexpected '${kw.value}' in people at line ${kw.line}`,
179
+ );
180
+ }
181
+ expect("RBRACE");
182
+ return people;
183
+ }
184
+
185
+ function parseProject() {
186
+ const id = parseStringOrIdent();
187
+ expect("LBRACE");
188
+ const proj = { id };
189
+ while (peek().type !== "RBRACE") {
190
+ const kw = advance();
191
+ if (kw.value === "name") proj.name = parseStringValue();
192
+ else if (kw.value === "type") proj.type = parseStringValue();
193
+ else if (kw.value === "phase") proj.phase = parseStringValue();
194
+ else if (kw.value === "teams") proj.teams = parseArray();
195
+ else if (kw.value === "timeline_start")
196
+ proj.timeline_start = parseDateValue();
197
+ else if (kw.value === "timeline_end")
198
+ proj.timeline_end = parseDateValue();
199
+ else if (kw.value === "prose_topic")
200
+ proj.prose_topic = parseStringValue();
201
+ else if (kw.value === "prose_tone") proj.prose_tone = parseStringValue();
202
+ else
203
+ throw new Error(
204
+ `Unexpected '${kw.value}' in project at line ${kw.line}`,
205
+ );
206
+ }
207
+ expect("RBRACE");
208
+ return proj;
209
+ }
210
+
211
+ function parseDxDrivers() {
212
+ expect("LBRACE");
213
+ const drivers = [];
214
+ while (peek().type !== "RBRACE") {
215
+ const driverId = parseStringOrIdent();
216
+ expect("LBRACE");
217
+ const driver = { driver_id: driverId };
218
+ while (peek().type !== "RBRACE") {
219
+ const kw = advance();
220
+ if (kw.value === "trajectory") driver.trajectory = parseStringValue();
221
+ else if (kw.value === "magnitude")
222
+ driver.magnitude = parseNumberValue();
223
+ else
224
+ throw new Error(
225
+ `Unexpected '${kw.value}' in dx_driver at line ${kw.line}`,
226
+ );
227
+ }
228
+ expect("RBRACE");
229
+ drivers.push(driver);
230
+ }
231
+ expect("RBRACE");
232
+ return drivers;
233
+ }
234
+
235
+ function parseAffect() {
236
+ const teamId = parseStringOrIdent();
237
+ expect("LBRACE");
238
+ const affect = { team_id: teamId };
239
+ while (peek().type !== "RBRACE") {
240
+ const kw = advance();
241
+ if (kw.value === "github_commits")
242
+ affect.github_commits = parseStringValue();
243
+ else if (kw.value === "github_prs")
244
+ affect.github_prs = parseStringValue();
245
+ else if (kw.value === "dx_drivers") affect.dx_drivers = parseDxDrivers();
246
+ else if (kw.value === "evidence_skills")
247
+ affect.evidence_skills = parseArray();
248
+ else if (kw.value === "evidence_floor")
249
+ affect.evidence_floor = parseStringValue();
250
+ else
251
+ throw new Error(
252
+ `Unexpected '${kw.value}' in affect at line ${kw.line}`,
253
+ );
254
+ }
255
+ expect("RBRACE");
256
+ return affect;
257
+ }
258
+
259
+ function parseScenario() {
260
+ const id = parseStringOrIdent();
261
+ expect("LBRACE");
262
+ const scenario = { id, affects: [] };
263
+ while (peek().type !== "RBRACE") {
264
+ const kw = advance();
265
+ if (kw.value === "name") scenario.name = parseStringValue();
266
+ else if (kw.value === "timerange_start")
267
+ scenario.timerange_start = parseDateValue();
268
+ else if (kw.value === "timerange_end")
269
+ scenario.timerange_end = parseDateValue();
270
+ else if (kw.value === "affect") scenario.affects.push(parseAffect());
271
+ else
272
+ throw new Error(
273
+ `Unexpected '${kw.value}' in scenario at line ${kw.line}`,
274
+ );
275
+ }
276
+ expect("RBRACE");
277
+ return scenario;
278
+ }
279
+
280
+ function parseSnapshots() {
281
+ expect("LBRACE");
282
+ const snaps = {};
283
+ while (peek().type !== "RBRACE") {
284
+ const kw = advance();
285
+ if (kw.value === "quarterly_from")
286
+ snaps.quarterly_from = parseDateValue();
287
+ else if (kw.value === "quarterly_to")
288
+ snaps.quarterly_to = parseDateValue();
289
+ else if (kw.value === "account_id") snaps.account_id = parseStringValue();
290
+ else if (kw.value === "comments_per_snapshot")
291
+ snaps.comments_per_snapshot = parseNumberValue();
292
+ else
293
+ throw new Error(
294
+ `Unexpected '${kw.value}' in snapshots at line ${kw.line}`,
295
+ );
296
+ }
297
+ expect("RBRACE");
298
+ return snaps;
299
+ }
300
+
301
+ function parseFramework() {
302
+ expect("LBRACE");
303
+ const fw = {
304
+ proficiencies: [],
305
+ maturities: [],
306
+ levels: [],
307
+ capabilities: [],
308
+ behaviours: [],
309
+ disciplines: [],
310
+ tracks: [],
311
+ drivers: [],
312
+ stages: [],
313
+ };
314
+ while (peek().type !== "RBRACE") {
315
+ const kw = advance();
316
+ if (kw.value === "proficiencies") fw.proficiencies = parseArray();
317
+ else if (kw.value === "maturities") fw.maturities = parseArray();
318
+ else if (kw.value === "stages") fw.stages = parseArray();
319
+ else if (kw.value === "levels") fw.levels = parseFrameworkLevels();
320
+ else if (kw.value === "capabilities")
321
+ fw.capabilities = parseFrameworkCapabilities();
322
+ else if (kw.value === "behaviours")
323
+ fw.behaviours = parseFrameworkBehaviours();
324
+ else if (kw.value === "disciplines")
325
+ fw.disciplines = parseFrameworkDisciplines();
326
+ else if (kw.value === "tracks") fw.tracks = parseFrameworkTracks();
327
+ else if (kw.value === "drivers") fw.drivers = parseFrameworkDrivers();
328
+ else
329
+ throw new Error(
330
+ `Unexpected '${kw.value}' in framework at line ${kw.line}`,
331
+ );
332
+ }
333
+ expect("RBRACE");
334
+ return fw;
335
+ }
336
+
337
+ /**
338
+ * Parse levels block: levels { J040 { title "..." rank 1 experience "..." } ... }
339
+ * @returns {object[]}
340
+ */
341
+ function parseFrameworkLevels() {
342
+ expect("LBRACE");
343
+ const levels = [];
344
+ while (peek().type !== "RBRACE") {
345
+ const id = parseStringOrIdent();
346
+ expect("LBRACE");
347
+ const level = { id };
348
+ while (peek().type !== "RBRACE") {
349
+ const kw = advance();
350
+ if (kw.value === "title") level.professionalTitle = parseStringValue();
351
+ else if (kw.value === "roleTitle")
352
+ level.managementTitle = parseStringValue();
353
+ else if (kw.value === "rank") level.rank = parseNumberValue();
354
+ else if (kw.value === "experience")
355
+ level.experience = parseStringValue();
356
+ else
357
+ throw new Error(
358
+ `Unexpected '${kw.value}' in level at line ${kw.line}`,
359
+ );
360
+ }
361
+ expect("RBRACE");
362
+ levels.push(level);
363
+ }
364
+ expect("RBRACE");
365
+ return levels;
366
+ }
367
+
368
+ /**
369
+ * Parse capabilities block: capabilities { id { name "..." skills [...] } ... }
370
+ * @returns {object[]}
371
+ */
372
+ function parseFrameworkCapabilities() {
373
+ expect("LBRACE");
374
+ const caps = [];
375
+ while (peek().type !== "RBRACE") {
376
+ const id = parseStringOrIdent();
377
+ expect("LBRACE");
378
+ const cap = { id };
379
+ while (peek().type !== "RBRACE") {
380
+ const kw = advance();
381
+ if (kw.value === "name") cap.name = parseStringValue();
382
+ else if (kw.value === "skills") cap.skills = parseArray();
383
+ else
384
+ throw new Error(
385
+ `Unexpected '${kw.value}' in capability at line ${kw.line}`,
386
+ );
387
+ }
388
+ expect("RBRACE");
389
+ caps.push(cap);
390
+ }
391
+ expect("RBRACE");
392
+ return caps;
393
+ }
394
+
395
+ /**
396
+ * Parse behaviours block: behaviours { id { name "..." } ... }
397
+ * @returns {object[]}
398
+ */
399
+ function parseFrameworkBehaviours() {
400
+ expect("LBRACE");
401
+ const behaviours = [];
402
+ while (peek().type !== "RBRACE") {
403
+ const id = parseStringOrIdent();
404
+ expect("LBRACE");
405
+ const beh = { id };
406
+ while (peek().type !== "RBRACE") {
407
+ const kw = advance();
408
+ if (kw.value === "name") beh.name = parseStringValue();
409
+ else
410
+ throw new Error(
411
+ `Unexpected '${kw.value}' in behaviour at line ${kw.line}`,
412
+ );
413
+ }
414
+ expect("RBRACE");
415
+ behaviours.push(beh);
416
+ }
417
+ expect("RBRACE");
418
+ return behaviours;
419
+ }
420
+
421
+ /**
422
+ * Parse disciplines block.
423
+ * @returns {object[]}
424
+ */
425
+ function parseFrameworkDisciplines() {
426
+ expect("LBRACE");
427
+ const disciplines = [];
428
+ while (peek().type !== "RBRACE") {
429
+ const id = parseStringOrIdent();
430
+ expect("LBRACE");
431
+ const disc = { id };
432
+ while (peek().type !== "RBRACE") {
433
+ const kw = advance();
434
+ if (kw.value === "roleTitle") disc.roleTitle = parseStringValue();
435
+ else if (kw.value === "specialization")
436
+ disc.specialization = parseStringValue();
437
+ else if (kw.value === "isProfessional") {
438
+ const val = parseStringOrIdent();
439
+ disc.isProfessional = val === "true";
440
+ } else if (kw.value === "core") disc.core = parseArray();
441
+ else if (kw.value === "supporting") disc.supporting = parseArray();
442
+ else if (kw.value === "broad") disc.broad = parseArray();
443
+ else if (kw.value === "validTracks")
444
+ disc.validTracks = parseNullableArray();
445
+ else
446
+ throw new Error(
447
+ `Unexpected '${kw.value}' in discipline at line ${kw.line}`,
448
+ );
449
+ }
450
+ expect("RBRACE");
451
+ disciplines.push(disc);
452
+ }
453
+ expect("RBRACE");
454
+ return disciplines;
455
+ }
456
+
457
+ /**
458
+ * Parse tracks block: tracks { id { name "..." } ... }
459
+ * @returns {object[]}
460
+ */
461
+ function parseFrameworkTracks() {
462
+ expect("LBRACE");
463
+ const tracks = [];
464
+ while (peek().type !== "RBRACE") {
465
+ const id = parseStringOrIdent();
466
+ expect("LBRACE");
467
+ const track = { id };
468
+ while (peek().type !== "RBRACE") {
469
+ const kw = advance();
470
+ if (kw.value === "name") track.name = parseStringValue();
471
+ else
472
+ throw new Error(
473
+ `Unexpected '${kw.value}' in track at line ${kw.line}`,
474
+ );
475
+ }
476
+ expect("RBRACE");
477
+ tracks.push(track);
478
+ }
479
+ expect("RBRACE");
480
+ return tracks;
481
+ }
482
+
483
+ /**
484
+ * Parse drivers block: drivers { id { name "..." skills [...] behaviours [...] } ... }
485
+ * @returns {object[]}
486
+ */
487
+ function parseFrameworkDrivers() {
488
+ expect("LBRACE");
489
+ const drivers = [];
490
+ while (peek().type !== "RBRACE") {
491
+ const id = parseStringOrIdent();
492
+ expect("LBRACE");
493
+ const driver = { id };
494
+ while (peek().type !== "RBRACE") {
495
+ const kw = advance();
496
+ if (kw.value === "name") driver.name = parseStringValue();
497
+ else if (kw.value === "skills") driver.skills = parseArray();
498
+ else if (kw.value === "behaviours") driver.behaviours = parseArray();
499
+ else
500
+ throw new Error(
501
+ `Unexpected '${kw.value}' in driver at line ${kw.line}`,
502
+ );
503
+ }
504
+ expect("RBRACE");
505
+ drivers.push(driver);
506
+ }
507
+ expect("RBRACE");
508
+ return drivers;
509
+ }
510
+
511
+ /**
512
+ * Parse array that may contain null literals: [null, platform, sre]
513
+ * @returns {Array<string|null>}
514
+ */
515
+ function parseNullableArray() {
516
+ expect("LBRACKET");
517
+ const items = [];
518
+ while (peek().type !== "RBRACKET") {
519
+ const t = peek();
520
+ if (t.type === "STRING") items.push(advance().value);
521
+ else if (t.type === "IDENT") {
522
+ const val = advance().value;
523
+ items.push(val === "null" ? null : val);
524
+ } else if (t.type === "KEYWORD") {
525
+ const val = advance().value;
526
+ items.push(val === "null" ? null : val);
527
+ } else throw new Error(`Unexpected ${t.type} in array at line ${t.line}`);
528
+ if (peek().type === "COMMA") advance();
529
+ }
530
+ expect("RBRACKET");
531
+ return items;
532
+ }
533
+
534
+ /**
535
+ * Parse a dataset block: dataset <id> { tool <name> rows <n> fields { ... } ... }
536
+ * @param {string} id
537
+ * @returns {object}
538
+ */
539
+ function parseDataset(id) {
540
+ expect("LBRACE");
541
+ const ds = { id, tool: null, config: {} };
542
+ while (peek().type !== "RBRACE") {
543
+ const kw = advance();
544
+ if (kw.value === "tool") ds.tool = parseStringOrIdent();
545
+ else if (kw.value === "population")
546
+ ds.config.population = parseNumberValue();
547
+ else if (kw.value === "modules") ds.config.modules = parseArray();
548
+ else if (kw.value === "metadata") ds.config.metadata = parseStringValue();
549
+ else if (kw.value === "data") ds.config.data = parseDatasetFields();
550
+ else if (kw.value === "rows") ds.config.rows = parseNumberValue();
551
+ else if (kw.value === "fields") ds.config.fields = parseDatasetFields();
552
+ else
553
+ throw new Error(
554
+ `Unexpected '${kw.value}' in dataset at line ${kw.line}`,
555
+ );
556
+ }
557
+ expect("RBRACE");
558
+ return ds;
559
+ }
560
+
561
+ /**
562
+ * Parse a fields/data block: { key "value" ... }
563
+ * @returns {Object<string, string>}
564
+ */
565
+ function parseDatasetFields() {
566
+ expect("LBRACE");
567
+ const fields = {};
568
+ while (peek().type !== "RBRACE") {
569
+ const name = parseStringOrIdent();
570
+ const value = parseStringValue();
571
+ fields[name] = value;
572
+ }
573
+ expect("RBRACE");
574
+ return fields;
575
+ }
576
+
577
+ const DATASET_FORMATS = new Set([
578
+ "json",
579
+ "yaml",
580
+ "csv",
581
+ "markdown",
582
+ "parquet",
583
+ "sql",
584
+ ]);
585
+
586
+ /**
587
+ * Parse an output block: output <dataset> <format> { path "..." [table "..."] }
588
+ * @param {string} datasetId
589
+ * @returns {object}
590
+ */
591
+ function parseOutput(datasetId) {
592
+ const format = parseStringOrIdent();
593
+ if (!DATASET_FORMATS.has(format)) {
594
+ throw new Error(
595
+ `Unknown output format '${format}'. Expected one of: ${[...DATASET_FORMATS].join(", ")}`,
596
+ );
597
+ }
598
+ expect("LBRACE");
599
+ const out = { dataset: datasetId, format, config: {} };
600
+ while (peek().type !== "RBRACE") {
601
+ const kw = advance();
602
+ if (kw.value === "path") out.config.path = parseStringValue();
603
+ else if (kw.value === "table") out.config.table = parseStringValue();
604
+ else
605
+ throw new Error(
606
+ `Unexpected '${kw.value}' in output at line ${kw.line}`,
607
+ );
608
+ }
609
+ expect("RBRACE");
610
+ return out;
611
+ }
612
+
613
+ function parseContent() {
614
+ const id = parseStringOrIdent();
615
+ expect("LBRACE");
616
+ const content = { id };
617
+ while (peek().type !== "RBRACE") {
618
+ const kw = advance();
619
+ if (kw.value === "articles") content.articles = parseNumberValue();
620
+ else if (kw.value === "article_topics")
621
+ content.article_topics = parseArray();
622
+ else if (kw.value === "blogs") content.blogs = parseNumberValue();
623
+ else if (kw.value === "faqs") content.faqs = parseNumberValue();
624
+ else if (kw.value === "howtos") content.howtos = parseNumberValue();
625
+ else if (kw.value === "howto_topics") content.howto_topics = parseArray();
626
+ else if (kw.value === "reviews") content.reviews = parseNumberValue();
627
+ else if (kw.value === "comments") content.comments = parseNumberValue();
628
+ else if (kw.value === "courses") content.courses = parseNumberValue();
629
+ else if (kw.value === "events") content.events = parseNumberValue();
630
+ else if (kw.value === "personas") content.personas = parseNumberValue();
631
+ else if (kw.value === "persona_levels")
632
+ content.persona_levels = parseArray();
633
+ else if (kw.value === "briefings_per_persona")
634
+ content.briefings_per_persona = parseNumberValue();
635
+ else if (kw.value === "notes_per_persona")
636
+ content.notes_per_persona = parseNumberValue();
637
+ else
638
+ throw new Error(
639
+ `Unexpected '${kw.value}' in content at line ${kw.line}`,
640
+ );
641
+ }
642
+ expect("RBRACE");
643
+ return content;
644
+ }
645
+
646
+ // Main: parse universe
647
+ expectKeyword("universe");
648
+ const name = parseStringOrIdent();
649
+ expect("LBRACE");
650
+
651
+ const ast = {
652
+ name,
653
+ domain: null,
654
+ industry: null,
655
+ seed: 42,
656
+ orgs: [],
657
+ departments: [],
658
+ teams: [],
659
+ people: null,
660
+ projects: [],
661
+ scenarios: [],
662
+ snapshots: null,
663
+ framework: null,
664
+ content: [],
665
+ datasets: [],
666
+ outputs: [],
667
+ };
668
+
669
+ while (peek().type !== "RBRACE" && peek().type !== "EOF") {
670
+ const kw = advance();
671
+ switch (kw.value) {
672
+ case "domain":
673
+ ast.domain = parseStringValue();
674
+ break;
675
+ case "industry":
676
+ ast.industry = parseStringValue();
677
+ break;
678
+ case "seed":
679
+ ast.seed = parseNumberValue();
680
+ break;
681
+ case "org":
682
+ ast.orgs.push(parseOrg());
683
+ break;
684
+ case "department": {
685
+ const { dept, teams } = parseDepartment();
686
+ ast.departments.push(dept);
687
+ ast.teams.push(...teams);
688
+ break;
689
+ }
690
+ case "people":
691
+ ast.people = parsePeople();
692
+ break;
693
+ case "project":
694
+ ast.projects.push(parseProject());
695
+ break;
696
+ case "scenario":
697
+ ast.scenarios.push(parseScenario());
698
+ break;
699
+ case "snapshots":
700
+ ast.snapshots = parseSnapshots();
701
+ break;
702
+ case "framework":
703
+ ast.framework = parseFramework();
704
+ break;
705
+ case "content":
706
+ ast.content.push(parseContent());
707
+ break;
708
+ case "dataset": {
709
+ const id = parseStringOrIdent();
710
+ ast.datasets.push(parseDataset(id));
711
+ break;
712
+ }
713
+ case "output": {
714
+ const datasetId = parseStringOrIdent();
715
+ ast.outputs.push(parseOutput(datasetId));
716
+ break;
717
+ }
718
+ default:
719
+ throw new Error(
720
+ `Unexpected keyword '${kw.value}' at top level, line ${kw.line}`,
721
+ );
722
+ }
723
+ }
724
+
725
+ if (peek().type === "RBRACE") advance();
726
+
727
+ return ast;
728
+ }