@glion/parser 0.15.3 → 0.19.0

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/README.md CHANGED
@@ -33,10 +33,9 @@ Register the parser as the reader for a `unified` processor. The parser reads fr
33
33
 
34
34
  ### Options
35
35
 
36
- | Option | Type | Description |
37
- | ------------------------ | --------------------- | ---------------------------------------------------------------------------------------- |
38
- | `delimiters` | `Partial<Delimiters>` | Override one or more delimiters. The parser merges with MSH-derived values and defaults. |
39
- | `experimental.emptyMode` | `"empty-array"` | Opt in to the empty-array representation of empty fields (see below). |
36
+ | Option | Type | Description |
37
+ | ------------ | --------------------- | ---------------------------------------------------------------------------------------- |
38
+ | `delimiters` | `Partial<Delimiters>` | Override one or more delimiters. The parser merges with MSH-derived values and defaults. |
40
39
 
41
40
  The default delimiters are `|` (field), `^` (component), `~` (repetition), `&` (subcomponent), `\` (escape), and `\r` (segment).
42
41
 
@@ -61,6 +60,16 @@ const customTree = unified()
61
60
 
62
61
  Options accept a partial `Delimiters` object, so only the characters you want to override need to be provided.
63
62
 
63
+ ### `Hl7v2Processor`
64
+
65
+ The `unified` `Processor<Root, Root | undefined, Root | undefined>` type every HL7v2 pipeline satisfies — head and tail admit `undefined` so the bare `unified().use(hl7v2Parser).freeze()` is assignable. Use it to type a processor built with `unified().use(hl7v2Parser)` regardless of which transformer or compiler plugins are added. `@glion/mllp` re-exports it unchanged.
66
+
67
+ ```ts
68
+ import type { Hl7v2Processor } from "@glion/parser";
69
+
70
+ const processor: Hl7v2Processor = unified().use(hl7v2Parser).freeze();
71
+ ```
72
+
64
73
  ## Parsing model
65
74
 
66
75
  - **Pull-based tokenizer.** Single pass, minimal object allocations — suitable for high-throughput ingestion.
@@ -68,20 +77,17 @@ Options accept a partial `Delimiters` object, so only the characters you want to
68
77
  - **`unist`-compatible output.** Nodes follow the `@glion/ast` spec and integrate with `unist-util-visit`, `unist-builder`, and the wider Glion plugin ecosystem.
69
78
  - **Streaming-friendly.** The pull-based design is ready for streaming ingestion even though the current public API takes a complete string.
70
79
 
71
- ### Experimental: empty-array mode
80
+ ### Empty-field representation
72
81
 
73
- By default the parser represents empty fields with full scaffolding (`Field → FieldRepetition → Component → Subcomponent` with `value: ""`). Passing `experimental: { emptyMode: "empty-array" }` switches to a more compact representation where empty parents carry `children: []` instead of nested empties.
82
+ Empty fields, repetitions, and components are represented with `children: []` rather than a placeholder leaf:
74
83
 
75
84
  ```ts
76
85
  import { unified } from "unified";
77
86
  import { hl7v2Parser } from "@glion/parser";
78
87
 
79
- const tree = unified()
80
- .use(hl7v2Parser, { experimental: { emptyMode: "empty-array" } })
81
- .parse("PID|1||");
88
+ const tree = unified().use(hl7v2Parser).parse("PID|1||");
82
89
 
