@pubid/pubid 0.1.8 → 0.1.10

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.
Files changed (76) hide show
  1. package/artifact/corpus.json.gz +0 -0
  2. package/dist/flavors/cen_cenelec/grammar.js +10 -4
  3. package/dist/flavors/cen_cenelec/index.js +20 -1
  4. package/dist/flavors/cen_cenelec/model.d.ts +1 -0
  5. package/dist/flavors/cen_cenelec/model.js +4 -1
  6. package/dist/flavors/evs/grammar.js +53 -7
  7. package/dist/flavors/evs/index.js +74 -7
  8. package/dist/flavors/evs/model.d.ts +37 -1
  9. package/dist/flavors/evs/model.js +173 -11
  10. package/dist/flavors/ieee/artifact_tree.d.ts +2 -0
  11. package/dist/flavors/ieee/artifact_tree.js +36 -0
  12. package/dist/flavors/ieee/grammar.js +99 -19
  13. package/dist/flavors/ieee/index.js +353 -18
  14. package/dist/flavors/ieee/model.d.ts +5 -0
  15. package/dist/flavors/ieee/model.js +132 -20
  16. package/dist/flavors/ieee/preprocessor.js +28 -6
  17. package/dist/flavors/nist/grammar.js +18 -9
  18. package/dist/flavors/nist/index.js +16 -7
  19. package/dist/flavors/nist/preprocessor.js +9 -1
  20. package/dist/parg/runtime.d.ts +25 -0
  21. package/dist/parg/runtime.js +76 -0
  22. package/dist/parg/suite.d.ts +4 -0
  23. package/dist/parg/suite.js +14 -0
  24. package/dist/urn/parsers.js +7 -0
  25. package/package.json +6 -4
  26. package/vendor/parg/adobe.json +1 -0
  27. package/vendor/parg/aiee.json +1 -0
  28. package/vendor/parg/amca.json +1 -0
  29. package/vendor/parg/ansi.json +1 -0
  30. package/vendor/parg/api.json +1 -0
  31. package/vendor/parg/ashrae.json +1 -0
  32. package/vendor/parg/asme.json +1 -0
  33. package/vendor/parg/astm.json +1 -0
  34. package/vendor/parg/bipm.json +1 -0
  35. package/vendor/parg/bsi.json +1 -0
  36. package/vendor/parg/calconnect.json +1 -0
  37. package/vendor/parg/ccsds.json +1 -0
  38. package/vendor/parg/cen_cenelec.json +1 -0
  39. package/vendor/parg/cie.json +1 -0
  40. package/vendor/parg/csa.json +1 -0
  41. package/vendor/parg/doi.json +1 -0
  42. package/vendor/parg/easc.json +1 -0
  43. package/vendor/parg/ecma.json +1 -0
  44. package/vendor/parg/etsi.json +1 -0
  45. package/vendor/parg/evs.json +1 -0
  46. package/vendor/parg/gb.json +1 -0
  47. package/vendor/parg/gost.json +1 -0
  48. package/vendor/parg/iala.json +1 -0
  49. package/vendor/parg/iana.json +1 -0
  50. package/vendor/parg/idf.json +1 -0
  51. package/vendor/parg/iec.json +1 -0
  52. package/vendor/parg/ieee.json +1 -0
  53. package/vendor/parg/ietf.json +1 -0
  54. package/vendor/parg/ire.json +1 -0
  55. package/vendor/parg/isbn.json +1 -0
  56. package/vendor/parg/iso.json +1 -0
  57. package/vendor/parg/itu.json +1 -0
  58. package/vendor/parg/jcgm.json +1 -0
  59. package/vendor/parg/jis.json +1 -0
  60. package/vendor/parg/nesc.json +1 -0
  61. package/vendor/parg/nist.json +1 -0
  62. package/vendor/parg/oasis.json +1 -0
  63. package/vendor/parg/ogc.json +1 -0
  64. package/vendor/parg/oiml.json +1 -0
  65. package/vendor/parg/omg.json +1 -0
  66. package/vendor/parg/parg.json +1 -0
  67. package/vendor/parg/plateau.json +1 -0
  68. package/vendor/parg/sae.json +1 -0
  69. package/vendor/parg/tgpp.json +1 -0
  70. package/vendor/parg/un.json +1 -0
  71. package/vendor/parg/w3c.json +1 -0
  72. package/vendor/parg/xsf.json +1 -0
  73. package/dist/pg/runtime.d.ts +0 -85
  74. package/dist/pg/runtime.js +0 -170
  75. package/dist/pg/suite.d.ts +0 -20
  76. package/dist/pg/suite.js +0 -90
@@ -117,7 +117,16 @@ export class IeeeCode {
117
117
  return result;
118
118
  }
119
119
  }
120
- const DRAFT_MONTH_NAMES = {
120
+ // Canonical month spelling (mirrors the gem's CANONICAL_ABBREVIATIONS):
121
+ // full names normalize onto the crawl abbreviation, and "September"
122
+ // keeps its IEEE "Sept" form — the 3-letter "Sep" folds onto it, so
123
+ // every spelling of September renders and hashes alike.
124
+ const DRAFT_MONTH_CANONICAL = {
125
+ January: "Jan", February: "Feb", March: "Mar", April: "Apr",
126
+ June: "Jun", July: "Jul", August: "Aug", September: "Sept",
127
+ October: "Oct", November: "Nov", December: "Dec", Sep: "Sept",
128
+ };
129
+ export const DRAFT_MONTH_NAMES = {
121
130
  January: "1", February: "2", March: "3", April: "4", May: "5", June: "6",
122
131
  July: "7", August: "8", September: "9", October: "10", November: "11",
123
132
  December: "12", Jan: "1", Feb: "2", Mar: "3", Apr: "4", Jun: "6",
@@ -139,12 +148,27 @@ export class IeeeDraft {
139
148
  this.commaBeforeMonthSet = "comma_before_month" in attrs;
140
149
  this.version = String(attrs["version"] ?? "");
141
150
  this.revision = attrs["revision"];
142
- this.isoStage = attrs["iso_stage"];
143
151
  this.isoIteration = attrs["iso_iteration"];
152
+ // The grammar's ordered alternation captures the stage WITH its
153
+ // draft-marker D when the spelling doubles it ("=DDIS"): the marker
154
+ // is notation, the stage is the bare ISO/IEC token (the reference's
155
+ // Draft constructor strips it).
156
+ let stage = attrs["iso_stage"];
157
+ if (stage !== undefined && stage.startsWith("D") &&
158
+ ["PWI", "NP", "WD", "CD", "CDV", "DIS", "FDIS"].includes(stage.slice(1))) {
159
+ stage = stage.slice(1);
160
+ }
161
+ this.isoStage = stage;
144
162
  this.year = attrs["year"];
145
- this.originalMonth = attrs["month"];
146
- this.month = attrs["month"] !== undefined
147
- ? DRAFT_MONTH_NAMES[String(attrs["month"])] ?? String(attrs["month"])
163
+ // Canonical month spelling: the crawl abbreviation ("September" →
164
+ // "Sept"); the 3-letter "Sep" folds onto the IEEE "Sept" form, so
165
+ // every spelling of September renders — and hashes — alike.
166
+ const rawMonth = attrs["month"];
167
+ this.originalMonth = rawMonth !== undefined
168
+ ? DRAFT_MONTH_CANONICAL[rawMonth.replace(/\.$/, "")] ?? rawMonth
169
+ : undefined;
170
+ this.month = rawMonth !== undefined
171
+ ? DRAFT_MONTH_NAMES[String(this.originalMonth)] ?? String(this.originalMonth)
148
172
  : undefined;
149
173
  this.day = attrs["day"];
150
174
  this.commaBeforeMonth = attrs["comma_before_month"] === true;
@@ -363,10 +387,17 @@ export class IeeeIdentifier extends BaseIdentifier {
363
387
  draft_status: { type: "string" },
364
388
  draft: { type: "string" },
365
389
  revision: { type: "string" },
390
+ rev_sep: { type: "string" },
391
+ rev_bare: { type: "boolean" },
366
392
  edition: { type: "string" },
367
393
  month: { type: "string" },
368
394
  day: { type: "string" },
369
395
  redline: { type: "boolean", default: false },
396
+ // Provenance (gem#520): the month arrived glued to the identity year
397
+ // on the code ("ANSI/IEEE 21-1976-11") rather than as a trailing
398
+ // print date. The render keys on the month being numeric; this
399
+ // records how it arrived.
400
+ month_glued: { type: "boolean", default: false },
370
401
  edition_month: { type: "string" },
371
402
  space_before_draft: { type: "boolean", default: false },
372
403
  typed_stage: { type: IeeeTypedStage },
@@ -384,7 +415,7 @@ export class IeeeIdentifier extends BaseIdentifier {
384
415
  interpretation: { type: "boolean", default: false },
385
416
  relationships: { type: "string", collection: true },
386
417
  });
387
- static mappings = keyValue({ wire: "publisher", to: "publisher" }, { wire: "copublisher", to: "copublisher" }, { wire: "year", to: "year" }, { wire: "type", to: "type" }, { wire: "draft_status", to: "draft_status" }, draftWire, { wire: "revision", to: "revision" }, { wire: "edition", to: "edition" }, { wire: "month", to: "month" }, { wire: "day", to: "day" }, { wire: "redline", to: "redline" }, { wire: "edition_month", to: "edition_month" }, { wire: "space_before_draft", to: "space_before_draft" }, typedStageWire, { wire: "parenthetical_content", to: "parenthetical_content" }, { wire: "nickname", to: "nickname" }, { wire: "adoption", to: "adoption" }, { wire: "amendment_to", to: "amendment_to" }, { wire: "note", to: "note" }, { wire: "iso_identifier", to: "iso_identifier" }, { wire: "revision_of", to: "revision_of" }, { wire: "ashrae_number", to: "ashrae_number" }, { wire: "ashrae_year", to: "ashrae_year" }, { wire: "crossref", to: "crossref" }, { wire: "reaffirmed", to: "reaffirmed" }, { wire: "interpretation", to: "interpretation" }, {
418
+ static mappings = keyValue({ wire: "publisher", to: "publisher" }, { wire: "copublisher", to: "copublisher" }, { wire: "year", to: "year" }, { wire: "type", to: "type" }, { wire: "draft_status", to: "draft_status" }, draftWire, { wire: "revision", to: "revision" }, { wire: "rev_sep", to: "rev_sep" }, { wire: "rev_bare", to: "rev_bare" }, { wire: "edition", to: "edition" }, { wire: "month", to: "month" }, { wire: "day", to: "day" }, { wire: "redline", to: "redline" }, { wire: "month_glued", to: "month_glued" }, { wire: "edition_month", to: "edition_month" }, { wire: "space_before_draft", to: "space_before_draft" }, typedStageWire, { wire: "parenthetical_content", to: "parenthetical_content" }, { wire: "nickname", to: "nickname" }, { wire: "adoption", to: "adoption" }, { wire: "amendment_to", to: "amendment_to" }, { wire: "note", to: "note" }, { wire: "iso_identifier", to: "iso_identifier" }, { wire: "revision_of", to: "revision_of" }, { wire: "ashrae_number", to: "ashrae_number" }, { wire: "ashrae_year", to: "ashrae_year" }, { wire: "crossref", to: "crossref" }, { wire: "reaffirmed", to: "reaffirmed" }, { wire: "interpretation", to: "interpretation" }, {
388
419
  wire: "relationships",
389
420
  to: "relationships",
390
421
  toWire: (model) => {
@@ -446,6 +477,12 @@ export function renderIeeeBase(id) {
446
477
  let typeStr = id.type;
447
478
  if (typeStr.startsWith("P"))
448
479
  typeStr = typeStr.slice(1);
480
+ // A draft carries the word Draft alone in every face (the C2
481
+ // ruling): Std/Standard never renders on a draft - a status-less
482
+ // draft ("IEEE Draft Std P802.3/D2.0") renders "IEEE Draft
483
+ // P802.3/D2.0".
484
+ if (id.draft !== undefined && id.draft_status === undefined)
485
+ typeStr = "Draft";
449
486
  if (typeStr.trim() !== "")
450
487
  parts.push(typeStr);
451
488
  }
@@ -455,6 +492,8 @@ export function renderIeeeBase(id) {
455
492
  // dates (catalogue metadata, not identity); wider sets and
456
493
  // space-separated dates print as spelled.
457
494
  const iecIeeOnly = id.publisher === "IEC" && JSON.stringify(id.copublisher) === '["IEEE"]';
495
+ // Short format = numeric month (glued face); the comma face is a month name.
496
+ const gluedMonth = /^\d{1,2}$/.test(id.month ?? "");
458
497
  if (codeObj !== undefined) {
459
498
  let result = codeObj.render();
460
499
  // Prepend P if this is a project AND code doesn't already have P.
@@ -467,11 +506,30 @@ export function renderIeeeBase(id) {
467
506
  !result.startsWith("P")) {
468
507
  result = `P${result}`;
469
508
  }
470
- if (id.year !== undefined && draftObj === undefined && id.edition === undefined && id.month === undefined) {
509
+ // A base year dash-attaches to the code when nothing else carries
510
+ // it: no edition, no identity-month date - and either no draft at
511
+ // all, or a draft that carries its OWN date (the draft+corrigendum
512
+ // family: "PC37.09-1999/D7.2, Nov 2006").
513
+ // A numeric identity month is the SHORT format: the year stays on
514
+ // the code and the month dash-joins after it, whatever the input
515
+ // spelling ("IEEE P15026.1, 05 2014" renders "P15026.1-2014-05").
516
+ const yearOnCode = id.year !== undefined && id.edition === undefined &&
517
+ (id.month === undefined || gluedMonth) &&
518
+ (draftObj === undefined || draftObj.year !== undefined ||
519
+ (draftObj.version ?? "").match(/-\d{4}-\d{2}$/) !== null);
520
+ if (yearOnCode) {
471
521
  result += `-${id.year}`;
472
522
  }
523
+ if (gluedMonth && id.month !== undefined) {
524
+ result += `-${id.month}`;
525
+ }
526
+ // The lettered revision keeps its printed separator ("P802.11-REVmc"
527
+ // renders "P802.11-Revmc"); the bare word with no id still prints
528
+ // "Rev" (pubid ruling 2026-10-14).
473
529
  if (id.revision !== undefined)
474
- result += `Rev${id.revision}`;
530
+ result += `${id.rev_sep ?? ""}Rev${id.revision}`;
531
+ if (id.rev_bare === true)
532
+ result += `${id.rev_sep ?? ""}Rev`;
475
533
  if (draftObj !== undefined) {
476
534
  let printed = draftObj.render();
477
535
  // The draft designator carries its date on every lead
@@ -510,13 +568,22 @@ export function renderIeeeBase(id) {
510
568
  parts.push(editionStr);
511
569
  }
512
570
  let result = parts.join(" ");
513
- if (id.month !== undefined && !iecIeeOnly) {
571
+ if (id.month !== undefined && !gluedMonth && !iecIeeOnly) {
514
572
  result += `, ${id.month}`;
515
573
  if (id.day !== undefined)
516
574
  result += ` ${id.day}`;
517
575
  if (id.year !== undefined && id.edition === undefined)
518
576
  result += ` ${id.year}`;
519
577
  }
578
+ else if (id.year !== undefined && draftObj !== undefined && id.edition === undefined &&
579
+ draftObj?.year === undefined &&
580
+ (draftObj.version ?? "").match(/-\d{4}-\d{2}$/) === null && !iecIeeOnly) {
581
+ // A year-only date trailing the draft ("ANSI N42.34/D9a, 2015")
582
+ // keeps its comma form instead of being silently dropped; a year
583
+ // whose draft carries its own date dash-attached to the code above
584
+ // and never repeats.
585
+ result += `, ${id.year}`;
586
+ }
520
587
  const parentheticals = [];
521
588
  if (id.reaffirmed !== undefined && id.reaffirmed.trim() !== "") {
522
589
  parentheticals.push(`(R${id.reaffirmed})`);
@@ -559,6 +626,13 @@ class IeeeUrnGenerator extends BaseUrnGenerator {
559
626
  push("redline");
560
627
  if (special === undefined && id.interpretation === true)
561
628
  push("int");
629
+ // A NESC preprint is a draft-stage document distinct from the
630
+ // published edition it precedes — without this component it would
631
+ // share the edition's URN (urn:ieee:ieee:C2:2012).
632
+ if (id.constructor.polymorphicName ===
633
+ "pubid:ieee:nesc-preprint") {
634
+ push("preprint");
635
+ }
562
636
  if (id.ashrae_number !== undefined) {
563
637
  let ashrae = `ashrae.${id.ashrae_number}`;
564
638
  if (id.ashrae_year !== undefined)
@@ -836,8 +910,14 @@ export const SiStandardClass = ieeeClass({ kind: "si-standard" }, {
836
910
  result += ` ${part}`;
837
911
  }
838
912
  }
839
- if (id.month !== undefined && id.year !== undefined)
840
- result += `, ${id.month} ${id.year}`;
913
+ if (id.month !== undefined && id.year !== undefined) {
914
+ // Short format (numeric month) renders glued; the comma marks a
915
+ // month name (the long format).
916
+ if (/^\d{1,2}$/.test(id.month))
917
+ result += `-${id.year}-${id.month}`;
918
+ else
919
+ result += `, ${id.month} ${id.year}`;
920
+ }
841
921
  else if (id.year !== undefined)
842
922
  result += `-${id.year}`;
843
923
  return result;
@@ -1030,16 +1110,28 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
1030
1110
  // P-state is identity-bearing and prints as spelled - it is never
1031
1111
  // added or stripped here.
1032
1112
  if (leadParty === "ISO") {
1113
+ // A D= designator row ("P26511/D8=DDIS.3") prints the designator
1114
+ // after the code, not the stage as a word; only genuine stage
1115
+ // rows print the stage position.
1116
+ const designatorDraft = id["ieee_draft"];
1117
+ const printsDesignator = designatorDraft !== undefined && /^D\d*=/.test(designatorDraft);
1033
1118
  const parts = [];
1034
1119
  if (publishers !== undefined && publishers.length > 0)
1035
1120
  parts.push(publishers.join("/"));
1036
- if (id.typed_stage !== undefined) {
1037
- parts.push(id.typed_stage.record.iso_stage_equivalent ?? id.typed_stage.render());
1038
- }
1039
- else if (isoStage !== undefined) {
1040
- parts.push(isoStage);
1121
+ if (!printsDesignator) {
1122
+ if (id.typed_stage !== undefined) {
1123
+ parts.push(id.typed_stage.record.iso_stage_equivalent ?? id.typed_stage.render());
1124
+ }
1125
+ else if (isoStage !== undefined) {
1126
+ parts.push(isoStage);
1127
+ }
1041
1128
  }
1042
- const codeStr = codeObj?.render() ?? "";
1129
+ let codeStr = codeObj?.render() ?? "";
1130
+ // A P-prefixed joint project prints its parts dot-joined on the
1131
+ // ISO face ("ISO/IEEE DIS P15500.1"), whatever separator the
1132
+ // parse captured ("IEC/IEEE FDIS P60214-2-2018(E)" aliases in).
1133
+ if (codeStr.startsWith("P"))
1134
+ codeStr = codeStr.replaceAll("-", ".");
1043
1135
  if (codeStr !== "")
1044
1136
  parts.push(codeStr);
1045
1137
  let result = parts.join(" ");
@@ -1049,12 +1141,16 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
1049
1141
  // the ISO-led print; only the bare language/edition marker ("(E)")
1050
1142
  // prints - a relationship narrative is metadata, not identity.
1051
1143
  const jointDraft = id["ieee_draft"];
1052
- if (jointDraft !== undefined && jointDraft.startsWith("D="))
1144
+ if (jointDraft !== undefined && /^D\d*=/.test(jointDraft))
1053
1145
  result += `/${jointDraft}`;
1054
1146
  const marker = id["parenthetical_content"];
1055
1147
  if (marker !== undefined && /^[A-Z](?:\s*[/&]\s*[A-Z])*$/.test(marker)) {
1056
1148
  result += ` (${marker})`;
1057
1149
  }
1150
+ // The joint ISO-print redline is a distinct document (pubid#215):
1151
+ // "15289:2015(E) - Redline".
1152
+ if (id.redline === true)
1153
+ result += " - Redline";
1058
1154
  return result;
1059
1155
  }
1060
1156
  // IEEE format
@@ -1074,6 +1170,8 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
1074
1170
  let result = parts.join(" ");
1075
1171
  if (id.year !== undefined)
1076
1172
  result += `-${id.year}`;
1173
+ if (id.redline === true)
1174
+ result += " - Redline";
1077
1175
  return result;
1078
1176
  },
1079
1177
  });
@@ -1215,11 +1313,25 @@ export const NescDraftClass = ieeeClass({ kind: "nesc-draft" }, {
1215
1313
  extraMappings: NescMappings,
1216
1314
  render: (id) => {
1217
1315
  const parts = ["Draft National Electrical Safety Code"];
1218
- if (id.month !== undefined && id.year !== undefined)
1219
- parts.push(`, ${id.month} ${id.year}`);
1316
+ if (id.month !== undefined && id.year !== undefined) {
1317
+ if (/^\d{1,2}$/.test(id.month))
1318
+ parts.push(` ${id.number}-${id.year}-${id.month}`);
1319
+ else
1320
+ parts.push(`, ${id.month} ${id.year}`);
1321
+ }
1220
1322
  return parts.join("");
1221
1323
  },
1222
1324
  });
1325
+ // The NESC's own term for its pre-edition drafts: a draft STAGE of the
1326
+ // C2 code, not a variant. Canonical spelling is dotted and prefixed;
1327
+ // the dash, catalogue slash-tail and verbose proposal spellings are
1328
+ // aliases.
1329
+ export const NescPreprintClass = ieeeClass({ kind: "nesc-preprint" }, {
1330
+ codeColumns: true,
1331
+ extraDefs: NescDefs,
1332
+ extraMappings: NescMappings,
1333
+ render: (id) => `IEEE Std ${["C2", id.year, "Preprint"].filter((p) => p !== undefined).join(".")}`,
1334
+ });
1223
1335
  function nescNamePortion(id) {
1224
1336
  let name = "National Electrical Safety Code";
1225
1337
  if (id["registered"] === true)
@@ -259,9 +259,34 @@ function normalizeRevisionNotation(input) {
259
259
  cleaned = cleaned.replace(/(\/D-?[0-9A-Za-z.]+)\s+[Rr][Ee][Vv]\s*(\d+)/, "$1/R-$2");
260
260
  cleaned = cleaned.replace(/[-/_.]?\s?[Rr][Ee][Vv][-\s]?(\d+)(\/D-?[0-9A-Za-z.]+)/, "$2/R-$1");
261
261
  cleaned = cleaned.replace(/(\d)[-._]?\s?[Rr][Ee][Vv]\s*(\d+)\s*$/, "$1/R-$2");
262
- // Lettered inline revisions stripped.
263
- cleaned = cleaned.replace(/[-/_.]?\s?[Rr][Ee][Vv][-\s]?[A-Za-z0-9]+(?=\/D-?[0-9A-Za-z])/, "");
264
- cleaned = cleaned.replace(/(\d)[Rr][Ee][Vv][A-Za-z0-9]+\s*$/, "$1");
262
+ // The joint IEEE/CSA catalogue spells the type word before the
263
+ // project number ("IEEE/CSA Draft P844.1/D2, Sept, 2016"): the P
264
+ // already carries the project semantics (docs/IEEE-DRAFT-STAGES.md
265
+ // §1.1), so the redundant word drops and the joint route parses.
266
+ cleaned = cleaned.replace(/IEEE\/CSA Draft (P[A-Z0-9])/g, "IEEE/CSA $1");
267
+ // The corrigendum's own draft rides before the ordinal in the house
268
+ // order ("PC37.101-2006/D2, 2007/Cor. 1"): the catalogue spellings
269
+ // ".../Cor 1, D4, July 2015" and ".../Cor 1-201x/D5, April 2012"
270
+ // re-order into it, with the draft's date glued -YYYY-MM (pubid
271
+ // ruling 2026-10-14: no comma dates).
272
+ const COR_MONTHS = {
273
+ January: "01", February: "02", March: "03", April: "04", May: "05",
274
+ June: "06", July: "07", August: "08", September: "09", Sept: "09",
275
+ October: "10", November: "11", December: "12", Jan: "01", Feb: "02",
276
+ Mar: "03", Apr: "04", Jun: "06", Jul: "07", Aug: "08", Sep: "09",
277
+ Oct: "10", Nov: "11", Dec: "12",
278
+ };
279
+ cleaned = cleaned.replace(/\/Cor(?:rigendum)? (\d+(?:-[0-9xX]{4})?)\/(D[A-Z0-9][^,]*), ([A-Za-z]+) (\d{4})/g, (_m, cor, draft, mon, year) => {
280
+ const month = COR_MONTHS[mon.charAt(0).toUpperCase() + mon.slice(1).toLowerCase()];
281
+ return month === undefined ? _m : `/${draft}-${year}-${month}/Cor. ${cor}`;
282
+ });
283
+ cleaned = cleaned.replace(/\/Cor(?:rigendum)? (\d+), (D[A-Z0-9][^,]*), ([A-Za-z]+) (\d{4})/g, (_m, cor, draft, mon, year) => {
284
+ const month = COR_MONTHS[mon.charAt(0).toUpperCase() + mon.slice(1).toLowerCase()];
285
+ return month === undefined ? _m : `/${draft}-${year}-${month}/Cor. ${cor}`;
286
+ });
287
+ // Lettered inline revisions are IDENTITY (pubid ruling 2026-10-14):
288
+ // the grammar captures them natively with their separator; the former
289
+ // strip rewrite here dropped the letters and is gone.
265
290
  return cleaned;
266
291
  }
267
292
  function normalizeJointStageSpellings(input) {
@@ -271,7 +296,6 @@ function normalizeJointStageSpellings(input) {
271
296
  const num = "(P?\\d+(?:[.-]\\d+)?)";
272
297
  const tail = "(,? (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Sept|Oct|Nov|Dec)[a-z]* \\d{4}|$|-\\d{4}(?:-\\d\\d)?)";
273
298
  cleaned = cleaned.replace(/(\d)_D(\d)/g, "$1/D$2");
274
- cleaned = cleaned.replace(/,(?=[A-Za-z])/, ", ");
275
299
  cleaned = cleaned.replace(new RegExp(`^${pubs}${num}/(D[\\d.]+)/ ?${stage}\\b`), "$1$4 $2/$3");
276
300
  cleaned = cleaned.replace(new RegExp(`^${pubs}${num}/ ${stage}${tail}`), "$1$3 $2$4");
277
301
  cleaned = cleaned.replace(new RegExp(`^${pubs}${num}/${stage} (\\d{4})\\b`), "$1$3 $2-$4");
@@ -287,7 +311,6 @@ function normalizeJointStageSpellings(input) {
287
311
  // The Parser.parse cleaning pipeline, in Ruby order.
288
312
  export function preprocessIeee(input) {
289
313
  let cleaned = input.replace(/\.pdf$/i, "");
290
- cleaned = cleaned.replace(/_(FDIS|CDV|CD|DIS|WD|PWI|NP)/g, "/$1");
291
314
  cleaned = cleaned.replace(/\s+/g, " ");
292
315
  cleaned = cleaned.replace(/\b(ISO\/IEC\/IEEE|IEEE\/ISO\/IEC|IEEE\/IEC\/ISO|ISO\/IEEE|IEC\/IEEE|IEEE\/IEC|ISO\/IEC)\/ ?(FDIS|FCD|CDV|DIS\d?|CD\d?|WD|PWI|NP)\b/g, "$1 $2");
293
316
  cleaned = normalizeRevisionNotation(cleaned);
@@ -363,7 +386,6 @@ export function preprocessIeee(input) {
363
386
  if (/^(\d+[-.]\d+)\/D\d+/.test(cleaned)) {
364
387
  cleaned = `IEEE P${cleaned}`;
365
388
  }
366
- cleaned = cleaned.replace(/\/Preprint\b/g, "");
367
389
  cleaned = cleaned.replaceAll("Proposed Revision of", "Revision of");
368
390
  cleaned = cleaned.replace(/\bammended\b/gi, "amended");
369
391
  cleaned = cleaned.replace(/(\/INT|\/Cor\s+\d+-\d{4})\./g, "$1");
@@ -70,8 +70,14 @@ function buildRules() {
70
70
  rule("simple_series", () => literalAlternation(SIMPLE_SERIES).as("series"));
71
71
  rule("number_suffix", () => str("U")
72
72
  .then(lowerLetter)
73
- .or(letter.then(literalAlternation([
74
- "ec", "ndex", "nsert", "rrata", "raft", "pp", "s", "t",
73
+ .or(
74
+ // An "r" directly followed by a digit is the revision marker
75
+ // (pubid#170), not a suffix — leave it for the edition rules.
76
+ str("r")
77
+ .then(digit)
78
+ .absent()
79
+ .then(letter, literalAlternation([
80
+ "ec", "ndex", "ndx", "nsert", "rrata", "raft", "pp", "s", "t",
75
81
  "hi", "iet", "ort", "r", "p",
76
82
  ]).absent(), digits.maybe())));
77
83
  rule("digits_with_suffix", () => digits.then(ref(rules, "number_suffix").then(digit.absent()).maybe()));
@@ -105,20 +111,23 @@ function buildRules() {
105
111
  .absent()
106
112
  .then(str("draft").absent(), (digits
107
113
  .then(literalAlternation(["supp", "sup"]), digit.absent(), letter.absent())
108
- .or(digits.then(str("r"), digits, str("U"), lowerLetter), digits.then(str("r"), digits, letter), digits.as("number_only").then(str("r"), digits.as("edition_id")), digits.then(str("_"), digits, dash, digits, upperLetter.maybe()), lowerLetter.then(dash, digits), digits.then(str("pt"), digits, dash.absent()), digits.then(upperLetter), digits
114
+ .or(digits.then(str("r"), digits, str("U"), lowerLetter), digits.then(str("r"), digits, letter), digits.as("number_only").then(str("r"), digits.then(dot.then(digits).maybe()).as("edition_id")), digits.then(str("_"), digits, dash, digits, upperLetter.maybe()), lowerLetter.then(dash, digits), digits.then(str("pt"), digits, dash.absent()), digits.then(upperLetter), digits
109
115
  .as("number_only")
110
116
  .then(str("r"), letter.as("letter"))
111
- .as("revision_letter"), str("r").then(letter).as("revision_letter_suffix"), str("r").then(digits.as("edition_id")).as("revision_simple"), str("NCNR"), str("PERMIS"), str("BFRL"), upperLetter.repeat(1, 3), rules["digits_with_suffix"]
117
+ .as("revision_letter"), str("r").then(letter).as("revision_letter_suffix"), str("r")
118
+ .then(digits.then(dot.then(digits).maybe()).as("edition_id"))
119
+ .as("revision_simple"), str("NCNR"), str("PERMIS"), str("BFRL"), upperLetter.repeat(1, 3), rules["digits_with_suffix"]
112
120
  .then(dash.then(monthAbbrev(), digits, slash).absent()), lowerLetter.then(digit.absent()))).as("second_number")));
121
+ const dottedDigits = () => digits.then(dot.then(digits).maybe());
113
122
  const editionInner = () => space
114
123
  .maybe()
115
- .then(str("e"), digits.as("edition_id"))
124
+ .then(str("e"), dottedDigits().as("edition_id"))
116
125
  .as("edition_e")
117
126
  .or(space
118
- .then(str("r"), digits.as("edition_id"), letter.as("edition_letter"))
119
- .as("edition_r_with_space_letter"), space.then(str("r"), digits.as("edition_id")).as("edition_r_with_space"), str("r")
120
- .then(digits.as("edition_id"), letter.as("edition_letter"))
121
- .as("edition_r_no_space_letter"), str("r").then(digits.as("edition_id")).as("edition_r_no_space"), space
127
+ .then(str("r"), dottedDigits().as("edition_id"), letter.as("edition_letter"))
128
+ .as("edition_r_with_space_letter"), space.then(str("r"), dottedDigits().as("edition_id")).as("edition_r_with_space"), str("r")
129
+ .then(dottedDigits().as("edition_id"), letter.as("edition_letter"))
130
+ .as("edition_r_no_space_letter"), str("r").then(dottedDigits().as("edition_id")).as("edition_r_no_space"), space
122
131
  .maybe()
123
132
  .then(str("rev"), space.maybe(), digits.as("edition_id"))
124
133
  .as("edition_rev"), dash.then(match("[1-9]").as("edition_id"), digit.absent()).as("edition_historical"), dash
@@ -1,5 +1,13 @@
1
- import { ParseFailed, parseGrammar } from "../../grammar/engine.js";
2
- import { nistGrammar } from "./grammar.js";
1
+ import { PubidRuntime, toBuilderTree } from "../../parg/runtime.js";
2
+ // The grammar runtime is the baked PARG artifact (pubid-grammar
3
+ // artifacts/nist.json via the parsanol engine) — the same artifact the
4
+ // gem runs, so grammar changes land once and this port cannot drift.
5
+ // Only the preprocessor, builder and model are ported code.
6
+ let cachedNistRuntime;
7
+ function cachedRuntime(input) {
8
+ cachedNistRuntime ??= PubidRuntime.load("nist");
9
+ return cachedNistRuntime.parseShape(input);
10
+ }
3
11
  import { preprocessNist } from "./preprocessor.js";
4
12
  import { NIST_CLASSES, NistCode, NistEdition, NistPart, NistStage, NistSupplement, NistTranslation, NistUpdate, NistVersion, NistVolume, SP_SUBSERIES, seriesPolicy, } from "./model.js";
5
13
  /**
@@ -1238,11 +1246,12 @@ export function nistGrammarImplementation() {
1238
1246
  return {
1239
1247
  parse(input) {
1240
1248
  const { cleaned, format } = preprocessNist(input);
1241
- const tree = parseGrammar(nistGrammar, cleaned);
1242
- if (typeof tree !== "object" || tree === null) {
1243
- throw new ParseFailed("NIST: unexpected parse tree", 0);
1244
- }
1245
- return builder.build(tree, format);
1249
+ const base = cleaned.replace(/\s*\(all parts\)\s*$/, "");
1250
+ const tree = toBuilderTree(cachedRuntime(base));
1251
+ const marked = base !== cleaned
1252
+ ? { ...tree, all_parts: true }
1253
+ : tree;
1254
+ return builder.build(marked, format);
1246
1255
  },
1247
1256
  };
1248
1257
  }
@@ -192,8 +192,16 @@ export function preprocessNist(input) {
192
192
  cleaned = cleaned.replace(/(\d)(v\d+[a-z]-[a-z])/g, "$1 $2");
193
193
  cleaned = cleaned.replace(/(\d)(v\d+[A-Z])/g, "$1 $2");
194
194
  cleaned = cleaned.replace(/(v\d+)([A-Z])-([A-Z])/g, (_m, v, a, b) => `${v}${a}-${b.toLowerCase()}`);
195
- // normalize_edition_year_suffix
195
+ // normalize_edition_year_suffix — a dotted minor after the dot stays
196
+ // attached (pubid#170): "2006ed.1" → "e2006.1", never "e20061".
197
+ cleaned = cleaned.replace(/(\d{4})ed\.(\d+)/g, "e$1.$2");
196
198
  cleaned = cleaned.replace(/(\d{4})ed\./g, "e$1");
199
+ // normalize_glued_dotted_edition — a glued dotted e-edition needs the
200
+ // space the spaced form has (pubid#170): "800-53e2.1" → "800-53 e2.1".
201
+ // The minor is 1-2 digits; a 4-digit tail is the additional-text year of
202
+ // "NBS CIRC 11e2.1915", which keeps its existing tree. Dot-less "800-53e2"
203
+ // keeps its number-bound tree.
204
+ cleaned = cleaned.replace(/(\d)e(\d+\.\d{1,2})(?!\d)/g, "$1 e$2");
197
205
  // normalize_revision_with_letter
198
206
  cleaned = cleaned.replace(/(\d+)(r\d{1,2})([a-z])(?=-|[A-Z]|$)/g, (_m, n, r, l) => `${n}${r}${l.toUpperCase()}`);
199
207
  cleaned = cleaned.replace(/(\d+)(r\d{1,2})(?![a-zA-Z])(?=[A-Z]|-(?=[A-Z])|\/(?:upd|errata|insert))/g, "$1 $2");
@@ -0,0 +1,25 @@
1
+ import { PargRuntime as EngineRuntime } from "parsanol";
2
+ export type { FieldRequirement, EntrySchema, Schema } from "parsanol";
3
+ export { PargRuntime } from "parsanol";
4
+ /** The artifacts directory: PARG_ARTIFACT_DIR, else the sibling
5
+ * pubid-grammar checkout of the pubid workspace, else the vendored copy
6
+ * shipped in the published package (refreshed by `npm run artifact`). */
7
+ export declare function artifactsDir(): string;
8
+ /** The pubid flavor runtime: loads baked artifacts from the workspace. */
9
+ export declare class PubidRuntime extends EngineRuntime {
10
+ static artifactsDir(): string;
11
+ static load(grammar: string, entry?: string): EngineRuntime;
12
+ }
13
+ /**
14
+ * The artifact emits capture leaves ({value, line, column, offset,
15
+ * length}) and a top-level sequence of capture hashes. This mirrors the
16
+ * gem's Pubid::Parg::Backend.to_builder_hash: scalarize leaves, fold the
17
+ * top-level list into one hash (last key wins — parslet's sequence fold).
18
+ */
19
+ export declare function toBuilderTree(shape: unknown): unknown;
20
+ /**
21
+ * Strip the shape metadata an artifact parse carries (offset/line/column
22
+ * wrappers) down to the plain string/array capture tree the builders
23
+ * consume — the same tree shape the hand-written grammars produced.
24
+ */
25
+ export declare function normalizeShape(node: unknown): unknown;
@@ -0,0 +1,76 @@
1
+ //! The PARG artifact runtime for pubid: a thin adapter over the
2
+ //! published `parsanol` npm package (the wasm engine), adding the
3
+ //! pubid-workspace artifacts directory convention. No grammar code and
4
+ //! no vendored engine copy live in this repo; the baked artifact is
5
+ //! the contract and the engine ships from the registry.
6
+ import { existsSync } from "node:fs";
7
+ import { fileURLToPath } from "node:url";
8
+ import { dirname, join } from "node:path";
9
+ import { PargRuntime as EngineRuntime } from "parsanol";
10
+ export { PargRuntime } from "parsanol";
11
+ /** Walk up from this module to the package root (which holds package.json). */
12
+ function packageRoot() {
13
+ let dir = dirname(fileURLToPath(import.meta.url));
14
+ for (; !existsSync(join(dir, "package.json")); dir = dirname(dir)) {
15
+ if (dir === dirname(dir))
16
+ throw new Error("package.json not found above the parg runtime");
17
+ }
18
+ return dir;
19
+ }
20
+ /** The artifacts directory: PARG_ARTIFACT_DIR, else the sibling
21
+ * pubid-grammar checkout of the pubid workspace, else the vendored copy
22
+ * shipped in the published package (refreshed by `npm run artifact`). */
23
+ export function artifactsDir() {
24
+ const env = process.env.PARG_ARTIFACT_DIR;
25
+ if (env)
26
+ return env;
27
+ const sibling = join(packageRoot(), "..", "pubid-grammar", "artifacts");
28
+ if (existsSync(sibling))
29
+ return sibling;
30
+ return join(packageRoot(), "vendor", "parg");
31
+ }
32
+ /** The pubid flavor runtime: loads baked artifacts from the workspace. */
33
+ export class PubidRuntime extends EngineRuntime {
34
+ static artifactsDir() {
35
+ return artifactsDir();
36
+ }
37
+ static load(grammar, entry) {
38
+ return EngineRuntime.fromFile(join(artifactsDir(), `${grammar}.json`), entry);
39
+ }
40
+ }
41
+ /**
42
+ * The artifact emits capture leaves ({value, line, column, offset,
43
+ * length}) and a top-level sequence of capture hashes. This mirrors the
44
+ * gem's Pubid::Parg::Backend.to_builder_hash: scalarize leaves, fold the
45
+ * top-level list into one hash (last key wins — parslet's sequence fold).
46
+ */
47
+ export function toBuilderTree(shape) {
48
+ const normalized = normalizeShape(shape);
49
+ if (Array.isArray(normalized) &&
50
+ normalized.length > 0 &&
51
+ normalized.every((n) => typeof n === "object" && n !== null && !Array.isArray(n))) {
52
+ return Object.assign({}, ...normalized);
53
+ }
54
+ return normalized;
55
+ }
56
+ /**
57
+ * Strip the shape metadata an artifact parse carries (offset/line/column
58
+ * wrappers) down to the plain string/array capture tree the builders
59
+ * consume — the same tree shape the hand-written grammars produced.
60
+ */
61
+ export function normalizeShape(node) {
62
+ if (Array.isArray(node))
63
+ return node.map(normalizeShape);
64
+ if (typeof node === "object" && node !== null) {
65
+ const rec = node;
66
+ if (typeof rec["value"] === "string" && Object.keys(rec).length >= 1 &&
67
+ "offset" in rec) {
68
+ return rec["value"];
69
+ }
70
+ const out = {};
71
+ for (const [k, v] of Object.entries(rec))
72
+ out[k] = normalizeShape(v);
73
+ return out;
74
+ }
75
+ return node;
76
+ }
@@ -0,0 +1,4 @@
1
+ export { loadSuites, readSuite, runSuite, type Suite, type SuiteTest, type TestKind, } from "parsanol/suite";
2
+ export { PubidRuntime } from "./runtime.js";
3
+ /** The suites directory: PARG_SUITES_DIR, else beside the artifacts. */
4
+ export declare function suitesDir(): string;
@@ -0,0 +1,14 @@
1
+ //! pgtest suite runner: re-exports the engine suite machinery from the
2
+ //! published `parsanol` package; only the suites directory convention
3
+ //! (sibling pubid-grammar checkout) is pubid-specific.
4
+ export { loadSuites, readSuite, runSuite, } from "parsanol/suite";
5
+ export { PubidRuntime } from "./runtime.js";
6
+ import { join } from "node:path";
7
+ import { artifactsDir } from "./runtime.js";
8
+ /** The suites directory: PARG_SUITES_DIR, else beside the artifacts. */
9
+ export function suitesDir() {
10
+ const env = process.env.PARG_SUITES_DIR;
11
+ if (env)
12
+ return env;
13
+ return join(artifactsDir(), "..", "suites");
14
+ }
@@ -552,6 +552,13 @@ export const URN_PARSERS = {
552
552
  const parts = splitParts(body);
553
553
  const [, code, year] = parts;
554
554
  let text = `IEEE Std ${code}`;
555
+ // A "-YYYY-MM" tail is a dated reference (year + numeric month),
556
+ // mirroring the gem's urn parser; it re-parses through the
557
+ // trailing-month-year clause so the render prints "21, 11 1976"
558
+ // like the reference.
559
+ const dated = /^(.*)-(\d{4})-(\d{2})$/.exec(text);
560
+ if (dated)
561
+ return parse(`${dated[1]}, ${dated[3]} ${dated[2]}`);
555
562
  if (year)
556
563
  text += `-${year}`;
557
564
  return parse(text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubid/pubid",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "TypeScript PubID identifiers: resolution, normalization and canonical hashes for every identifier in the pubid-testsuite corpus (41 standards bodies), with a conformance runner mirroring the Ruby reference",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -17,15 +17,17 @@
17
17
  "artifact": "tsc -p tsconfig.json && node scripts/build-artifact.mjs"
18
18
  },
19
19
  "dependencies": {
20
+ "parsanol": "^0.10.3",
20
21
  "yaml": "^2.5.0"
21
22
  },
22
23
  "devDependencies": {
23
- "typescript": "^5.6.0",
24
- "@types/node": "^20.14.0"
24
+ "@types/node": "^20.14.0",
25
+ "typescript": "^5.6.0"
25
26
  },
26
27
  "files": [
27
28
  "dist",
28
- "artifact"
29
+ "artifact",
30
+ "vendor/parg"
29
31
  ],
30
32
  "keywords": [
31
33
  "pubid",
@@ -0,0 +1 @@
1
+ {"bindings":null,"version":"0.1.0","grammar":"PubidAdobe","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Entity":{"atom":48}},{"Entity":{"atom":30}},{"Entity":{"atom":9}},{"Str":{"pattern":"Adobe"}},{"Entity":{"atom":5}},{"Str":{"pattern":" "}},{"Str":{"pattern":"TN"}},{"Entity":{"atom":5}},{"Repetition":{"atom":7,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[3,4,6,8]}},{"Entity":{"atom":24}},{"Str":{"pattern":"Adobe"}},{"Entity":{"atom":5}},{"Str":{"pattern":"Technical"}},{"Entity":{"atom":5}},{"Str":{"pattern":"Note"}},{"Entity":{"atom":5}},{"Repetition":{"atom":16,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"#"}},{"Repetition":{"atom":18,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":5}},{"Repetition":{"atom":20,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[17,19,21]}},{"Repetition":{"atom":22,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[11,12,13,14,15,23]}},{"Entity":{"atom":29}},{"Str":{"pattern":"ATN"}},{"Entity":{"atom":5}},{"Repetition":{"atom":27,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[26,28]}},{"Alternative":{"atoms":[2,10,25]}},{"Entity":{"atom":32}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":31,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"number","atom":33}},{"Entity":{"atom":36}},{"Str":{"pattern":"."}},{"Entity":{"atom":42}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[_]"}},{"Alternative":{"atoms":[38,39,40,41]}},{"Repetition":{"atom":37,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"slug","atom":43}},{"Sequence":{"atoms":[35,44]}},{"Repetition":{"atom":45,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[1,34,46]}},{"Named":{"name":"tech_note","atom":47}},{"Entity":{"atom":53}},{"Entity":{"atom":32}},{"Repetition":{"atom":50,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"number","atom":51}},{"Named":{"name":"tech_note_bare","atom":52}},{"Entity":{"atom":87}},{"Str":{"pattern":"Adobe Publication "}},{"Repetition":{"atom":55,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":86}},{"Entity":{"atom":82}},{"Entity":{"atom":75}},{"Entity":{"atom":69}},{"Entity":{"atom":62}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Repetition":{"atom":61,"min":1,"max":null,"tag":"Repetition"}},{"Entity":{"atom":67}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Alternative":{"atoms":[65,66]}},{"Repetition":{"atom":64,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[63,68]}},{"Entity":{"atom":71}},{"Str":{"pattern":"-"}},{"Entity":{"atom":69}},{"Sequence":{"atoms":[70,72]}},{"Repetition":{"atom":73,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[60,74]}},{"Named":{"name":"slug","atom":59}},{"Entity":{"atom":71}},{"Entity":{"atom":32}},{"Repetition":{"atom":78,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"version","atom":79}},{"Sequence":{"atoms":[76,77,80]}},{"Named":{"name":"publication_version","atom":81}},{"Entity":{"atom":85}},{"Entity":{"atom":75}},{"Named":{"name":"slug","atom":84}},{"Alternative":{"atoms":[58,83]}},{"Sequence":{"atoms":[56,57]}},{"Alternative":{"atoms":[0,49,54]}}],"root":88},"bindings":[]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule tech_note_prefix: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule tech_note_prefix: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule tech_note_prefix: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule publication_slug_or_version: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule identifier: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"Adobe TN 15","expect":{}},{"entry":"identifier","kind":"accept","input":"ATN 30","expect":{}},{"entry":"identifier","kind":"accept","input":"Adobe Technical Note #12","expect":{}},{"entry":"identifier","kind":"reject","input":"###","expect":{}}],"docs":{},"source":"# Port of lib/pubid/adobe/parser.rb: tech notes (Adobe TN 5014, legacy\n# \"Adobe Technical Note #5014\", ATN5014, bare 5014) and publications\n# (adobe-glyph-list, adobe-japan1-7 with the version split tried first).\n\ngrammar PubidAdobe version \"0.1.0\" {\n tech_note_tn_prefix = \"Adobe\" space \"TN\" [space]\n tech_note_long_prefix = \"Adobe\" space \"Technical\" space \"Note\" [[space] [\"#\"] [space]]\n tech_note_atn_prefix = \"ATN\" [space]\n tech_note_prefix = tech_note_tn_prefix / tech_note_long_prefix / tech_note_atn_prefix\n # Capture precedence: (*rep) as name wraps the whole repetition, so a\n # digit run joins to one string instead of re-keying every character;\n # slug is captured exactly once (the bare form) or as a sibling of\n # version inside the publication_version capture.\n tech_note = (tech_note_prefix (1*digit) as number [(dot (1*slug_char) as slug)]) as tech_note\n bare_number = ((1*digit) as number) as tech_note_bare\n slug_segment = 1*lower1 *lowerd\n slug_text = slug_segment *(dash slug_segment)\n slug_inner = (slug_text) as slug\n publication_with_version = ((slug_text) as slug dash (1*digit) as version) as publication_version\n publication_slug_or_version = publication_with_version / slug_inner\n publication = [\"Adobe Publication \"] publication_slug_or_version\n identifier = tech_note / bare_number / publication\n slug_char = %x41-5A / %x61-7A / %x30-39 / %x5F\n lower1 = %x61-7A\n lowerd = %x61-7A / %x30-39\n digit = %x30-39\n space = \" \"\n dot = \".\"\n dash = \"-\"\n entry identifier: identifier\n\n test {\n accept \"Adobe TN 15\"\n accept \"ATN 30\"\n accept \"Adobe Technical Note #12\"\n reject \"###\"\n }\n}\n","checksum":"sha256:38e8488c3da1c0f4a36fe2f63d87fee551d9317c5dae62f56972da1efe842a52"}