83
- // PID-2 (empty field) becomes: { type: "field", children: [] }
84
- // rather than: Field → Rep → Comp → Sub with value: ""
90
+ // PID-2 (empty field): { type: "field", children: [] }
85
91
  ```
86
92
 
87
93
  Rules:
@@ -90,14 +96,12 @@ Rules:
90
96
  - **Parent nodes** (`Field`, `FieldRepetition`, `Component`) with no content carry `children: []`.
91
97
  - **Presence vs value:** a node in the parent's `children` array means the position exists; empty `children` means the position has no value.
92
98
 
93
- | Wire format | Legacy mode | Empty-array mode |
94
- | --------------- | ---------------------------------------------- | -------------------------------------- |
95
- | `PID\|1\|\|` | Field → Rep → Comp → Sub("") | Field(children: []) |
96
- | `PID\|1\|^\|` | Field → Rep → [Comp → Sub(""), Comp → Sub("")] | Field → Rep → [Comp[], Comp[]] |
97
- | `PID\|1\|~\|` | Field → [Rep → Comp → Sub(""), Rep → ...] | Field → [Rep[], Rep[]] |
98
- | `PID\|1\|ABC\|` | Field → Rep → Comp → Sub("ABC") | Field → Rep → Comp → Sub("ABC") (same) |
99
-
100
- For messages with many empty fields, the empty-array representation reduces node count by 37–63% and improves sparse-message parsing throughput by about 11%. Empty-array mode is planned to become the default; the legacy representation will be retired.
99
+ | Wire format | Representation |
100
+ | --------------- | ------------------------------- |
101
+ | `PID\|1\|\|` | Field(children: []) |
102
+ | `PID\|1\|^\|` | Field → Rep → [Comp[], Comp[]] |
103
+ | `PID\|1\|~\|` | Field → [Rep[], Rep[]] |
104
+ | `PID\|1\|ABC\|` | Field → Rep → Comp → Sub("ABC") |
101
105
 
102
106
  ## Part of Glion
103
107
 
package/dist/index.js CHANGED
@@ -78,9 +78,9 @@ function createSubcomponent(start) {
78
78
  value: ""
79
79
  };
80
80
  }
81
- function createComponent(start, mode) {
81
+ function createComponent(start) {
82
82
  return {
83
- children: mode === "empty" ? [] : [createSubcomponent(start)],
83
+ children: [],
84
84
  position: {
85
85
  end: start,
86
86
  start
@@ -88,9 +88,9 @@ function createComponent(start, mode) {
88
88
  type: "component"
89
89
  };
90
90
  }
91
- function createFieldRepetition(start, mode) {
91
+ function createFieldRepetition(start) {
92
92
  return {
93
- children: mode === "empty" ? [] : [createComponent(start, mode)],
93
+ children: [],
94
94
  position: {
95
95
  end: start,
96
96
  start
@@ -98,9 +98,9 @@ function createFieldRepetition(start, mode) {
98
98
  type: "field-repetition"
99
99
  };
100
100
  }
101
- function createField(start, mode) {
101
+ function createField(start) {
102
102
  return {
103
- children: mode === "empty" ? [] : [createFieldRepetition(start, mode)],
103
+ children: [],
104
104
  position: {
105
105
  end: start,
106
106
  start
@@ -108,8 +108,7 @@ function createField(start, mode) {
108
108
  type: "field"
109
109
  };
110
110
  }
111
- function createParserCore(ctx) {
112
- const mode = ctx.emptyMode;
111
+ function createParserCore() {
113
112
  const root = {
114
113
  children: [],
115
114
  type: "root"
@@ -161,58 +160,45 @@ function createParserCore(ctx) {
161
160
  const ensureSegment = (start) => {
162
161
  if (!seg) {
163
162
  openSegment("", {
164
- start,
165
- end: start
163
+ end: start,
164
+ start
166
165
  });
167
166
  justSetSegmentName = false;
168
167
  }
169
168
  };
170
169
  const openField = (start) => {
171
170
  ensureSegment(start);
172
- field = createField(start, mode);
171
+ field = createField(start);
173
172
  seg.children.push(field);
174
- if (mode === "empty") {
175
- rep = null;
176
- comp = null;
177
- currentSub = null;
178
- } else {
179
- rep = field.children[0] ?? null;
180
- comp = rep?.children[0] ?? null;
181
- currentSub = comp?.children[0] ?? null;
182
- }
173
+ rep = null;
174
+ comp = null;
175
+ currentSub = null;
183
176
  segmentHasContent = true;
184
177
  };
185
178
  const openRepetition = (start) => {
186
179
  if (!field) {
187
180
  openField(start);
188
- if (mode === "empty") {
189
- const emptyRep = createFieldRepetition(start, mode);
190
- field.children.push(emptyRep);
191
- }
181
+ const emptyRep = createFieldRepetition(start);
182
+ field.children.push(emptyRep);
192
183
  }
193
- if (mode === "empty" && field.children.length === 0) {
194
- const emptyRep = createFieldRepetition(start, mode);
184
+ if (field.children.length === 0) {
185
+ const emptyRep = createFieldRepetition(start);
195
186
  field.children.push(emptyRep);
196
187
  }
197
- rep = createFieldRepetition(start, mode);
188
+ rep = createFieldRepetition(start);
198
189
  field.children.push(rep);
199
- if (mode === "empty") {
200
- comp = null;
201
- currentSub = null;
202
- } else {
203
- comp = rep.children[0] ?? null;
204
- currentSub = comp?.children[0] ?? null;
205
- }
190
+ comp = null;
191
+ currentSub = null;
206
192
  segmentHasContent = true;
207
193
  };
208
194
  const ensureComponent = (start) => {
209
195
  if (!field) openField(start);
210
196
  if (!rep) {
211
- rep = createFieldRepetition(start, mode);
197
+ rep = createFieldRepetition(start);
212
198
  field.children.push(rep);
213
199
  }
214
200
  if (!comp) {
215
- comp = createComponent(start, mode);
201
+ comp = createComponent(start);
216
202
  rep.children.push(comp);
217
203
  }
218
204
  segmentHasContent = true;
@@ -220,48 +206,33 @@ function createParserCore(ctx) {
220
206
  const openComponent = (start) => {
221
207
  if (!field) {
222
208
  openField(start);
223
- if (mode === "empty") {
224
- rep = createFieldRepetition(start, mode);
225
- field.children.push(rep);
226
- const emptyComp = createComponent(start, mode);
227
- rep.children.push(emptyComp);
228
- }
209
+ rep = createFieldRepetition(start);
210
+ field.children.push(rep);
211
+ const emptyComp = createComponent(start);
212
+ rep.children.push(emptyComp);
229
213
  }
230
214
  if (!rep) {
231
- rep = createFieldRepetition(start, mode);
215
+ rep = createFieldRepetition(start);
232
216
  field.children.push(rep);
233
- if (mode === "empty") {
234
- const emptyComp = createComponent(start, mode);
235
- rep.children.push(emptyComp);
236
- }
237
- } else if (mode === "empty" && rep.children.length === 0) {
238
- const emptyComp = createComponent(start, mode);
217
+ const emptyComp = createComponent(start);
218
+ rep.children.push(emptyComp);
219
+ } else if (rep.children.length === 0) {
220
+ const emptyComp = createComponent(start);
239
221
  rep.children.push(emptyComp);
240
222
  }
241
- comp = createComponent(start, mode);
223
+ comp = createComponent(start);
242
224
  rep.children.push(comp);
243
- currentSub = mode === "empty" ? null : comp.children[0] ?? null;
225
+ currentSub = null;
244
226
  segmentHasContent = true;
245
227
  };
246
228
  const ensureForText = (start) => {
247
- if (!field) {
248
- openField(start);
249
- if (mode !== "empty") return;
250
- }
229
+ if (!field) openField(start);
251
230
  if (!rep) {
252
- if (mode !== "empty") {
253
- openRepetition(start);
254
- return;
255
- }
256
- rep = createFieldRepetition(start, mode);
231
+ rep = createFieldRepetition(start);
257
232
  field.children.push(rep);
258
233
  }
259
234
  if (!comp) {
260
- if (mode !== "empty") {
261
- openComponent(start);
262
- return;
263
- }
264
- comp = createComponent(start, mode);
235
+ comp = createComponent(start);
265
236
  rep.children.push(comp);
266
237
  }
267
238
  if (!currentSub) {
@@ -309,7 +280,7 @@ function createParserCore(ctx) {
309
280
  lastContentEnd = tok.position.end;
310
281
  documentEnd = tok.position.end;
311
282
  ensureComponent(tok.position.start);
312
- if (mode === "empty" && comp.children.length === 0) {
283
+ if (comp.children.length === 0) {
313
284
  const emptySub = createSubcomponent(tok.position.start);
314
285
  comp.children.push(emptySub);
315
286
  }
@@ -346,7 +317,7 @@ function createParserCore(ctx) {
346
317
  const finalize = () => {
347
318
  if (lastContentEnd && seg) updatePositionsToEnd(lastContentEnd);
348
319
  dropTrailingEmptyFieldIfPresent();
349
- if (seg && !segmentHasContent) root.children.pop();
320
+ if (seg && !segmentHasContent && !seg.name) root.children.pop();
350
321
  if (root.position && documentEnd) root.position.end = documentEnd;
351
322
  else if (!root.position) root.position = {
352
323
  end: {
@@ -380,8 +351,8 @@ function createParserCore(ctx) {
380
351
  root
381
352
  };
382
353
  }
383
- function parseHL7v2FromIterator(tokens, ctx) {
384
- const core = createParserCore(ctx);
354
+ function parseHL7v2FromIterator(tokens) {
355
+ const core = createParserCore();
385
356
  for (const tok of tokens) core.processToken(tok);
386
357
  return core.finalize();
387
358
  }
@@ -560,13 +531,12 @@ function parseHL7v2(input, opts = {}, settings) {
560
531
  ...DEFAULT_DELIMITERS,
561
532
  ...settings?.delimiters ?? {}
562
533
  },
563
- emptyMode: settings?.experimental?.emptyMode,
564
534
  input
565
535
  };
566
536
  ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);
567
537
  const tokenizer = new HL7v2Tokenizer();
568
538
  tokenizer.reset(ctx);
569
- return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer), ctx);
539
+ return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer));
570
540
  }
571
541
  const hl7v2Parser = function hl7v2Parser(options = {}) {
572
542
  this.parser = (document) => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/preprocessor.ts","../src/processor.ts","../src/tokenizer.ts","../src/parser.ts"],"sourcesContent":["import { DEFAULT_DELIMITERS } from \"@glion/utils\";\n\nimport type { ParserContext, PreprocessorStep } from \"./types\";\n\n// PreprocessorStep is declared in `types.ts` to avoid circular imports\n\n/**\n * Step: strip UTF-8 BOM.\n */\nexport const stripBOM: PreprocessorStep = (ctx) => {\n if (ctx.input.codePointAt(0) === 0xfe_ff) {\n ctx.input = ctx.input.slice(1);\n }\n return ctx;\n};\n\n/**\n * Step: normalize newlines to the default delimiters.\n */\nexport const normalizeNewlines: PreprocessorStep = (ctx) => {\n ctx.input = ctx.input.replaceAll(/\\r?\\n/g, ctx.delimiters.segment);\n return ctx;\n};\n\n/**\n * Step: auto-detect delimiters from MSH.\n */\nexport const detectDelimiters: PreprocessorStep = (ctx) => {\n if (ctx.input.startsWith(\"MSH\")) {\n const field = ctx.input.charAt(3) || DEFAULT_DELIMITERS.field;\n const enc = ctx.input.slice(4, 8);\n const component = enc.charAt(0) || DEFAULT_DELIMITERS.component;\n const repetition = enc.charAt(1) || DEFAULT_DELIMITERS.repetition;\n const _escape = enc.charAt(2) || DEFAULT_DELIMITERS.escape;\n const subcomponent = enc.charAt(3) || DEFAULT_DELIMITERS.subcomponent;\n const { segment } = DEFAULT_DELIMITERS;\n\n ctx.delimiters = {\n component,\n escape: _escape,\n field,\n repetition,\n segment,\n subcomponent,\n };\n }\n return ctx;\n};\n\n/**\n * Default preprocessing pipeline.\n */\nexport const defaultPreprocessors: PreprocessorStep[] = [\n stripBOM,\n normalizeNewlines,\n detectDelimiters,\n];\n\n/**\n * Run the pipeline to initialize ParserContext.\n */\nexport function runPreprocessors(\n ctx: ParserContext,\n steps: PreprocessorStep[]\n): ParserContext {\n for (const step of steps) {\n // oxlint-disable-next-line no-param-reassign\n ctx = step(ctx);\n }\n\n return ctx;\n}\n","import type {\n Component,\n Field,\n FieldRepetition,\n Root,\n Segment,\n Subcomponent,\n} from \"@glion/ast\";\nimport { isEmptyNode } from \"@glion/utils\";\nimport type { Position } from \"unist\";\n\nimport type { ParserContext, Token } from \"./types\";\n\n/**\n * Check if a field has structural content beyond a single empty value.\n * A field with multiple repetitions, multiple components, or multiple\n * subcomponents has structural content even if all values are empty.\n * This is used to prevent dropping trailing fields like |^| or |&|\n * that have meaningful delimiter structure.\n */\nfunction hasStructuralContent(field: Field): boolean {\n if (field.children.length > 1) {\n return true;\n }\n const rep = field.children[0];\n if (rep && rep.children.length > 1) {\n return true;\n }\n const comp = rep?.children[0];\n return comp !== undefined && comp.children.length > 1;\n}\n\n// Helper to create an empty subcomponent at a given position\nfunction createSubcomponent(start: Position[\"start\"]): Subcomponent {\n return {\n position: { end: start, start },\n type: \"subcomponent\",\n value: \"\",\n };\n}\n\n// Helper to create a component (with or without children based on mode)\nfunction createComponent(\n start: Position[\"start\"],\n mode: \"legacy\" | \"empty\" | undefined\n): Component {\n return {\n children: mode === \"empty\" ? [] : [createSubcomponent(start)],\n position: { end: start, start },\n type: \"component\",\n };\n}\n\n// Helper to create a field repetition (with or without children based on mode)\nfunction createFieldRepetition(\n start: Position[\"start\"],\n mode: \"legacy\" | \"empty\" | undefined\n): FieldRepetition {\n return {\n children: mode === \"empty\" ? [] : [createComponent(start, mode)],\n position: { end: start, start },\n type: \"field-repetition\",\n };\n}\n\n// Helper to create a field (with or without children based on mode)\nfunction createField(\n start: Position[\"start\"],\n mode: \"legacy\" | \"empty\" | undefined\n): Field {\n return {\n children: mode === \"empty\" ? [] : [createFieldRepetition(start, mode)],\n position: { end: start, start },\n type: \"field\",\n };\n}\n\n// Shared core: process a single token into mutable parse state\nfunction createParserCore(ctx: ParserContext) {\n const mode = ctx.emptyMode;\n const root: Root = {\n children: [],\n type: \"root\",\n };\n\n let seg: Segment | null = null;\n let field: Field | null = null;\n let rep: FieldRepetition | null = null;\n let comp: Component | null = null;\n let currentSub: Subcomponent | null = null;\n let segmentHasContent = false;\n let lastContentEnd: Position[\"end\"] | null = null;\n let documentEnd: Position[\"end\"] | null = null; // Track document-level end position\n let expectingSegmentName = true; // Start expecting a segment name\n let justSetSegmentName = false; // Track if we just set the segment name\n let rootStartSet = false; // Track if we've set the root start position\n\n const resetState = () => {\n seg = null;\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n lastContentEnd = null;\n expectingSegmentName = true;\n };\n\n const openSegment = (name: string, position: Position) => {\n seg = {\n children: [],\n name,\n position: { end: position.end, start: position.start },\n type: \"segment\",\n };\n root.children.push(seg);\n // Reset field-level state\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n justSetSegmentName = true;\n expectingSegmentName = false;\n };\n\n /**\n * Ensure there is an active segment. If delimiter tokens arrive before\n * any segment name, lazily open a segment with an empty name.\n */\n const ensureSegment = (start: Position[\"start\"]) => {\n if (!seg) {\n openSegment(\"\", {\n start,\n end: start,\n });\n // Reset justSetSegmentName so the first FIELD_DELIM isn't skipped\n // (there's no real segment name to separate from fields)\n justSetSegmentName = false;\n }\n };\n\n const openField = (start: Position[\"start\"]) => {\n ensureSegment(start);\n field = createField(start, mode);\n seg!.children.push(field);\n if (mode === \"empty\") {\n rep = null;\n comp = null;\n currentSub = null;\n } else {\n rep = field.children[0] ?? null;\n comp = rep?.children[0] ?? null;\n currentSub = comp?.children[0] ?? null;\n }\n segmentHasContent = true;\n };\n\n const openRepetition = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n // In empty-array mode, openField creates empty field\n // We need to add an empty first repetition before the delimiter\n if (mode === \"empty\") {\n const emptyRep = createFieldRepetition(start, mode);\n field!.children.push(emptyRep);\n }\n // Fall through to create the repetition after the delimiter\n }\n // In empty-array mode, if field has no children yet, add empty first rep\n if (mode === \"empty\" && field!.children.length === 0) {\n const emptyRep = createFieldRepetition(start, mode);\n field!.children.push(emptyRep);\n }\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n if (mode === \"empty\") {\n comp = null;\n currentSub = null;\n } else {\n comp = rep.children[0] ?? null;\n currentSub = comp?.children[0] ?? null;\n }\n segmentHasContent = true;\n };\n\n // Ensure there's a component to add subcomponents to (for SUBCOMP_DELIM)\n // Does NOT add empty sibling components\n const ensureComponent = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n }\n if (!rep) {\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n }\n if (!comp) {\n comp = createComponent(start, mode);\n rep.children.push(comp);\n }\n segmentHasContent = true;\n };\n\n // Handle COMPONENT_DELIM - adds empty component before delimiter if needed\n const openComponent = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n // In empty-array mode, openField creates empty field\n // We need to create rep and add empty first component\n if (mode === \"empty\") {\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n const emptyComp = createComponent(start, mode);\n rep.children.push(emptyComp);\n }\n // Fall through to create the component after the delimiter\n }\n if (!rep) {\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n // In empty-array mode, need to add empty first component before the delimiter\n if (mode === \"empty\") {\n const emptyComp = createComponent(start, mode);\n rep.children.push(emptyComp);\n }\n } else if (mode === \"empty\" && rep.children.length === 0) {\n // If rep exists but has no children, add empty first component\n const emptyComp = createComponent(start, mode);\n rep.children.push(emptyComp);\n }\n comp = createComponent(start, mode);\n rep.children.push(comp);\n currentSub = mode === \"empty\" ? null : (comp.children[0] ?? null);\n segmentHasContent = true;\n };\n\n const ensureForText = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n if (mode !== \"empty\") {\n return; // Everything is already set up by openField in legacy mode\n }\n // In empty-array mode, openField created empty field, need to build structure\n }\n if (!rep) {\n if (mode !== \"empty\") {\n openRepetition(start);\n return; // Everything is already set up by openRepetition in legacy mode\n }\n // In empty-array mode, need to create repetition\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n }\n if (!comp) {\n if (mode !== \"empty\") {\n openComponent(start);\n return; // Everything is already set up by openComponent in legacy mode\n }\n // In empty-array mode, need to create component\n comp = createComponent(start, mode);\n rep!.children.push(comp);\n }\n if (!currentSub) {\n currentSub = createSubcomponent(start);\n comp!.children.push(currentSub);\n segmentHasContent = true;\n }\n };\n\n const updatePositionsToEnd = (endPos: Position[\"end\"]) => {\n if (currentSub?.position) {\n currentSub.position.end = endPos;\n }\n if (comp?.position) {\n comp.position.end = endPos;\n }\n if (rep?.position) {\n rep.position.end = endPos;\n }\n if (field?.position) {\n field.position.end = endPos;\n }\n if (seg?.position) {\n seg.position.end = endPos;\n }\n };\n\n const processToken = (tok: Token) => {\n switch (tok.type) {\n case \"SEGMENT_END\": {\n // Use the last content position instead of the segment delimiter position\n const endPos = lastContentEnd || tok.position.start;\n updatePositionsToEnd(endPos);\n dropTrailingEmptyFieldIfPresent();\n resetState();\n return;\n }\n case \"FIELD_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n // Skip the first field delimiter after setting segment name (it separates segment name from fields)\n if (justSetSegmentName) {\n justSetSegmentName = false;\n return;\n }\n // Leading field delimiter implies an empty first field\n if (!field) {\n // Open an empty first field (already has empty subcomponent from openField)\n openField(tok.position.start);\n }\n openField(tok.position.end);\n return;\n }\n case \"REPETITION_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n if (!field) {\n openField(tok.position.start);\n }\n openRepetition(tok.position.end);\n return;\n }\n case \"COMPONENT_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n openComponent(tok.position.end);\n return;\n }\n case \"SUBCOMP_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n // Ensure there's a component to add subcomponents to\n ensureComponent(tok.position.start);\n // In empty-array mode, if comp has no children yet, add empty first subcomponent\n if (mode === \"empty\" && comp!.children.length === 0) {\n const emptySub = createSubcomponent(tok.position.start);\n comp!.children.push(emptySub);\n }\n currentSub = createSubcomponent(tok.position.end);\n comp?.children.push(currentSub);\n segmentHasContent = true;\n return;\n }\n case \"TEXT\": {\n const val = tok.value ?? \"\";\n\n // Set root start position on first token\n if (!rootStartSet) {\n root.position = {\n end: tok.position.start,\n start: tok.position.start,\n };\n rootStartSet = true;\n }\n\n // If we're expecting a segment name, this TEXT is the segment name\n if (expectingSegmentName) {\n // `tok.position` is always available for TEXT tokens\n openSegment(val, tok.position);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n return;\n }\n\n // Otherwise, it's regular field content\n ensureForText(tok.position.start);\n if (currentSub) {\n currentSub.value += val;\n }\n updatePositionsToEnd(tok.position.end);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n segmentHasContent = true;\n return;\n }\n default: {\n throw new Error(`Unexpected token type: ${tok.type}`);\n }\n }\n };\n\n // Do not pre-open a segment; lazily open upon first non-SEGMENT_END token.\n\n const finalize = () => {\n // Handle input that ends without an explicit segment delimiter as above.\n if (lastContentEnd && seg) {\n updatePositionsToEnd(lastContentEnd);\n }\n dropTrailingEmptyFieldIfPresent();\n if (seg && !segmentHasContent) {\n // Drop trailing empty segment if no content was added\n root.children.pop();\n }\n\n // Set root end position once at finalization using document-level end\n if (root.position && documentEnd) {\n root.position.end = documentEnd;\n } else if (!root.position) {\n // Empty document - set default position\n root.position = {\n end: { column: 1, line: 1, offset: 0 },\n start: { column: 1, line: 1, offset: 0 },\n };\n }\n\n return root;\n };\n\n /**\n * Drop only the phantom trailing empty field created by a terminal field\n * delimiter (e.g. the last `|` in `PID|foo|`). Without this, every segment\n * ending with `|` would gain an extra empty field on each parse cycle.\n */\n function dropTrailingEmptyFieldIfPresent() {\n if (!seg || seg.children.length === 0) {\n return;\n }\n const lastChild = seg.children.at(-1);\n if (lastChild?.type !== \"field\") {\n return;\n }\n const lastField = lastChild as Field;\n // Both conditions are needed: a field like `|^|` is isEmpty (no leaf data)\n // but has structural content (component separators) and must be preserved.\n if (isEmptyNode(lastField) && !hasStructuralContent(lastField)) {\n seg.children.pop();\n }\n }\n\n return { finalize, processToken, root };\n}\n\n// Sync convenience wrapper over a sync Iterable token source\nexport function parseHL7v2FromIterator(\n tokens: Iterable<Token>,\n ctx: ParserContext\n): Root {\n const core = createParserCore(ctx);\n for (const tok of tokens) {\n core.processToken(tok);\n }\n return core.finalize();\n}\n","// src/tokenizer.ts\nimport type { Delimiters } from \"@glion/ast\";\nimport type { Position } from \"unist\";\n\nimport type { ParserContext, Token, Tokenizer, TokenType } from \"./types\";\n\nconst MSH_SEGMENT_START = 0;\nconst MSH_SEGMENT_END = 3;\nconst MSH_FIELD_SEPERATOR_START = 3;\nconst MSH_FIELD_SEPERATOR_END = 4;\nconst MSH_FIELD_DELIMITER_START = 4;\nconst MSH_FIELD_DELIMITER_END = 8;\n\nexport class HL7v2Tokenizer implements Tokenizer, Iterable<Token> {\n private input = \"\";\n private i = 0;\n private line = 1;\n private col = 1;\n private delims!: Delimiters;\n\n // Only-run-once MSH bootstrap at the start of the file\n private didMshBootstrap = false;\n private pendingBootstrap: null | Array<\n | { kind: \"TEXT\"; value: string; advance: number }\n | { kind: \"FIELD_DELIM\"; advance: number }\n > = null; // queue to emit TEXT('MSH'), FIELD_DELIM, TEXT('^~\\\\&')\n\n reset(ctx: ParserContext) {\n this.input = ctx.input;\n this.delims = ctx.delimiters;\n this.i = 0;\n this.line = 1;\n this.col = 1;\n this.didMshBootstrap = false;\n this.pendingBootstrap = null;\n\n // Prepare a one-time bootstrap if file starts with MSH\n if (this.input.startsWith(\"MSH\")) {\n // Precompute MSH and MSH.2; MSH.1 is the field delimiter char at index 3\n const msh = this._slice(MSH_SEGMENT_START, MSH_SEGMENT_END);\n const msh1 = this._slice(\n MSH_FIELD_SEPERATOR_START,\n MSH_FIELD_SEPERATOR_END\n ); // the field delimiter char at index 3\n const msh2 = this._slice(\n MSH_FIELD_DELIMITER_START,\n MSH_FIELD_DELIMITER_END\n ); // may be shorter than 4 if truncated\n this.pendingBootstrap = [\n { advance: msh.length, kind: \"TEXT\", value: msh },\n { advance: 0, kind: \"FIELD_DELIM\" },\n { advance: msh1.length, kind: \"TEXT\", value: msh1 },\n { advance: 0, kind: \"FIELD_DELIM\" },\n { advance: msh2.length, kind: \"TEXT\", value: msh2 },\n ];\n // NOTE: we have not advanced indices yet; we will as we emit tokens\n }\n }\n\n // oxlint-disable-next-line complexity\n next(): Token | null {\n const s = this.input;\n const n = s.length;\n if (this.i >= n && !this.pendingBootstrap?.length) {\n return null;\n }\n\n const start = { column: this.col, line: this.line, offset: this.i };\n\n // ---- One-time MSH bootstrap at file start ----\n if (!this.didMshBootstrap && this.pendingBootstrap) {\n const step = this.pendingBootstrap.shift();\n if (step) {\n if (step.kind === \"TEXT\") {\n const take = Math.min(step.advance, n - this.i);\n const out = step.value.slice(0, take);\n this._fastAdvance(out);\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"TEXT\", out, start);\n }\n // FIELD_DELIM: advance exactly one char (the delimiter) and emit a FIELD_DELIM token\n this._advance(Math.min(step.advance, n - this.i));\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n }\n\n // ---- Normal tokenization (delimiters + text) ----\n if (this.i >= n) {\n return null;\n }\n const ch = s.charAt(this.i);\n\n if (ch === this.delims.segment) {\n this._advance(1, true);\n return this._tok(\"SEGMENT_END\", undefined, start);\n }\n if (ch === this.delims.field) {\n this._advance(1);\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n if (ch === this.delims.repetition) {\n this._advance(1);\n return this._tok(\"REPETITION_DELIM\", undefined, start);\n }\n if (ch === this.delims.component) {\n this._advance(1);\n return this._tok(\"COMPONENT_DELIM\", undefined, start);\n }\n if (ch === this.delims.subcomponent) {\n this._advance(1);\n return this._tok(\"SUBCOMP_DELIM\", undefined, start);\n }\n\n // TEXT until next delimiter or end\n let j = this.i;\n const cmp = this.delims.component,\n fld = this.delims.field,\n rep = this.delims.repetition,\n seg = this.delims.segment,\n sub = this.delims.subcomponent;\n\n while (j < s.length) {\n const c = s.charAt(j);\n if (c === seg || c === fld || c === rep || c === cmp || c === sub) {\n break;\n }\n j += 1;\n }\n\n const val = s.slice(this.i, j);\n this._fastAdvance(val);\n return this._tok(\"TEXT\", val, start);\n }\n\n // ---- helpers ----\n private _slice(start: number, end: number): string {\n return this.input.slice(start, Math.min(end, this.input.length));\n }\n\n private _advance(n: number, isNewline = false) {\n this.i += n;\n if (isNewline) {\n this.line += 1;\n this.col = 1;\n } else {\n this.col += n;\n }\n }\n\n private _fastAdvance(chunk: string) {\n const parts = chunk.split(this.delims.segment);\n if (parts.length > 1) {\n this.line += parts.length - 1;\n this.col = (parts.at(-1)?.length ?? 0) + 1;\n } else {\n this.col += chunk.length;\n }\n this.i += chunk.length;\n }\n\n private _tok(\n type: TokenType,\n value: string | undefined,\n start: Position[\"start\"]\n ): Token {\n const end = { column: this.col, line: this.line, offset: this.i };\n return { position: { end, start }, type, value };\n }\n\n // Iterable protocol (sync)\n [Symbol.iterator](): Iterator<Token> {\n return {\n next: () => {\n const t = this.next();\n if (!t) {\n return { done: true, value: undefined as unknown as Token };\n }\n return { done: false, value: t };\n },\n };\n }\n}\n","import type { Root } from \"@glion/ast\";\nimport type { HL7v2Settings } from \"@glion/config\";\nimport { DEFAULT_DELIMITERS } from \"@glion/utils\";\nimport type { Plugin, Processor } from \"unified\";\n\nimport { defaultPreprocessors, runPreprocessors } from \"./preprocessor\";\nimport { parseHL7v2FromIterator } from \"./processor\";\nimport { HL7v2Tokenizer } from \"./tokenizer\";\nimport type { ParseOptions, ParserContext, Token, Tokenizer } from \"./types\";\n\nfunction* iterateTokenizerSync(t: Tokenizer): Iterable<Token> {\n for (let tok = t.next(); tok; tok = t.next()) {\n yield tok;\n }\n}\n\nexport function parseHL7v2(\n input: string,\n opts: ParseOptions = {},\n settings?: HL7v2Settings\n): Root {\n // Merge settings: defaults < provided settings\n // Note: MSH auto-detection (via detectDelimiters preprocessor) runs after this\n // and will override delimiters if the message starts with MSH\n\n let ctx: ParserContext = {\n delimiters: {\n ...DEFAULT_DELIMITERS,\n ...(settings?.delimiters ?? {}),\n },\n emptyMode: settings?.experimental?.emptyMode,\n input,\n };\n // Run preprocessing\n ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);\n // Run tokenizer\n const tokenizer = new HL7v2Tokenizer();\n // Reset tokenizer.\n tokenizer.reset(ctx);\n // Parse\n return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer), ctx);\n}\n\nconst hl7v2Parser: Plugin<[ParseOptions?], string, Root> = function hl7v2Parser(\n this: Processor,\n options = {}\n) {\n this.parser = (document: string) => {\n // Get settings from processor (populated by unified-args from config)\n const settings = this.data(\"settings\");\n\n return parseHL7v2(document, options, settings);\n };\n};\n\nexport default hl7v2Parser;\n"],"mappings":";;;;;AASA,MAAa,YAA8B,QAAQ;AACjD,KAAI,IAAI,MAAM,YAAY,EAAE,KAAK,MAC/B,KAAI,QAAQ,IAAI,MAAM,MAAM,EAAE;AAEhC,QAAO;;;;;AAMT,MAAa,qBAAuC,QAAQ;AAC1D,KAAI,QAAQ,IAAI,MAAM,WAAW,UAAU,IAAI,WAAW,QAAQ;AAClE,QAAO;;;;;AAMT,MAAa,oBAAsC,QAAQ;AACzD,KAAI,IAAI,MAAM,WAAW,MAAM,EAAE;EAC/B,MAAM,QAAQ,IAAI,MAAM,OAAO,EAAE,IAAI,mBAAmB;EACxD,MAAM,MAAM,IAAI,MAAM,MAAM,GAAG,EAAE;EACjC,MAAM,YAAY,IAAI,OAAO,EAAE,IAAI,mBAAmB;EACtD,MAAM,aAAa,IAAI,OAAO,EAAE,IAAI,mBAAmB;EACvD,MAAM,UAAU,IAAI,OAAO,EAAE,IAAI,mBAAmB;EACpD,MAAM,eAAe,IAAI,OAAO,EAAE,IAAI,mBAAmB;EACzD,MAAM,EAAE,YAAY;AAEpB,MAAI,aAAa;GACf;GACA,QAAQ;GACR;GACA;GACA;GACA;GACD;;AAEH,QAAO;;;;;AAMT,MAAa,uBAA2C;CACtD;CACA;CACA;CACD;;;;AAKD,SAAgB,iBACd,KACA,OACe;AACf,MAAK,MAAM,QAAQ,MAEjB,OAAM,KAAK,IAAI;AAGjB,QAAO;;;;;;;;;;;AClDT,SAAS,qBAAqB,OAAuB;AACnD,KAAI,MAAM,SAAS,SAAS,EAC1B,QAAO;CAET,MAAM,MAAM,MAAM,SAAS;AAC3B,KAAI,OAAO,IAAI,SAAS,SAAS,EAC/B,QAAO;CAET,MAAM,OAAO,KAAK,SAAS;AAC3B,QAAO,SAAS,KAAA,KAAa,KAAK,SAAS,SAAS;;AAItD,SAAS,mBAAmB,OAAwC;AAClE,QAAO;EACL,UAAU;GAAE,KAAK;GAAO;GAAO;EAC/B,MAAM;EACN,OAAO;EACR;;AAIH,SAAS,gBACP,OACA,MACW;AACX,QAAO;EACL,UAAU,SAAS,UAAU,EAAE,GAAG,CAAC,mBAAmB,MAAM,CAAC;EAC7D,UAAU;GAAE,KAAK;GAAO;GAAO;EAC/B,MAAM;EACP;;AAIH,SAAS,sBACP,OACA,MACiB;AACjB,QAAO;EACL,UAAU,SAAS,UAAU,EAAE,GAAG,CAAC,gBAAgB,OAAO,KAAK,CAAC;EAChE,UAAU;GAAE,KAAK;GAAO;GAAO;EAC/B,MAAM;EACP;;AAIH,SAAS,YACP,OACA,MACO;AACP,QAAO;EACL,UAAU,SAAS,UAAU,EAAE,GAAG,CAAC,sBAAsB,OAAO,KAAK,CAAC;EACtE,UAAU;GAAE,KAAK;GAAO;GAAO;EAC/B,MAAM;EACP;;AAIH,SAAS,iBAAiB,KAAoB;CAC5C,MAAM,OAAO,IAAI;CACjB,MAAM,OAAa;EACjB,UAAU,EAAE;EACZ,MAAM;EACP;CAED,IAAI,MAAsB;CAC1B,IAAI,QAAsB;CAC1B,IAAI,MAA8B;CAClC,IAAI,OAAyB;CAC7B,IAAI,aAAkC;CACtC,IAAI,oBAAoB;CACxB,IAAI,iBAAyC;CAC7C,IAAI,cAAsC;CAC1C,IAAI,uBAAuB;CAC3B,IAAI,qBAAqB;CACzB,IAAI,eAAe;CAEnB,MAAM,mBAAmB;AACvB,QAAM;AACN,UAAQ;AACR,QAAM;AACN,SAAO;AACP,eAAa;AACb,sBAAoB;AACpB,mBAAiB;AACjB,yBAAuB;;CAGzB,MAAM,eAAe,MAAc,aAAuB;AACxD,QAAM;GACJ,UAAU,EAAE;GACZ;GACA,UAAU;IAAE,KAAK,SAAS;IAAK,OAAO,SAAS;IAAO;GACtD,MAAM;GACP;AACD,OAAK,SAAS,KAAK,IAAI;AAEvB,UAAQ;AACR,QAAM;AACN,SAAO;AACP,eAAa;AACb,sBAAoB;AACpB,uBAAqB;AACrB,yBAAuB;;;;;;CAOzB,MAAM,iBAAiB,UAA6B;AAClD,MAAI,CAAC,KAAK;AACR,eAAY,IAAI;IACd;IACA,KAAK;IACN,CAAC;AAGF,wBAAqB;;;CAIzB,MAAM,aAAa,UAA6B;AAC9C,gBAAc,MAAM;AACpB,UAAQ,YAAY,OAAO,KAAK;AAChC,MAAK,SAAS,KAAK,MAAM;AACzB,MAAI,SAAS,SAAS;AACpB,SAAM;AACN,UAAO;AACP,gBAAa;SACR;AACL,SAAM,MAAM,SAAS,MAAM;AAC3B,UAAO,KAAK,SAAS,MAAM;AAC3B,gBAAa,MAAM,SAAS,MAAM;;AAEpC,sBAAoB;;CAGtB,MAAM,kBAAkB,UAA6B;AACnD,MAAI,CAAC,OAAO;AACV,aAAU,MAAM;AAGhB,OAAI,SAAS,SAAS;IACpB,MAAM,WAAW,sBAAsB,OAAO,KAAK;AACnD,UAAO,SAAS,KAAK,SAAS;;;AAKlC,MAAI,SAAS,WAAW,MAAO,SAAS,WAAW,GAAG;GACpD,MAAM,WAAW,sBAAsB,OAAO,KAAK;AACnD,SAAO,SAAS,KAAK,SAAS;;AAEhC,QAAM,sBAAsB,OAAO,KAAK;AACxC,QAAO,SAAS,KAAK,IAAI;AACzB,MAAI,SAAS,SAAS;AACpB,UAAO;AACP,gBAAa;SACR;AACL,UAAO,IAAI,SAAS,MAAM;AAC1B,gBAAa,MAAM,SAAS,MAAM;;AAEpC,sBAAoB;;CAKtB,MAAM,mBAAmB,UAA6B;AACpD,MAAI,CAAC,MACH,WAAU,MAAM;AAElB,MAAI,CAAC,KAAK;AACR,SAAM,sBAAsB,OAAO,KAAK;AACxC,SAAO,SAAS,KAAK,IAAI;;AAE3B,MAAI,CAAC,MAAM;AACT,UAAO,gBAAgB,OAAO,KAAK;AACnC,OAAI,SAAS,KAAK,KAAK;;AAEzB,sBAAoB;;CAItB,MAAM,iBAAiB,UAA6B;AAClD,MAAI,CAAC,OAAO;AACV,aAAU,MAAM;AAGhB,OAAI,SAAS,SAAS;AACpB,UAAM,sBAAsB,OAAO,KAAK;AACxC,UAAO,SAAS,KAAK,IAAI;IACzB,MAAM,YAAY,gBAAgB,OAAO,KAAK;AAC9C,QAAI,SAAS,KAAK,UAAU;;;AAIhC,MAAI,CAAC,KAAK;AACR,SAAM,sBAAsB,OAAO,KAAK;AACxC,SAAO,SAAS,KAAK,IAAI;AAEzB,OAAI,SAAS,SAAS;IACpB,MAAM,YAAY,gBAAgB,OAAO,KAAK;AAC9C,QAAI,SAAS,KAAK,UAAU;;aAErB,SAAS,WAAW,IAAI,SAAS,WAAW,GAAG;GAExD,MAAM,YAAY,gBAAgB,OAAO,KAAK;AAC9C,OAAI,SAAS,KAAK,UAAU;;AAE9B,SAAO,gBAAgB,OAAO,KAAK;AACnC,MAAI,SAAS,KAAK,KAAK;AACvB,eAAa,SAAS,UAAU,OAAQ,KAAK,SAAS,MAAM;AAC5D,sBAAoB;;CAGtB,MAAM,iBAAiB,UAA6B;AAClD,MAAI,CAAC,OAAO;AACV,aAAU,MAAM;AAChB,OAAI,SAAS,QACX;;AAIJ,MAAI,CAAC,KAAK;AACR,OAAI,SAAS,SAAS;AACpB,mBAAe,MAAM;AACrB;;AAGF,SAAM,sBAAsB,OAAO,KAAK;AACxC,SAAO,SAAS,KAAK,IAAI;;AAE3B,MAAI,CAAC,MAAM;AACT,OAAI,SAAS,SAAS;AACpB,kBAAc,MAAM;AACpB;;AAGF,UAAO,gBAAgB,OAAO,KAAK;AACnC,OAAK,SAAS,KAAK,KAAK;;AAE1B,MAAI,CAAC,YAAY;AACf,gBAAa,mBAAmB,MAAM;AACtC,QAAM,SAAS,KAAK,WAAW;AAC/B,uBAAoB;;;CAIxB,MAAM,wBAAwB,WAA4B;AACxD,MAAI,YAAY,SACd,YAAW,SAAS,MAAM;AAE5B,MAAI,MAAM,SACR,MAAK,SAAS,MAAM;AAEtB,MAAI,KAAK,SACP,KAAI,SAAS,MAAM;AAErB,MAAI,OAAO,SACT,OAAM,SAAS,MAAM;AAEvB,MAAI,KAAK,SACP,KAAI,SAAS,MAAM;;CAIvB,MAAM,gBAAgB,QAAe;AACnC,UAAQ,IAAI,MAAZ;GACE,KAAK;AAGH,yBADe,kBAAkB,IAAI,SAAS,MAClB;AAC5B,qCAAiC;AACjC,gBAAY;AACZ;GAEF,KAAK;AACH,qBAAiB,IAAI,SAAS;AAC9B,kBAAc,IAAI,SAAS;AAE3B,QAAI,oBAAoB;AACtB,0BAAqB;AACrB;;AAGF,QAAI,CAAC,MAEH,WAAU,IAAI,SAAS,MAAM;AAE/B,cAAU,IAAI,SAAS,IAAI;AAC3B;GAEF,KAAK;AACH,qBAAiB,IAAI,SAAS;AAC9B,kBAAc,IAAI,SAAS;AAC3B,QAAI,CAAC,MACH,WAAU,IAAI,SAAS,MAAM;AAE/B,mBAAe,IAAI,SAAS,IAAI;AAChC;GAEF,KAAK;AACH,qBAAiB,IAAI,SAAS;AAC9B,kBAAc,IAAI,SAAS;AAC3B,kBAAc,IAAI,SAAS,IAAI;AAC/B;GAEF,KAAK;AACH,qBAAiB,IAAI,SAAS;AAC9B,kBAAc,IAAI,SAAS;AAE3B,oBAAgB,IAAI,SAAS,MAAM;AAEnC,QAAI,SAAS,WAAW,KAAM,SAAS,WAAW,GAAG;KACnD,MAAM,WAAW,mBAAmB,IAAI,SAAS,MAAM;AACvD,UAAM,SAAS,KAAK,SAAS;;AAE/B,iBAAa,mBAAmB,IAAI,SAAS,IAAI;AACjD,UAAM,SAAS,KAAK,WAAW;AAC/B,wBAAoB;AACpB;GAEF,KAAK,QAAQ;IACX,MAAM,MAAM,IAAI,SAAS;AAGzB,QAAI,CAAC,cAAc;AACjB,UAAK,WAAW;MACd,KAAK,IAAI,SAAS;MAClB,OAAO,IAAI,SAAS;MACrB;AACD,oBAAe;;AAIjB,QAAI,sBAAsB;AAExB,iBAAY,KAAK,IAAI,SAAS;AAC9B,sBAAiB,IAAI,SAAS;AAC9B,mBAAc,IAAI,SAAS;AAC3B;;AAIF,kBAAc,IAAI,SAAS,MAAM;AACjC,QAAI,WACF,YAAW,SAAS;AAEtB,yBAAqB,IAAI,SAAS,IAAI;AACtC,qBAAiB,IAAI,SAAS;AAC9B,kBAAc,IAAI,SAAS;AAC3B,wBAAoB;AACpB;;GAEF,QACE,OAAM,IAAI,MAAM,0BAA0B,IAAI,OAAO;;;CAO3D,MAAM,iBAAiB;AAErB,MAAI,kBAAkB,IACpB,sBAAqB,eAAe;AAEtC,mCAAiC;AACjC,MAAI,OAAO,CAAC,kBAEV,MAAK,SAAS,KAAK;AAIrB,MAAI,KAAK,YAAY,YACnB,MAAK,SAAS,MAAM;WACX,CAAC,KAAK,SAEf,MAAK,WAAW;GACd,KAAK;IAAE,QAAQ;IAAG,MAAM;IAAG,QAAQ;IAAG;GACtC,OAAO;IAAE,QAAQ;IAAG,MAAM;IAAG,QAAQ;IAAG;GACzC;AAGH,SAAO;;;;;;;CAQT,SAAS,kCAAkC;AACzC,MAAI,CAAC,OAAO,IAAI,SAAS,WAAW,EAClC;EAEF,MAAM,YAAY,IAAI,SAAS,GAAG,GAAG;AACrC,MAAI,WAAW,SAAS,QACtB;EAEF,MAAM,YAAY;AAGlB,MAAI,YAAY,UAAU,IAAI,CAAC,qBAAqB,UAAU,CAC5D,KAAI,SAAS,KAAK;;AAItB,QAAO;EAAE;EAAU;EAAc;EAAM;;AAIzC,SAAgB,uBACd,QACA,KACM;CACN,MAAM,OAAO,iBAAiB,IAAI;AAClC,MAAK,MAAM,OAAO,OAChB,MAAK,aAAa,IAAI;AAExB,QAAO,KAAK,UAAU;;;;ACnbxB,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AACxB,MAAM,4BAA4B;AAClC,MAAM,0BAA0B;AAChC,MAAM,4BAA4B;AAClC,MAAM,0BAA0B;AAEhC,IAAa,iBAAb,MAAkE;CAChE,QAAgB;CAChB,IAAY;CACZ,OAAe;CACf,MAAc;CACd;CAGA,kBAA0B;CAC1B,mBAGI;CAEJ,MAAM,KAAoB;AACxB,OAAK,QAAQ,IAAI;AACjB,OAAK,SAAS,IAAI;AAClB,OAAK,IAAI;AACT,OAAK,OAAO;AACZ,OAAK,MAAM;AACX,OAAK,kBAAkB;AACvB,OAAK,mBAAmB;AAGxB,MAAI,KAAK,MAAM,WAAW,MAAM,EAAE;GAEhC,MAAM,MAAM,KAAK,OAAO,mBAAmB,gBAAgB;GAC3D,MAAM,OAAO,KAAK,OAChB,2BACA,wBACD;GACD,MAAM,OAAO,KAAK,OAChB,2BACA,wBACD;AACD,QAAK,mBAAmB;IACtB;KAAE,SAAS,IAAI;KAAQ,MAAM;KAAQ,OAAO;KAAK;IACjD;KAAE,SAAS;KAAG,MAAM;KAAe;IACnC;KAAE,SAAS,KAAK;KAAQ,MAAM;KAAQ,OAAO;KAAM;IACnD;KAAE,SAAS;KAAG,MAAM;KAAe;IACnC;KAAE,SAAS,KAAK;KAAQ,MAAM;KAAQ,OAAO;KAAM;IACpD;;;CAML,OAAqB;EACnB,MAAM,IAAI,KAAK;EACf,MAAM,IAAI,EAAE;AACZ,MAAI,KAAK,KAAK,KAAK,CAAC,KAAK,kBAAkB,OACzC,QAAO;EAGT,MAAM,QAAQ;GAAE,QAAQ,KAAK;GAAK,MAAM,KAAK;GAAM,QAAQ,KAAK;GAAG;AAGnE,MAAI,CAAC,KAAK,mBAAmB,KAAK,kBAAkB;GAClD,MAAM,OAAO,KAAK,iBAAiB,OAAO;AAC1C,OAAI,MAAM;AACR,QAAI,KAAK,SAAS,QAAQ;KACxB,MAAM,OAAO,KAAK,IAAI,KAAK,SAAS,IAAI,KAAK,EAAE;KAC/C,MAAM,MAAM,KAAK,MAAM,MAAM,GAAG,KAAK;AACrC,UAAK,aAAa,IAAI;AACtB,SAAI,KAAK,iBAAiB,WAAW,GAAG;AACtC,WAAK,mBAAmB;AACxB,WAAK,kBAAkB;;AAEzB,YAAO,KAAK,KAAK,QAAQ,KAAK,MAAM;;AAGtC,SAAK,SAAS,KAAK,IAAI,KAAK,SAAS,IAAI,KAAK,EAAE,CAAC;AACjD,QAAI,KAAK,iBAAiB,WAAW,GAAG;AACtC,UAAK,mBAAmB;AACxB,UAAK,kBAAkB;;AAEzB,WAAO,KAAK,KAAK,eAAe,KAAA,GAAW,MAAM;;;AAKrD,MAAI,KAAK,KAAK,EACZ,QAAO;EAET,MAAM,KAAK,EAAE,OAAO,KAAK,EAAE;AAE3B,MAAI,OAAO,KAAK,OAAO,SAAS;AAC9B,QAAK,SAAS,GAAG,KAAK;AACtB,UAAO,KAAK,KAAK,eAAe,KAAA,GAAW,MAAM;;AAEnD,MAAI,OAAO,KAAK,OAAO,OAAO;AAC5B,QAAK,SAAS,EAAE;AAChB,UAAO,KAAK,KAAK,eAAe,KAAA,GAAW,MAAM;;AAEnD,MAAI,OAAO,KAAK,OAAO,YAAY;AACjC,QAAK,SAAS,EAAE;AAChB,UAAO,KAAK,KAAK,oBAAoB,KAAA,GAAW,MAAM;;AAExD,MAAI,OAAO,KAAK,OAAO,WAAW;AAChC,QAAK,SAAS,EAAE;AAChB,UAAO,KAAK,KAAK,mBAAmB,KAAA,GAAW,MAAM;;AAEvD,MAAI,OAAO,KAAK,OAAO,cAAc;AACnC,QAAK,SAAS,EAAE;AAChB,UAAO,KAAK,KAAK,iBAAiB,KAAA,GAAW,MAAM;;EAIrD,IAAI,IAAI,KAAK;EACb,MAAM,MAAM,KAAK,OAAO,WACtB,MAAM,KAAK,OAAO,OAClB,MAAM,KAAK,OAAO,YAClB,MAAM,KAAK,OAAO,SAClB,MAAM,KAAK,OAAO;AAEpB,SAAO,IAAI,EAAE,QAAQ;GACnB,MAAM,IAAI,EAAE,OAAO,EAAE;AACrB,OAAI,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,MAAM,IAC5D;AAEF,QAAK;;EAGP,MAAM,MAAM,EAAE,MAAM,KAAK,GAAG,EAAE;AAC9B,OAAK,aAAa,IAAI;AACtB,SAAO,KAAK,KAAK,QAAQ,KAAK,MAAM;;CAItC,OAAe,OAAe,KAAqB;AACjD,SAAO,KAAK,MAAM,MAAM,OAAO,KAAK,IAAI,KAAK,KAAK,MAAM,OAAO,CAAC;;CAGlE,SAAiB,GAAW,YAAY,OAAO;AAC7C,OAAK,KAAK;AACV,MAAI,WAAW;AACb,QAAK,QAAQ;AACb,QAAK,MAAM;QAEX,MAAK,OAAO;;CAIhB,aAAqB,OAAe;EAClC,MAAM,QAAQ,MAAM,MAAM,KAAK,OAAO,QAAQ;AAC9C,MAAI,MAAM,SAAS,GAAG;AACpB,QAAK,QAAQ,MAAM,SAAS;AAC5B,QAAK,OAAO,MAAM,GAAG,GAAG,EAAE,UAAU,KAAK;QAEzC,MAAK,OAAO,MAAM;AAEpB,OAAK,KAAK,MAAM;;CAGlB,KACE,MACA,OACA,OACO;AAEP,SAAO;GAAE,UAAU;IAAE,KADT;KAAE,QAAQ,KAAK;KAAK,MAAM,KAAK;KAAM,QAAQ,KAAK;KAAG;IACvC;IAAO;GAAE;GAAM;GAAO;;CAIlD,CAAC,OAAO,YAA6B;AACnC,SAAO,EACL,YAAY;GACV,MAAM,IAAI,KAAK,MAAM;AACrB,OAAI,CAAC,EACH,QAAO;IAAE,MAAM;IAAM,OAAO,KAAA;IAA+B;AAE7D,UAAO;IAAE,MAAM;IAAO,OAAO;IAAG;KAEnC;;;;;AChLL,UAAU,qBAAqB,GAA+B;AAC5D,MAAK,IAAI,MAAM,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,CAC1C,OAAM;;AAIV,SAAgB,WACd,OACA,OAAqB,EAAE,EACvB,UACM;CAKN,IAAI,MAAqB;EACvB,YAAY;GACV,GAAG;GACH,GAAI,UAAU,cAAc,EAAE;GAC/B;EACD,WAAW,UAAU,cAAc;EACnC;EACD;AAED,OAAM,iBAAiB,KAAK,KAAK,cAAc,qBAAqB;CAEpE,MAAM,YAAY,IAAI,gBAAgB;AAEtC,WAAU,MAAM,IAAI;AAEpB,QAAO,uBAAuB,qBAAqB,UAAU,EAAE,IAAI;;AAGrE,MAAM,cAAqD,SAAS,YAElE,UAAU,EAAE,EACZ;AACA,MAAK,UAAU,aAAqB;AAIlC,SAAO,WAAW,UAAU,SAFX,KAAK,KAAK,WAAW,CAEQ"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/preprocessor.ts","../src/processor.ts","../src/tokenizer.ts","../src/parser.ts"],"sourcesContent":["import { DEFAULT_DELIMITERS } from \"@glion/utils\";\n\nimport type { ParserContext, PreprocessorStep } from \"./types\";\n\n// PreprocessorStep is declared in `types.ts` to avoid circular imports\n\n/**\n * Step: strip UTF-8 BOM.\n */\nexport const stripBOM: PreprocessorStep = (ctx) => {\n if (ctx.input.codePointAt(0) === 0xfe_ff) {\n ctx.input = ctx.input.slice(1);\n }\n return ctx;\n};\n\n/**\n * Step: normalize newlines to the default delimiters.\n */\nexport const normalizeNewlines: PreprocessorStep = (ctx) => {\n ctx.input = ctx.input.replaceAll(/\\r?\\n/g, ctx.delimiters.segment);\n return ctx;\n};\n\n/**\n * Step: auto-detect delimiters from MSH.\n */\nexport const detectDelimiters: PreprocessorStep = (ctx) => {\n if (ctx.input.startsWith(\"MSH\")) {\n const field = ctx.input.charAt(3) || DEFAULT_DELIMITERS.field;\n const enc = ctx.input.slice(4, 8);\n const component = enc.charAt(0) || DEFAULT_DELIMITERS.component;\n const repetition = enc.charAt(1) || DEFAULT_DELIMITERS.repetition;\n const _escape = enc.charAt(2) || DEFAULT_DELIMITERS.escape;\n const subcomponent = enc.charAt(3) || DEFAULT_DELIMITERS.subcomponent;\n const { segment } = DEFAULT_DELIMITERS;\n\n ctx.delimiters = {\n component,\n escape: _escape,\n field,\n repetition,\n segment,\n subcomponent,\n };\n }\n return ctx;\n};\n\n/**\n * Default preprocessing pipeline.\n */\nexport const defaultPreprocessors: PreprocessorStep[] = [\n stripBOM,\n normalizeNewlines,\n detectDelimiters,\n];\n\n/**\n * Run the pipeline to initialize ParserContext.\n */\nexport function runPreprocessors(\n ctx: ParserContext,\n steps: PreprocessorStep[]\n): ParserContext {\n for (const step of steps) {\n // oxlint-disable-next-line no-param-reassign\n ctx = step(ctx);\n }\n\n return ctx;\n}\n","import type {\n Component,\n Field,\n FieldRepetition,\n Root,\n Segment,\n Subcomponent,\n} from \"@glion/ast\";\nimport { isEmptyNode } from \"@glion/utils\";\nimport type { Position } from \"unist\";\n\nimport type { Token } from \"./types\";\n\n/**\n * Check if a field has structural content beyond a single empty value.\n * A field with multiple repetitions, multiple components, or multiple\n * subcomponents has structural content even if all values are empty.\n * This is used to prevent dropping trailing fields like |^| or |&|\n * that have meaningful delimiter structure.\n */\nfunction hasStructuralContent(field: Field): boolean {\n if (field.children.length > 1) {\n return true;\n }\n const rep = field.children[0];\n if (rep && rep.children.length > 1) {\n return true;\n }\n const comp = rep?.children[0];\n return comp !== undefined && comp.children.length > 1;\n}\n\nfunction createSubcomponent(start: Position[\"start\"]): Subcomponent {\n return {\n position: { end: start, start },\n type: \"subcomponent\",\n value: \"\",\n };\n}\n\nfunction createComponent(start: Position[\"start\"]): Component {\n return {\n children: [],\n position: { end: start, start },\n type: \"component\",\n };\n}\n\nfunction createFieldRepetition(start: Position[\"start\"]): FieldRepetition {\n return {\n children: [],\n position: { end: start, start },\n type: \"field-repetition\",\n };\n}\n\nfunction createField(start: Position[\"start\"]): Field {\n return {\n children: [],\n position: { end: start, start },\n type: \"field\",\n };\n}\n\n// Shared core: process a single token into mutable parse state\nfunction createParserCore() {\n const root: Root = {\n children: [],\n type: \"root\",\n };\n\n let seg: Segment | null = null;\n let field: Field | null = null;\n let rep: FieldRepetition | null = null;\n let comp: Component | null = null;\n let currentSub: Subcomponent | null = null;\n let segmentHasContent = false;\n let lastContentEnd: Position[\"end\"] | null = null;\n let documentEnd: Position[\"end\"] | null = null; // Track document-level end position\n let expectingSegmentName = true; // Start expecting a segment name\n let justSetSegmentName = false; // Track if we just set the segment name\n let rootStartSet = false; // Track if we've set the root start position\n\n const resetState = () => {\n seg = null;\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n lastContentEnd = null;\n expectingSegmentName = true;\n };\n\n const openSegment = (name: string, position: Position) => {\n seg = {\n children: [],\n name,\n position: { end: position.end, start: position.start },\n type: \"segment\",\n };\n root.children.push(seg);\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n justSetSegmentName = true;\n expectingSegmentName = false;\n };\n\n /**\n * Ensure there is an active segment. If delimiter tokens arrive before\n * any segment name, lazily open a segment with an empty name.\n */\n const ensureSegment = (start: Position[\"start\"]) => {\n if (!seg) {\n openSegment(\"\", { end: start, start });\n // Reset justSetSegmentName so the first FIELD_DELIM isn't skipped\n // (there's no real segment name to separate from fields)\n justSetSegmentName = false;\n }\n };\n\n const openField = (start: Position[\"start\"]) => {\n ensureSegment(start);\n field = createField(start);\n seg!.children.push(field);\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = true;\n };\n\n const openRepetition = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n // Add an empty first repetition before the delimiter\n const emptyRep = createFieldRepetition(start);\n field!.children.push(emptyRep);\n }\n // If field has no children yet, add empty first rep\n if (field!.children.length === 0) {\n const emptyRep = createFieldRepetition(start);\n field!.children.push(emptyRep);\n }\n rep = createFieldRepetition(start);\n field!.children.push(rep);\n comp = null;\n currentSub = null;\n segmentHasContent = true;\n };\n\n // Ensure there's a component to add subcomponents to (for SUBCOMP_DELIM)\n // Does NOT add empty sibling components.\n const ensureComponent = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n }\n if (!rep) {\n rep = createFieldRepetition(start);\n field!.children.push(rep);\n }\n if (!comp) {\n comp = createComponent(start);\n rep.children.push(comp);\n }\n segmentHasContent = true;\n };\n\n const openComponent = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n // Build out a rep + empty first component before the delimiter\n rep = createFieldRepetition(start);\n field!.children.push(rep);\n const emptyComp = createComponent(start);\n rep.children.push(emptyComp);\n }\n if (!rep) {\n rep = createFieldRepetition(start);\n field!.children.push(rep);\n const emptyComp = createComponent(start);\n rep.children.push(emptyComp);\n } else if (rep.children.length === 0) {\n const emptyComp = createComponent(start);\n rep.children.push(emptyComp);\n }\n comp = createComponent(start);\n rep.children.push(comp);\n currentSub = null;\n segmentHasContent = true;\n };\n\n const ensureForText = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n }\n if (!rep) {\n rep = createFieldRepetition(start);\n field!.children.push(rep);\n }\n if (!comp) {\n comp = createComponent(start);\n rep.children.push(comp);\n }\n if (!currentSub) {\n currentSub = createSubcomponent(start);\n comp.children.push(currentSub);\n segmentHasContent = true;\n }\n };\n\n const updatePositionsToEnd = (endPos: Position[\"end\"]) => {\n if (currentSub?.position) {\n currentSub.position.end = endPos;\n }\n if (comp?.position) {\n comp.position.end = endPos;\n }\n if (rep?.position) {\n rep.position.end = endPos;\n }\n if (field?.position) {\n field.position.end = endPos;\n }\n if (seg?.position) {\n seg.position.end = endPos;\n }\n };\n\n const processToken = (tok: Token) => {\n switch (tok.type) {\n case \"SEGMENT_END\": {\n const endPos = lastContentEnd || tok.position.start;\n updatePositionsToEnd(endPos);\n dropTrailingEmptyFieldIfPresent();\n resetState();\n return;\n }\n case \"FIELD_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n // Skip the first field delimiter after setting segment name\n if (justSetSegmentName) {\n justSetSegmentName = false;\n return;\n }\n // Leading field delimiter implies an empty first field\n if (!field) {\n openField(tok.position.start);\n }\n openField(tok.position.end);\n return;\n }\n case \"REPETITION_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n if (!field) {\n openField(tok.position.start);\n }\n openRepetition(tok.position.end);\n return;\n }\n case \"COMPONENT_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n openComponent(tok.position.end);\n return;\n }\n case \"SUBCOMP_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n ensureComponent(tok.position.start);\n // If comp has no children yet, add empty first subcomponent\n if (comp!.children.length === 0) {\n const emptySub = createSubcomponent(tok.position.start);\n comp!.children.push(emptySub);\n }\n currentSub = createSubcomponent(tok.position.end);\n comp?.children.push(currentSub);\n segmentHasContent = true;\n return;\n }\n case \"TEXT\": {\n const val = tok.value ?? \"\";\n\n // Set root start position on first token\n if (!rootStartSet) {\n root.position = {\n end: tok.position.start,\n start: tok.position.start,\n };\n rootStartSet = true;\n }\n\n // If we're expecting a segment name, this TEXT is the segment name\n if (expectingSegmentName) {\n openSegment(val, tok.position);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n return;\n }\n\n // Otherwise, it's regular field content\n ensureForText(tok.position.start);\n if (currentSub) {\n currentSub.value += val;\n }\n updatePositionsToEnd(tok.position.end);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n segmentHasContent = true;\n return;\n }\n default: {\n throw new Error(`Unexpected token type: ${tok.type}`);\n }\n }\n };\n\n // Do not pre-open a segment; lazily open upon first non-SEGMENT_END token.\n\n const finalize = () => {\n if (lastContentEnd && seg) {\n updatePositionsToEnd(lastContentEnd);\n }\n dropTrailingEmptyFieldIfPresent();\n if (seg && !segmentHasContent && !seg.name) {\n // Drop trailing empty segment only when it was lazily opened by\n // `ensureSegment` (no name, no content). A segment that arrived\n // with a name carries information — even if no fields followed —\n // and dropping it would break round-trip idempotency: the\n // serializer re-emits the name, the next parse would drop it\n // again, and any sibling segment immediately preceding it would\n // shift into the trailing position and get dropped on the\n // following parse.\n root.children.pop();\n }\n\n // Set root end position once at finalization using document-level end\n if (root.position && documentEnd) {\n root.position.end = documentEnd;\n } else if (!root.position) {\n root.position = {\n end: { column: 1, line: 1, offset: 0 },\n start: { column: 1, line: 1, offset: 0 },\n };\n }\n\n return root;\n };\n\n /**\n * Drop only the phantom trailing empty field created by a terminal field\n * delimiter (e.g. the last `|` in `PID|foo|`). Without this, every segment\n * ending with `|` would gain an extra empty field on each parse cycle.\n */\n function dropTrailingEmptyFieldIfPresent() {\n if (!seg || seg.children.length === 0) {\n return;\n }\n const lastChild = seg.children.at(-1);\n if (lastChild?.type !== \"field\") {\n return;\n }\n const lastField = lastChild as Field;\n // A field like `|^|` is isEmpty (no leaf data) but has structural content\n // (component separators) and must be preserved.\n if (isEmptyNode(lastField) && !hasStructuralContent(lastField)) {\n seg.children.pop();\n }\n }\n\n return { finalize, processToken, root };\n}\n\nexport function parseHL7v2FromIterator(tokens: Iterable<Token>): Root {\n const core = createParserCore();\n for (const tok of tokens) {\n core.processToken(tok);\n }\n return core.finalize();\n}\n","// src/tokenizer.ts\nimport type { Delimiters } from \"@glion/ast\";\nimport type { Position } from \"unist\";\n\nimport type { ParserContext, Token, Tokenizer, TokenType } from \"./types\";\n\nconst MSH_SEGMENT_START = 0;\nconst MSH_SEGMENT_END = 3;\nconst MSH_FIELD_SEPERATOR_START = 3;\nconst MSH_FIELD_SEPERATOR_END = 4;\nconst MSH_FIELD_DELIMITER_START = 4;\nconst MSH_FIELD_DELIMITER_END = 8;\n\nexport class HL7v2Tokenizer implements Tokenizer, Iterable<Token> {\n private input = \"\";\n private i = 0;\n private line = 1;\n private col = 1;\n private delims!: Delimiters;\n\n // Only-run-once MSH bootstrap at the start of the file\n private didMshBootstrap = false;\n private pendingBootstrap: null | Array<\n | { kind: \"TEXT\"; value: string; advance: number }\n | { kind: \"FIELD_DELIM\"; advance: number }\n > = null; // queue to emit TEXT('MSH'), FIELD_DELIM, TEXT('^~\\\\&')\n\n reset(ctx: ParserContext) {\n this.input = ctx.input;\n this.delims = ctx.delimiters;\n this.i = 0;\n this.line = 1;\n this.col = 1;\n this.didMshBootstrap = false;\n this.pendingBootstrap = null;\n\n // Prepare a one-time bootstrap if file starts with MSH\n if (this.input.startsWith(\"MSH\")) {\n // Precompute MSH and MSH.2; MSH.1 is the field delimiter char at index 3\n const msh = this._slice(MSH_SEGMENT_START, MSH_SEGMENT_END);\n const msh1 = this._slice(\n MSH_FIELD_SEPERATOR_START,\n MSH_FIELD_SEPERATOR_END\n ); // the field delimiter char at index 3\n const msh2 = this._slice(\n MSH_FIELD_DELIMITER_START,\n MSH_FIELD_DELIMITER_END\n ); // may be shorter than 4 if truncated\n this.pendingBootstrap = [\n { advance: msh.length, kind: \"TEXT\", value: msh },\n { advance: 0, kind: \"FIELD_DELIM\" },\n { advance: msh1.length, kind: \"TEXT\", value: msh1 },\n { advance: 0, kind: \"FIELD_DELIM\" },\n { advance: msh2.length, kind: \"TEXT\", value: msh2 },\n ];\n // NOTE: we have not advanced indices yet; we will as we emit tokens\n }\n }\n\n // oxlint-disable-next-line complexity/complexity -- tokenizer hot path: one branch per delimiter class, kept flat for speed\n next(): Token | null {\n const s = this.input;\n const n = s.length;\n if (this.i >= n && !this.pendingBootstrap?.length) {\n return null;\n }\n\n const start = { column: this.col, line: this.line, offset: this.i };\n\n // ---- One-time MSH bootstrap at file start ----\n if (!this.didMshBootstrap && this.pendingBootstrap) {\n const step = this.pendingBootstrap.shift();\n if (step) {\n if (step.kind === \"TEXT\") {\n const take = Math.min(step.advance, n - this.i);\n const out = step.value.slice(0, take);\n this._fastAdvance(out);\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"TEXT\", out, start);\n }\n // FIELD_DELIM: advance exactly one char (the delimiter) and emit a FIELD_DELIM token\n this._advance(Math.min(step.advance, n - this.i));\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n }\n\n // ---- Normal tokenization (delimiters + text) ----\n if (this.i >= n) {\n return null;\n }\n const ch = s.charAt(this.i);\n\n if (ch === this.delims.segment) {\n this._advance(1, true);\n return this._tok(\"SEGMENT_END\", undefined, start);\n }\n if (ch === this.delims.field) {\n this._advance(1);\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n if (ch === this.delims.repetition) {\n this._advance(1);\n return this._tok(\"REPETITION_DELIM\", undefined, start);\n }\n if (ch === this.delims.component) {\n this._advance(1);\n return this._tok(\"COMPONENT_DELIM\", undefined, start);\n }\n if (ch === this.delims.subcomponent) {\n this._advance(1);\n return this._tok(\"SUBCOMP_DELIM\", undefined, start);\n }\n\n // TEXT until next delimiter or end\n let j = this.i;\n const cmp = this.delims.component,\n fld = this.delims.field,\n rep = this.delims.repetition,\n seg = this.delims.segment,\n sub = this.delims.subcomponent;\n\n while (j < s.length) {\n const c = s.charAt(j);\n if (c === seg || c === fld || c === rep || c === cmp || c === sub) {\n break;\n }\n j += 1;\n }\n\n const val = s.slice(this.i, j);\n this._fastAdvance(val);\n return this._tok(\"TEXT\", val, start);\n }\n\n // ---- helpers ----\n private _slice(start: number, end: number): string {\n return this.input.slice(start, Math.min(end, this.input.length));\n }\n\n private _advance(n: number, isNewline = false) {\n this.i += n;\n if (isNewline) {\n this.line += 1;\n this.col = 1;\n } else {\n this.col += n;\n }\n }\n\n private _fastAdvance(chunk: string) {\n const parts = chunk.split(this.delims.segment);\n if (parts.length > 1) {\n this.line += parts.length - 1;\n this.col = (parts.at(-1)?.length ?? 0) + 1;\n } else {\n this.col += chunk.length;\n }\n this.i += chunk.length;\n }\n\n private _tok(\n type: TokenType,\n value: string | undefined,\n start: Position[\"start\"]\n ): Token {\n const end = { column: this.col, line: this.line, offset: this.i };\n return { position: { end, start }, type, value };\n }\n\n // Iterable protocol (sync)\n [Symbol.iterator](): Iterator<Token> {\n return {\n next: () => {\n const t = this.next();\n if (!t) {\n return { done: true, value: undefined as unknown as Token };\n }\n return { done: false, value: t };\n },\n };\n }\n}\n","import type { Root } from \"@glion/ast\";\nimport type { HL7v2Settings } from \"@glion/config\";\nimport { DEFAULT_DELIMITERS } from \"@glion/utils\";\nimport type { Plugin, Processor } from \"unified\";\n\nimport { defaultPreprocessors, runPreprocessors } from \"./preprocessor\";\nimport { parseHL7v2FromIterator } from \"./processor\";\nimport { HL7v2Tokenizer } from \"./tokenizer\";\nimport type { ParseOptions, ParserContext, Token, Tokenizer } from \"./types\";\n\nfunction* iterateTokenizerSync(t: Tokenizer): Iterable<Token> {\n for (let tok = t.next(); tok; tok = t.next()) {\n yield tok;\n }\n}\n\nexport function parseHL7v2(\n input: string,\n opts: ParseOptions = {},\n settings?: HL7v2Settings\n): Root {\n // Merge settings: defaults < provided settings\n // Note: MSH auto-detection (via detectDelimiters preprocessor) runs after this\n // and will override delimiters if the message starts with MSH\n\n let ctx: ParserContext = {\n delimiters: {\n ...DEFAULT_DELIMITERS,\n ...(settings?.delimiters ?? {}),\n },\n input,\n };\n // Run preprocessing\n ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);\n // Run tokenizer\n const tokenizer = new HL7v2Tokenizer();\n // Reset tokenizer.\n tokenizer.reset(ctx);\n // Parse\n return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer));\n}\n\nconst hl7v2Parser: Plugin<[ParseOptions?], string, Root> = function hl7v2Parser(\n this: Processor,\n options = {}\n) {\n this.parser = (document: string) => {\n // Get settings from processor (populated by unified-args from config)\n const settings = this.data(\"settings\");\n\n return parseHL7v2(document, options, settings);\n };\n};\n\nexport default hl7v2Parser;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,SAAS,CAAC;AAKjD,OAAO,KAAK,EAAE,YAAY,EAAmC,MAAM,SAAS,CAAC;AAQ7E,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,YAAiB,EACvB,QAAQ,CAAC,EAAE,aAAa,GACvB,IAAI,CAqBN;AAED,QAAA,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAUtD,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,SAAS,CAAC;AAKjD,OAAO,KAAK,EAAE,YAAY,EAAmC,MAAM,SAAS,CAAC;AAQ7E,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,YAAiB,EACvB,QAAQ,CAAC,EAAE,aAAa,GACvB,IAAI,CAoBN;AAED,QAAA,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAUtD,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,4 +1,4 @@
1
1
  import type { Root } from "@glion/ast";
2
- import type { ParserContext, Token } from "./types";
3
- export declare function parseHL7v2FromIterator(tokens: Iterable<Token>, ctx: ParserContext): Root;
2
+ import type { Token } from "./types";
3
+ export declare function parseHL7v2FromIterator(tokens: Iterable<Token>): Root;
4
4
  //# sourceMappingURL=processor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../src/processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,IAAI,EAGL,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAsapD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,EACvB,GAAG,EAAE,aAAa,GACjB,IAAI,CAMN"}
1
+ {"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../src/processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,IAAI,EAGL,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AA8WrC,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,IAAI,CAMpE"}
package/dist/types.d.ts CHANGED
@@ -1,5 +1,15 @@
1
- import type { Delimiters } from "@glion/ast";
1
+ import type { Delimiters, Root } from "@glion/ast";
2
+ import type { Processor } from "unified";
2
3
  import type { Position } from "unist";
4
+ /**
5
+ * A unified `Processor` that parses HL7v2 messages into `Root` trees.
6
+ *
7
+ * Only `ParseTree` is constrained — head and tail admit `undefined` so that
8
+ * any processor built with `unified().use(hl7v2Parser)` is assignable, from
9
+ * the bare parser (whose transform head/tail are `undefined`) through full
10
+ * transformer/compiler pipelines like `@glion/hl7v2`'s `parseHL7v2`.
11
+ */
12
+ export type Hl7v2Processor = Processor<Root, Root | undefined, Root | undefined>;
3
13
  export type PreprocessorStep = (ctx: ParserContext) => ParserContext;
4
14
  export interface ParseOptions {
5
15
  /**
@@ -11,7 +21,6 @@ export interface ParserContext {
11
21
  input: string;
12
22
  delimiters: Delimiters;
13
23
  metadata?: Record<string, unknown>;
14
- emptyMode?: "legacy" | "empty";
15
24
  }
16
25
  export interface Token {
17
26
  type: TokenType;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAItC,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,aAAa,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CAChC;AAGD,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAGD,MAAM,MAAM,SAAS,GACjB,aAAa,GACb,aAAa,GACb,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,GACf,MAAM,CAAC;AAEX,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC;CACtB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,CACpC,IAAI,EACJ,IAAI,GAAG,SAAS,EAChB,IAAI,GAAG,SAAS,CACjB,CAAC;AAIF,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,aAAa,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAGD,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAGD,MAAM,MAAM,SAAS,GACjB,aAAa,GACb,aAAa,GACb,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,GACf,MAAM,CAAC;AAEX,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC;CACtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glion/parser",
3
- "version": "0.15.3",
3
+ "version": "0.19.0",
4
4
  "description": "hl7v2 plugin to parse hl7v2 messages",
5
5
  "keywords": [
6
6
  "health",
@@ -33,32 +33,29 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
+ "@types/unist": "3.0.3",
36
37
  "unified": "11.0.5",
37
- "@glion/utils": "0.15.3"
38
+ "@glion/ast": "0.19.0",
39
+ "@glion/config": "0.19.0",
40
+ "@glion/utils": "0.19.0"
38
41
  },
39
42
  "devDependencies": {
40
- "@types/node": "^25.5.0",
41
- "@types/unist": "^3.0.3",
42
- "@vitest/coverage-v8": "4.1.2",
43
+ "@types/node": "25.6.0",
44
+ "@vitest/coverage-v8": "4.1.11",
43
45
  "publint": "0.3.18",
44
- "tsdown": "0.21.7",
45
- "typescript": "^5.9.3",
46
- "unist": "^0.0.1",
47
- "vitest": "4.1.2",
48
- "@glion/check-readme": "0.15.3",
49
- "@glion/ast": "0.15.3",
50
- "@glion/tsconfig": "0.15.3",
51
- "@glion/testing": "0.15.3",
52
- "@glion/config": "0.15.3"
46
+ "tsdown": "0.21.10",
47
+ "typescript": "6.0.3",
48
+ "vitest": "4.1.11",
49
+ "@glion/check-readme": "0.18.0",
50
+ "@glion/testing": "0.18.0",
51
+ "@glion/tsconfig": "0.18.0"
53
52
  },
54
53
  "engines": {
55
- "node": ">=18"
54
+ "node": ">=22"
56
55
  },
57
- "packageManager": "pnpm@10.14.0",
58
56
  "scripts": {
59
- "bench": "vitest bench --run",
60
57
  "build": "tsdown && tsc --emitDeclarationOnly",
61
- "check-types": "tsc --noEmit",
58
+ "check-types": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json",
62
59
  "check:readme": "glion-check-readme",
63
60
  "test": "vitest run",
64
61
  "test:coverage": "vitest run --coverage",