@cosyte/astm 0.0.15 → 0.0.18
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/CHANGELOG.md +290 -0
- package/README.md +118 -11
- package/dist/index.cjs +413 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +965 -58
- package/dist/index.d.ts +965 -58
- package/dist/index.mjs +407 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -74,18 +74,237 @@ declare const WARNING_CODES: {
|
|
|
74
74
|
* boundaries, silently, and this can happen to one record inside a run of these warnings); and a
|
|
75
75
|
* set differing in the repeat, component or escape role usually splits into fields normally,
|
|
76
76
|
* where a mis-split component can cost a test identity while the value survives, and where an
|
|
77
|
-
* `&X&` sequence whose body is
|
|
77
|
+
* `&X&` sequence whose body is an unrecognized character that is itself a delimiter in force is an
|
|
78
|
+
* opaque atom, so that delimiter does not split
|
|
78
79
|
* and every field after it shifts. The escape role's worst case has narrowed and not
|
|
79
80
|
* disappeared: an escape character heading no sequence is now read as a literal and reported
|
|
80
81
|
* under {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER} rather than merging the rest of the
|
|
81
|
-
* record,
|
|
82
|
-
* {@link WARNING_CODES.
|
|
82
|
+
* record, and a delimiter swallowed inside an `&X&` body now raises
|
|
83
|
+
* {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE} as well as the tolerable
|
|
84
|
+
* {@link WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE}, though it still splits the same way. The
|
|
85
|
+
* mirror of that case, where the leftmost alignment lets a delimiter split that a competing
|
|
86
|
+
* alignment would have held, gains a boundary rather than losing one, so the record splits into
|
|
87
|
+
* more fields than another reading gives and this code cannot see it either
|
|
88
|
+
* ({@link WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT} reports that). Treat this code as a
|
|
83
89
|
* report that one record definitely lost its fields, never as a sweep that would have fired if
|
|
84
90
|
* any had.
|
|
85
91
|
*/
|
|
86
92
|
readonly ASTM_RECORD_FIELDS_UNSEPARATED: "ASTM_RECORD_FIELDS_UNSEPARATED";
|
|
87
93
|
/** The header declared delimiters other than the canonical `H|\^&`: tolerated, noted. */
|
|
88
94
|
readonly ASTM_NONSTANDARD_DELIMITERS: "ASTM_NONSTANDARD_DELIMITERS";
|
|
95
|
+
/**
|
|
96
|
+
* A header declared **one character in two roles**, so the boundary between those two roles is
|
|
97
|
+
* not recoverable from the bytes. The declaration is still read and honored and no record is
|
|
98
|
+
* dropped: what is gone is a distinction the sender's own bytes no longer carry.
|
|
99
|
+
*
|
|
100
|
+
* The field separator is not part of this: a declaration naming it in another role is refused
|
|
101
|
+
* earlier (the `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal on the first header,
|
|
102
|
+
* {@link WARNING_CODES.ASTM_RECORD_UNREADABLE_REDECLARATION} on a later one). What this code
|
|
103
|
+
* covers is the three unordered pairs among the rest: **repeat/component, repeat/escape,
|
|
104
|
+
* component/escape**.
|
|
105
|
+
*
|
|
106
|
+
* Measured on `H|^^&` (repeat and component both `^`): the field `A^B^C^D` reads back as four
|
|
107
|
+
* repeats of one component each, so `components` holds only `A` and a two-repeats-of-two-components
|
|
108
|
+
* reading cannot be recovered. Measured on `H|\&&` (component and escape both `&`): `A&B` splits
|
|
109
|
+
* into two components while `A&F&B` reads as the single component `A|B`, so the same character
|
|
110
|
+
* means two different things depending on what follows it.
|
|
111
|
+
*
|
|
112
|
+
* **It is not tolerable**, and the reason is the pair it travels with: such a set is always
|
|
113
|
+
* non-canonical, so before this code existed the only warning on the stream was
|
|
114
|
+
* {@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS}, which a profile may tolerate. That made a
|
|
115
|
+
* structurally unreadable declaration indistinguishable, to a strict consumer, from an ordinary
|
|
116
|
+
* vendor set. Emit refuses the same sets outright (`ASTM_EMIT_INVALID_DELIMITERS`).
|
|
117
|
+
*
|
|
118
|
+
* One warning per header that **changes** the set in force into such a set, not one per colliding
|
|
119
|
+
* pair. A later header restating the colliding set already in force is a no-op and warns nothing,
|
|
120
|
+
* on the same rule as {@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS}: the set it names was
|
|
121
|
+
* already reported when it came into force.
|
|
122
|
+
*/
|
|
123
|
+
readonly ASTM_RECORD_DELIMITER_ROLE_COLLISION: "ASTM_RECORD_DELIMITER_ROLE_COLLISION";
|
|
124
|
+
/**
|
|
125
|
+
* An escape sequence whose body was **not** a recognized mnemonic held a character that is one of
|
|
126
|
+
* the three splitting delimiters in force, and the atom rule (an `&X&` triple is opaque) kept it
|
|
127
|
+
* out of the split, so a boundary the bytes carried never became one. The sequence is preserved
|
|
128
|
+
* verbatim in the value; nothing is dropped and nothing is re-split.
|
|
129
|
+
*
|
|
130
|
+
* Measured on the canonical set: `R|1|^^^687|28.6&|&U/L||||F` reads `value` = `28.6&|&U/L`, with
|
|
131
|
+
* **no units** and status `unspecified` rather than `final`.
|
|
132
|
+
*
|
|
133
|
+
* **This is the code that says a boundary was lost.** The same condition also raises
|
|
134
|
+
* {@link WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE}, which stays and stays tolerable: that code
|
|
135
|
+
* reports only that a body was not recognized, which is true of bodies that cost nothing. This one
|
|
136
|
+
* is the narrower, safety-critical half, and no profile may tolerate it.
|
|
137
|
+
*
|
|
138
|
+
* **What it does not do is repair anything.** The atom rule is unchanged (it is what keeps `&F&`
|
|
139
|
+
* one token under a set that names `F` as a delimiter), so the value is byte-identical to what it
|
|
140
|
+
* was before this code existed. It also cannot see the condition through a re-emit: emit rewrites
|
|
141
|
+
* the preserved sequence into recognized mnemonics, and the resulting stream says that value
|
|
142
|
+
* unambiguously, so a second-generation read is silent and correct about its own bytes. The place
|
|
143
|
+
* to catch this is the first read of the wire bytes, which is where it now refuses a strict parse.
|
|
144
|
+
*/
|
|
145
|
+
readonly ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE: "ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE";
|
|
146
|
+
/**
|
|
147
|
+
* Escape sequences are matched greedily and leftmost, so the escape character that **closed** an
|
|
148
|
+
* unrecognized sequence could not also **open** the next one. Where it could have, and where the
|
|
149
|
+
* body it would have held is the delimiter that was split on, the same bytes carry two alignments
|
|
150
|
+
* that disagree by one boundary: under the reading taken that delimiter ends a field, repeat or
|
|
151
|
+
* component, and under the other it sits inside an opaque atom and ends nothing. The leftmost
|
|
152
|
+
* reading is kept, every byte is preserved, and nothing is re-split.
|
|
153
|
+
*
|
|
154
|
+
* **This is the mirror of {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE}, and
|
|
155
|
+
* the direction is the reason it needs its own code.** That one reports a boundary the reading
|
|
156
|
+
* **lost**; this one reports a boundary the reading may have **gained**, which is the more
|
|
157
|
+
* dangerous direction, because a gained boundary hands back a value the sender's bytes do not
|
|
158
|
+
* unambiguously carry. Measured on the canonical set: `R|1|^^^687|28.6&Z&|&U/L||||F` reads
|
|
159
|
+
* `value` = `28.6&Z&` and `units` = `&U/L` under the leftmost alignment, and reads as a single
|
|
160
|
+
* unsplit field carrying both under the other.
|
|
161
|
+
*
|
|
162
|
+
* **Two exclusions, both deliberate, and the first is wider than its own argument.** Where the
|
|
163
|
+
* earlier sequence's body is a **recognized** mnemonic nothing is reported, because the reading
|
|
164
|
+
* taken interprets a construct (`&F&` is the sender escaping a field separator, which is what the
|
|
165
|
+
* mechanism is for) while the competing alignment's body is a delimiter character it usually
|
|
166
|
+
* cannot interpret at all, so this codec's own vocabulary prefers the reading taken. That is not
|
|
167
|
+
* the same as the reading taken being conformant, and where the declared set names a mnemonic
|
|
168
|
+
* letter as a splitting delimiter both alignments interpret one construct and neither is
|
|
169
|
+
* preferred, yet this stays silent. Both residues are measured and recorded rather than closed by
|
|
170
|
+
* widening the test. And where the escape
|
|
171
|
+
* character after the delimiter does not itself close a sequence there is no competing alignment
|
|
172
|
+
* at all, so an ordinary escaped value followed by an ordinary boundary is silent.
|
|
173
|
+
*
|
|
174
|
+
* **It is a report, not a repair, and not a round-trip guard.** The reading is unchanged: picking
|
|
175
|
+
* the other alignment would be a different guess with no more evidence behind it. Emit then
|
|
176
|
+
* rewrites the preserved sequences into recognized mnemonics, and those bytes carry the reading
|
|
177
|
+
* that was taken unambiguously, so a second-generation read is silent and is correct about its own
|
|
178
|
+
* bytes. The first read of the wire bytes is the only place the ambiguity exists to be caught.
|
|
179
|
+
*/
|
|
180
|
+
readonly ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT: "ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT";
|
|
181
|
+
/**
|
|
182
|
+
* Two escape alignments of the same bytes disagreed about a **field** boundary, the reading taken
|
|
183
|
+
* kept it, and the escape character that reading resumes on **heads no sequence at all**, so the
|
|
184
|
+
* boundary was bought with a byte this reading cannot read while the competing alignment is
|
|
185
|
+
* exactly the reading that can. Every field after that point sits one place further right than the
|
|
186
|
+
* competing alignment puts it.
|
|
187
|
+
*
|
|
188
|
+
* **The shift is the harm, and on a result record it reaches the status slot.** Measured on the
|
|
189
|
+
* canonical set, `R|1|^^^687|28.6&F&|&U/L||||F` reads **9** fields under the reading taken and
|
|
190
|
+
* **8** under the competing one, so the sender's trailing `F` lands in field 9 (the result status)
|
|
191
|
+
* under the first and in no field at all under the second. The parse hands back units `&U/L` and a
|
|
192
|
+
* status of **`final`**, and both are consequences of the alignment rather than values the sender
|
|
193
|
+
* placed in those slots. A downstream system reading `final` would act on a result the bytes do
|
|
194
|
+
* not say was finalised. Before this code the only warning on that stream was the **tolerable**
|
|
195
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}, so the widest gate-legal profile plus
|
|
196
|
+
* `{ strict: true }` accepted it.
|
|
197
|
+
*
|
|
198
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is identical, and
|
|
199
|
+
* the units and status read are the ones that were always read. Picking the other alignment would
|
|
200
|
+
* be a different guess with no more evidence behind it, and it would change values on a published
|
|
201
|
+
* package. What is new is that the shift is reported by a code no profile may tolerate.
|
|
202
|
+
*
|
|
203
|
+
* **It fires alongside {@link WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT}, not instead
|
|
204
|
+
* of it, and neither test is a widening of the other.** That code asks whether this codec's
|
|
205
|
+
* vocabulary prefers the reading taken *at* the contested position, and is silent where the
|
|
206
|
+
* earlier body is a recognized mnemonic. This one asks what the reading taken makes of the bytes
|
|
207
|
+
* *after* the boundary, and does not consult the earlier body at all.
|
|
208
|
+
*
|
|
209
|
+
* **Two deliberate bounds, both stated rather than left to be found.**
|
|
210
|
+
* - **Only the field role, and that bound is a CHOICE rather than a consequence.** A gained repeat
|
|
211
|
+
* or component boundary divides one field and reaches nothing outside it, so it moves no
|
|
212
|
+
* **field-indexed** slot: the units and the status stay where they were. **It does not follow
|
|
213
|
+
* that it moves no modeled slot at all, and writing that down would be false.** Components are
|
|
214
|
+
* modeled *inside* a field: a Universal Test ID's four components are the LOINC-candidate slot,
|
|
215
|
+
* the test name, the **coding scheme** and the **local code**, and a patient name's three are
|
|
216
|
+
* last, first and middle. A gained component boundary shifts those, so a local code can be read
|
|
217
|
+
* as a coding scheme and a given name as a middle name. Measured, and **where the earlier body is
|
|
218
|
+
* a recognized mnemonic, reported by nothing** (only the tolerable
|
|
219
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER} fires, so a gate-legal profile accepts it).
|
|
220
|
+
* An **unrecognized** earlier body raises
|
|
221
|
+
* {@link WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT} there and is refused, which is the
|
|
222
|
+
* same split this code's own exclusion takes. It is a separate, open condition rather than something this
|
|
223
|
+
* code covers, because wiring this sink to another split is a different criterion needing its own
|
|
224
|
+
* population measurement. The repeat role costs the **value**, which this code does not report
|
|
225
|
+
* either.
|
|
226
|
+
* - **The tail is weighed one construct deep.** Where the escape character the reading taken
|
|
227
|
+
* resumes on heads a sequence this codec *recognizes*, the reading taken interprets it and the
|
|
228
|
+
* competing alignment would leave it bare, so the bytes prefer the reading taken: under a set
|
|
229
|
+
* naming the field separator `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and
|
|
230
|
+
* escaped again, and refusing it would be an over-refusal of a well-formed stream. Where it
|
|
231
|
+
* heads a sequence whose body is *unrecognized*, the reading taken still consumes it while the
|
|
232
|
+
* competing alignment would leave **two** escape characters bare, so the preference is stronger
|
|
233
|
+
* again, and this stays silent there even though the field shift is real. That last case is the
|
|
234
|
+
* named residue: measured, not overlooked.
|
|
235
|
+
*
|
|
236
|
+
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized mnemonics,
|
|
237
|
+
* and those bytes carry the reading that was taken unambiguously, so a second-generation read is
|
|
238
|
+
* silent and is correct about its own bytes. A clean re-read is not evidence.
|
|
239
|
+
*/
|
|
240
|
+
readonly ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS: "ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS";
|
|
241
|
+
/**
|
|
242
|
+
* Two escape alignments of the same bytes disagreed about a **repeat** boundary, the reading
|
|
243
|
+
* taken kept it, and the escape character that reading resumes on **heads no sequence at all**.
|
|
244
|
+
* Same contested position and same tail test as
|
|
245
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS}, on a different delimiter role, and
|
|
246
|
+
* what it costs is a different thing, which is why it is a different code.
|
|
247
|
+
*
|
|
248
|
+
* **Nothing shifts, and that is not the same as nothing being lost.** No field-indexed slot
|
|
249
|
+
* moves: the units and the result status are read out of the same field numbers under either
|
|
250
|
+
* alignment. What this reports is that **the field is read as more repeats than the competing
|
|
251
|
+
* alignment gives it**, which is true wherever it fires. What that costs depends on **which**
|
|
252
|
+
* boundary was gained, and the two cases are stated separately rather than folded into one claim.
|
|
253
|
+
*
|
|
254
|
+
* **Where it is the FIRST boundary in the field, a modeled slot is lost**, because a field's
|
|
255
|
+
* modeled value and components are taken from its **first repeat alone**. Everything past the
|
|
256
|
+
* boundary stays on the wire and stays in `repeats`, and leaves every modeled slot:
|
|
257
|
+
*
|
|
258
|
+
* - **A value truncates.** Under `H|F^&`, `R|1|^^^687|28.6&S&F&U/L||||F` reads the value field as
|
|
259
|
+
* the two repeats `28.6^` and `&U/L`, so a consumer reads `28.6^` and the rest of the value is
|
|
260
|
+
* gone. The competing alignment reads one repeat carrying all of it.
|
|
261
|
+
* - **A modeled component list is deleted rather than shifted.** The same boundary inside a
|
|
262
|
+
* Universal Test ID leaves the components of the first repeat only: under `H|F^&`,
|
|
263
|
+
* `R|1|&F&F&687|28.6|U/L||||F` reads a UTID of one component holding a decoded field separator,
|
|
264
|
+
* with no coding scheme and no local code, where the bytes carry `687`. A patient name loses
|
|
265
|
+
* its given and middle names the same way. That is the half of this condition the shift report
|
|
266
|
+
* could not cover, because components are modeled **inside** a field.
|
|
267
|
+
*
|
|
268
|
+
* Before this code the only warnings on those streams were **tolerable** ones
|
|
269
|
+
* ({@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS} and, where the tail applies,
|
|
270
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}), so the widest gate-legal profile plus
|
|
271
|
+
* `{ strict: true }` accepted a truncated value and an emptied Universal Test ID.
|
|
272
|
+
*
|
|
273
|
+
* **Where it is a LATER boundary, no modeled slot moves and this still fires.** The first repeat
|
|
274
|
+
* is identical under both alignments, so the value, the components and every slot taken from them
|
|
275
|
+
* read the same either way (`R|1|^^^687|5.0F28.6&S&F&U/L||||F` under `H|F^&` reads a value of
|
|
276
|
+
* `5.0` under both), and what differs is the repeat structure after the first. It fires there
|
|
277
|
+
* deliberately: the boundary is still one the bytes do not force and a consumer reading `repeats`
|
|
278
|
+
* is still reading an alignment guess. Relative to the modeled slots that is **over-reporting,
|
|
279
|
+
* never under-reporting**. Narrowing it to the first boundary would change which streams a
|
|
280
|
+
* published package refuses and wants its own measurement, so the bound is written down instead.
|
|
281
|
+
*
|
|
282
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is identical,
|
|
283
|
+
* `repeats` still carries every one of them, and the value read is the value that was always
|
|
284
|
+
* read. Picking the other alignment would be a different guess with no more evidence behind it,
|
|
285
|
+
* on a published package.
|
|
286
|
+
*
|
|
287
|
+
* **Two further deliberate bounds, both stated rather than left to be found.**
|
|
288
|
+
* - **Only the repeat role.** The **component** role reaches a modeled slot too, and differently:
|
|
289
|
+
* there the components stay in the record and move one slot along, so a local code reads as a
|
|
290
|
+
* coding scheme and a given name as a middle name. That is measured, reported by nothing where
|
|
291
|
+
* the earlier body is a recognized mnemonic, and open. It is not covered here, because wiring a
|
|
292
|
+
* sink to another split is another criterion wanting its own population measurement.
|
|
293
|
+
* - **The tail is weighed one construct deep**, exactly as for the shift report. Where the escape
|
|
294
|
+
* character the reading resumes on heads a sequence this codec *recognizes*, the bytes prefer
|
|
295
|
+
* the reading taken and the repeats are the ones the sender wrote: under a set naming the
|
|
296
|
+
* repeat separator `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and escaped again,
|
|
297
|
+
* and refusing it would be an over-refusal of a well-formed stream. Where it heads a sequence
|
|
298
|
+
* whose body is *unrecognized*, the competing alignment would leave **two** escape characters
|
|
299
|
+
* bare, so the preference is stronger again, and this stays silent there even though the gained
|
|
300
|
+
* boundary is real. That last case is the named residue: measured, not overlooked.
|
|
301
|
+
*
|
|
302
|
+
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized
|
|
303
|
+
* mnemonics, and those bytes carry the reading that was taken unambiguously, so a
|
|
304
|
+
* second-generation read is silent and is correct about its own bytes. A clean re-read is not
|
|
305
|
+
* evidence.
|
|
306
|
+
*/
|
|
307
|
+
readonly ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD: "ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD";
|
|
89
308
|
/** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
|
|
90
309
|
readonly ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE";
|
|
91
310
|
/**
|
|
@@ -107,7 +326,8 @@ declare const WARNING_CODES: {
|
|
|
107
326
|
* *different* escape character in the same record may still head a real three-character sequence,
|
|
108
327
|
* and if that sequence's body happens to be a delimiter (`&|&` under the canonical set) the atom
|
|
109
328
|
* rule means that delimiter does not split. That case is reported separately, under
|
|
110
|
-
* {@link WARNING_CODES.
|
|
329
|
+
* {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE}, and it still costs a field
|
|
330
|
+
* boundary.
|
|
111
331
|
*/
|
|
112
332
|
readonly ASTM_UNPAIRED_ESCAPE_CHARACTER: "ASTM_UNPAIRED_ESCAPE_CHARACTER";
|
|
113
333
|
/**
|
|
@@ -173,7 +393,8 @@ declare const WARNING_CODES: {
|
|
|
173
393
|
*/
|
|
174
394
|
readonly ASTM_RECORD_DELIMITERS_REDECLARED: "ASTM_RECORD_DELIMITERS_REDECLARED";
|
|
175
395
|
/**
|
|
176
|
-
* A later `H` (header) record could not declare a usable delimiter set: it was
|
|
396
|
+
* A later `H` (header) record could not declare a usable delimiter set: it was not a header, it
|
|
397
|
+
* was too short, its delimiter definition held fewer than three characters, or the
|
|
177
398
|
* field separator it named also appeared among the other three, leaving the four roles
|
|
178
399
|
* indistinguishable. The delimiters **already in force are kept** and every record is still surfaced;
|
|
179
400
|
* a set is never guessed and no record is dropped.
|
|
@@ -283,6 +504,106 @@ declare function fieldsUnseparated(position: AstmPosition): AstmRecordWarning;
|
|
|
283
504
|
* ```
|
|
284
505
|
*/
|
|
285
506
|
declare function nonStandardDelimiters(position: AstmPosition): AstmRecordWarning;
|
|
507
|
+
/**
|
|
508
|
+
* Build an `ASTM_RECORD_DELIMITER_ROLE_COLLISION` warning. Emitted when a header
|
|
509
|
+
* declares one character in two of the repeat / component / escape roles. The
|
|
510
|
+
* declaration is honored and no record is dropped; the message names no character,
|
|
511
|
+
* because a delimiter is a byte off the wire.
|
|
512
|
+
*
|
|
513
|
+
* A profile may **not** tolerate this code: it reports a distinction the bytes no
|
|
514
|
+
* longer carry, and the only other warning such a set raises
|
|
515
|
+
* ({@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS}) is tolerable.
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```ts
|
|
519
|
+
* import { delimiterRoleCollision } from "@cosyte/astm";
|
|
520
|
+
* delimiterRoleCollision({ recordIndex: 0, recordType: "H" });
|
|
521
|
+
* ```
|
|
522
|
+
*/
|
|
523
|
+
declare function delimiterRoleCollision(position: AstmPosition): AstmRecordWarning;
|
|
524
|
+
/**
|
|
525
|
+
* Build an `ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE` warning. Emitted when an
|
|
526
|
+
* unrecognized escape body is itself a splitting delimiter in force, so the atom
|
|
527
|
+
* rule kept it out of the split. The value is preserved verbatim and is identical
|
|
528
|
+
* with the warning and without it; what the warning reports is the boundary that
|
|
529
|
+
* did not happen.
|
|
530
|
+
*
|
|
531
|
+
* A profile may **not** tolerate this code. It fires alongside
|
|
532
|
+
* {@link WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE}, which remains tolerable and
|
|
533
|
+
* reports the strictly weaker fact that a body was not recognized.
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```ts
|
|
537
|
+
* import { delimiterSwallowedByEscape } from "@cosyte/astm";
|
|
538
|
+
* delimiterSwallowedByEscape({ recordIndex: 4, recordType: "R", fieldIndex: 4 });
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
declare function delimiterSwallowedByEscape(position: AstmPosition): AstmRecordWarning;
|
|
542
|
+
/**
|
|
543
|
+
* Build an `ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT` warning. Emitted when the escape
|
|
544
|
+
* character that closed an unrecognized escape sequence could instead have opened one
|
|
545
|
+
* holding the delimiter that was split on, so the bytes carry two alignments that
|
|
546
|
+
* disagree about that boundary. The leftmost alignment is kept and every byte is
|
|
547
|
+
* preserved; what the warning reports is that the boundary is a choice.
|
|
548
|
+
*
|
|
549
|
+
* A profile may **not** tolerate this code. It fires alongside
|
|
550
|
+
* {@link WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE}, which remains tolerable and
|
|
551
|
+
* reports the strictly weaker fact that a body was not recognized.
|
|
552
|
+
*
|
|
553
|
+
* @example
|
|
554
|
+
* ```ts
|
|
555
|
+
* import { ambiguousEscapeAlignment } from "@cosyte/astm";
|
|
556
|
+
* ambiguousEscapeAlignment({ recordIndex: 4, recordType: "R", fieldIndex: 4 });
|
|
557
|
+
* ```
|
|
558
|
+
*/
|
|
559
|
+
declare function ambiguousEscapeAlignment(position: AstmPosition): AstmRecordWarning;
|
|
560
|
+
/**
|
|
561
|
+
* Build an `ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS` warning. Emitted when a competing escape
|
|
562
|
+
* alignment decided a **field** boundary and the escape character the reading taken resumes on
|
|
563
|
+
* heads no sequence of its own, so every field after that point sits one place further right than
|
|
564
|
+
* the competing alignment puts it. On a result record that reaches the units and the **result
|
|
565
|
+
* status**: a trailing status letter lands in field 9 under the reading taken and in no field at
|
|
566
|
+
* all under the competing one.
|
|
567
|
+
*
|
|
568
|
+
* A profile may **not** tolerate this code. It fires alongside
|
|
569
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}, which remains tolerable and reports the
|
|
570
|
+
* strictly weaker fact that one escape character was read as a literal, and alongside
|
|
571
|
+
* {@link WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT} where that also applies. The reading
|
|
572
|
+
* is unchanged: this reports the shift, it does not repair it.
|
|
573
|
+
*
|
|
574
|
+
* @example
|
|
575
|
+
* ```ts
|
|
576
|
+
* import { alignmentShiftedFields } from "@cosyte/astm";
|
|
577
|
+
* alignmentShiftedFields({ recordIndex: 2, recordType: "R", fieldIndex: 4 });
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
declare function alignmentShiftedFields(position: AstmPosition): AstmRecordWarning;
|
|
581
|
+
/**
|
|
582
|
+
* Build an `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD` warning. Emitted when a competing escape
|
|
583
|
+
* alignment decided a **repeat** boundary and the escape character the reading taken resumes on
|
|
584
|
+
* heads no sequence of its own, so the field is read as more repeats than the competing alignment
|
|
585
|
+
* gives it. No field-indexed slot moves, and that is the difference from
|
|
586
|
+
* {@link alignmentShiftedFields}. Where the gained boundary is the **first** in the field it still
|
|
587
|
+
* reaches a modeled slot, because a field's modeled value and components are taken from its first
|
|
588
|
+
* repeat alone: everything past the boundary stays in `repeats` and leaves every modeled slot, so a
|
|
589
|
+
* result value truncates and a Universal Test ID or a patient name loses the components that sat
|
|
590
|
+
* after it. At a **later** boundary nothing modeled moves and this still fires, which is
|
|
591
|
+
* over-reporting relative to those slots and never under-reporting; see
|
|
592
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD}.
|
|
593
|
+
*
|
|
594
|
+
* A profile may **not** tolerate this code. It fires alongside
|
|
595
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}, which remains tolerable and reports the
|
|
596
|
+
* strictly weaker fact that one escape character was read as a literal, and alongside
|
|
597
|
+
* {@link WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT} where that also applies. The reading
|
|
598
|
+
* is unchanged: this reports the gained boundary, it does not repair it.
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```ts
|
|
602
|
+
* import { alignmentTruncatedField } from "@cosyte/astm";
|
|
603
|
+
* alignmentTruncatedField({ recordIndex: 2, recordType: "R", fieldIndex: 4 });
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
declare function alignmentTruncatedField(position: AstmPosition): AstmRecordWarning;
|
|
286
607
|
/**
|
|
287
608
|
* Build an `ASTM_RECORD_DELIMITERS_REDECLARED` warning. Emitted when a later `H`
|
|
288
609
|
* record declares a delimiter set different from the one in force; the new set is
|
|
@@ -328,8 +649,10 @@ declare function unknownEscapeSequence(position: AstmPosition): AstmRecordWarnin
|
|
|
328
649
|
*
|
|
329
650
|
* **It is a statement about that one character, not about the record.** A
|
|
330
651
|
* different escape character in the same record may head a real three-character
|
|
331
|
-
* sequence, and if that sequence's body is
|
|
332
|
-
*
|
|
652
|
+
* sequence, and if that sequence's body is an unrecognized character that is itself a delimiter in
|
|
653
|
+
* force, that delimiter does not
|
|
654
|
+
* split, which is reported separately by
|
|
655
|
+
* {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE}.
|
|
333
656
|
*
|
|
334
657
|
* A profile **may** tolerate this code: the value it reports is byte-identical
|
|
335
658
|
* with the warning and without it, because reading the character as a literal is
|
|
@@ -528,13 +851,39 @@ interface Delimiters {
|
|
|
528
851
|
* ```
|
|
529
852
|
*/
|
|
530
853
|
declare const CANONICAL_DELIMITERS: Delimiters;
|
|
854
|
+
/**
|
|
855
|
+
* Why a header record could not declare a usable delimiter set. Four distinct
|
|
856
|
+
* conditions, kept distinct because a consumer is told which one it was and two of
|
|
857
|
+
* them describe records that are **not** short.
|
|
858
|
+
*
|
|
859
|
+
* - `not-a-header`: the record does not begin with an `H` type letter.
|
|
860
|
+
* - `record-too-short`: fewer than five characters, so `H` plus a field separator
|
|
861
|
+
* plus a three-character definition cannot fit.
|
|
862
|
+
* - `definition-truncated`: the delimiter-definition field runs to the next field
|
|
863
|
+
* separator (or to the end of the record) and holds fewer than three characters.
|
|
864
|
+
* **This is the one a full-length header reaches**, and it is what a declaration
|
|
865
|
+
* naming its own field separator among the other three produces, because that
|
|
866
|
+
* separator ends the definition where it appears.
|
|
867
|
+
* - `field-separator-reused`: the field separator is also the repeat, component or
|
|
868
|
+
* escape character.
|
|
869
|
+
*/
|
|
870
|
+
type DelimiterDeclarationFault = "not-a-header" | "record-too-short" | "definition-truncated" | "field-separator-reused";
|
|
871
|
+
/** The result of reading delimiters from a header record: either resolved or a named failure. */
|
|
872
|
+
type DelimiterReadResult = {
|
|
873
|
+
readonly ok: true;
|
|
874
|
+
readonly delimiters: Delimiters;
|
|
875
|
+
} | {
|
|
876
|
+
readonly ok: false;
|
|
877
|
+
readonly fault: DelimiterDeclarationFault;
|
|
878
|
+
};
|
|
531
879
|
/**
|
|
532
880
|
* Read the four delimiters from a header record's raw text (a single `H`
|
|
533
881
|
* record, its terminator already stripped).
|
|
534
882
|
*
|
|
535
|
-
* Returns `undefined` when the record cannot declare all four delimiters
|
|
536
|
-
*
|
|
537
|
-
*
|
|
883
|
+
* Returns `undefined` when the record cannot declare all four delimiters. The four
|
|
884
|
+
* ways that happens are named by {@link readDelimiterDeclaration}, which is this
|
|
885
|
+
* function with the reason kept; use it wherever the reason is shown to a consumer,
|
|
886
|
+
* because "too short" is false of some of them.
|
|
538
887
|
*
|
|
539
888
|
* What the caller does with that depends on **which** header it is. On the first
|
|
540
889
|
* header it is the `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal, because there is no
|
|
@@ -543,6 +892,12 @@ declare const CANONICAL_DELIMITERS: Delimiters;
|
|
|
543
892
|
* and an `ASTM_RECORD_UNREADABLE_REDECLARATION` warning is raised instead; a set is
|
|
544
893
|
* never guessed and no record is dropped.
|
|
545
894
|
*
|
|
895
|
+
* **A set two of whose other three roles share a character is resolved, not
|
|
896
|
+
* refused**, because the stream is still readable and refusing it would drop
|
|
897
|
+
* records the sender did send. What it costs is the boundary between those two
|
|
898
|
+
* roles, which the bytes no longer carry: see {@link hasCollidingRoles}, which
|
|
899
|
+
* the parse path calls to report it (`ASTM_RECORD_DELIMITER_ROLE_COLLISION`).
|
|
900
|
+
*
|
|
546
901
|
* This function does not throw: delimiter resolution and the escalation decision are
|
|
547
902
|
* kept separate so the reader stays pure and testable.
|
|
548
903
|
*
|
|
@@ -556,6 +911,68 @@ declare const CANONICAL_DELIMITERS: Delimiters;
|
|
|
556
911
|
* ```
|
|
557
912
|
*/
|
|
558
913
|
declare function readDelimiters(headerRecord: string): Delimiters | undefined;
|
|
914
|
+
/**
|
|
915
|
+
* The same read as {@link readDelimiters}, keeping **why** it failed.
|
|
916
|
+
*
|
|
917
|
+
* The reason is not cosmetic. A caller that reports every failure as "too short"
|
|
918
|
+
* says something false about `H||^&`, a full-length header whose definition field is
|
|
919
|
+
* empty because its own field separator ends it, and a consumer reading that
|
|
920
|
+
* diagnostic looks for the wrong thing. The two conditions are separate rules that
|
|
921
|
+
* happen to coincide today (see the fault list on
|
|
922
|
+
* {@link DelimiterDeclarationFault}), so they are named separately.
|
|
923
|
+
*
|
|
924
|
+
* **`field-separator-reused` is unreachable through this function today, and the
|
|
925
|
+
* check stays.** A field separator occurring in the definition positions sits at
|
|
926
|
+
* index 2, 3 or 4, so it ends the definition where it appears and the definition is
|
|
927
|
+
* under three characters: `definition-truncated` answers first, every time. The
|
|
928
|
+
* outcome is right either way (such a declaration does not resolve at all). The
|
|
929
|
+
* check is the statement of an invariant the length rule currently enforces on its
|
|
930
|
+
* behalf, and the two are not the same rule: a change to how the definition field is
|
|
931
|
+
* bounded would separate them again. Deleting it would leave the invariant
|
|
932
|
+
* unstated and the change silent.
|
|
933
|
+
*
|
|
934
|
+
* @param headerRecord - The raw `H` record text (no trailing CR/LF).
|
|
935
|
+
* @returns The resolved delimiters, or the named reason they could not be read.
|
|
936
|
+
* @example
|
|
937
|
+
* ```ts
|
|
938
|
+
* import { readDelimiterDeclaration } from "@cosyte/astm";
|
|
939
|
+
* readDelimiterDeclaration("H|\\^&").ok; // true
|
|
940
|
+
* readDelimiterDeclaration("H||^&"); // { ok: false, fault: "definition-truncated" }
|
|
941
|
+
* ```
|
|
942
|
+
*/
|
|
943
|
+
declare function readDelimiterDeclaration(headerRecord: string): DelimiterReadResult;
|
|
944
|
+
/**
|
|
945
|
+
* Whether a resolved set names one character in two roles, so the boundary
|
|
946
|
+
* between those two roles cannot be recovered from the bytes.
|
|
947
|
+
*
|
|
948
|
+
* {@link readDelimiters} already refuses a declaration whose **field** separator
|
|
949
|
+
* is one of the other three (it returns `undefined`, which is the
|
|
950
|
+
* `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal on the first header and the
|
|
951
|
+
* `ASTM_RECORD_UNREADABLE_REDECLARATION` warning on a later one), so what is left
|
|
952
|
+
* to test here is the three unordered pairs among the remaining roles:
|
|
953
|
+
* **repeat/component, repeat/escape, and component/escape**. Three pairs, named
|
|
954
|
+
* rather than counted, because the count is only meaningful with the list.
|
|
955
|
+
*
|
|
956
|
+
* Such a declaration is read and honored (nothing is guessed, no record is
|
|
957
|
+
* dropped) and the loss it causes is real and was previously silent. Measured on
|
|
958
|
+
* the canonical-looking `H|^^&`, where the repeat and component roles are both
|
|
959
|
+
* `^`: the field `A^B^C^D` reads back as **four repeats of one component each**,
|
|
960
|
+
* so a two-repeats-of-two-components reading is unrecoverable and `components`
|
|
961
|
+
* holds only `A`. On `H|\&&`, where the component and escape roles are both `&`,
|
|
962
|
+
* the same character splits (`A&B` reads as two components) or opens an atom
|
|
963
|
+
* (`A&F&B` reads as the single component `A|B`) depending only on what follows
|
|
964
|
+
* it. Emit refuses such a set outright (`ASTM_EMIT_INVALID_DELIMITERS`).
|
|
965
|
+
*
|
|
966
|
+
* @param d - A resolved delimiter set.
|
|
967
|
+
* @returns `true` iff two of the repeat / component / escape roles share a character.
|
|
968
|
+
* @example
|
|
969
|
+
* ```ts
|
|
970
|
+
* import { hasCollidingRoles, readDelimiters } from "@cosyte/astm";
|
|
971
|
+
* hasCollidingRoles(readDelimiters("H|\\^&")!); // false
|
|
972
|
+
* hasCollidingRoles(readDelimiters("H|^^&")!); // true (repeat === component)
|
|
973
|
+
* ```
|
|
974
|
+
*/
|
|
975
|
+
declare function hasCollidingRoles(d: Delimiters): boolean;
|
|
559
976
|
/**
|
|
560
977
|
* Whether a resolved delimiter set differs from the canonical `H|\^&`. A `true`
|
|
561
978
|
* result is a tolerated vendor quirk, surfaced as a value-free warning so a
|
|
@@ -2116,10 +2533,18 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
2116
2533
|
*
|
|
2117
2534
|
* **What is still not guaranteed.** Together the two checks guarantee that every
|
|
2118
2535
|
* record re-reads as its own *type*. They do not guarantee that every *field* of it
|
|
2119
|
-
* lands where it did: an escape sequence whose body is
|
|
2536
|
+
* lands where it did: an escape sequence whose body is an unrecognized character that is itself a
|
|
2537
|
+
* delimiter in force is read as
|
|
2120
2538
|
* one opaque atom, so that delimiter never becomes a boundary and the fields after it
|
|
2121
|
-
* shift. That is reported on the parse side
|
|
2122
|
-
*
|
|
2539
|
+
* shift. That is reported on the parse side, by `ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE`
|
|
2540
|
+
* (which no profile may tolerate) alongside the tolerable `ASTM_UNKNOWN_ESCAPE_SEQUENCE`, and is
|
|
2541
|
+
* not what these refusals cover. Nor is the mirror of it: sequences are matched leftmost, so one
|
|
2542
|
+
* can end where another could have begun and the delimiter between them splits where the competing
|
|
2543
|
+
* alignment would have held it, reported by `ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT`. **Neither
|
|
2544
|
+
* report survives this serializer**, by construction rather than by oversight: it rewrites the
|
|
2545
|
+
* preserved characters into recognized mnemonics, so the emitted stream carries the reading that
|
|
2546
|
+
* was taken with nothing ambiguous left in it, and a second-generation parse is silent and correct
|
|
2547
|
+
* about its own bytes. Catch both on the first read of the wire bytes.
|
|
2123
2548
|
*/
|
|
2124
2549
|
|
|
2125
2550
|
/**
|
|
@@ -2273,9 +2698,14 @@ declare function serializeAstmRecord(record: AstmRecord, d?: Delimiters): string
|
|
|
2273
2698
|
* therefore checked to start with the letter the record models, and one that does
|
|
2274
2699
|
* not is `ASTM_EMIT_TYPE_LETTER_COLLISION` rather than a stream that reads back as
|
|
2275
2700
|
* different records. What neither check promises is that every *field* lands where
|
|
2276
|
-
* it did: an escape sequence whose body is
|
|
2701
|
+
* it did: an escape sequence whose body is an unrecognized character that is itself a delimiter in
|
|
2702
|
+
* force is an opaque atom, so that
|
|
2277
2703
|
* delimiter never becomes a boundary, and the parse side reports that
|
|
2278
|
-
* (`
|
|
2704
|
+
* (`ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE`, plus the tolerable
|
|
2705
|
+
* `ASTM_UNKNOWN_ESCAPE_SEQUENCE`) rather than emit refusing it. The mirror case, where a sequence
|
|
2706
|
+
* ends where another could have begun so a delimiter splits that the competing alignment would
|
|
2707
|
+
* have held, is reported the same way (`ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT`). Emitting
|
|
2708
|
+
* normalizes both away rather than preserving them, so neither reaches a second generation.
|
|
2279
2709
|
*
|
|
2280
2710
|
* @param input - A parsed {@link AstmMessage} or a list of {@link AstmRecord}s.
|
|
2281
2711
|
* @param d - The delimiters to emit against; defaults to the canonical `H|\^&` set.
|
|
@@ -2896,6 +3326,27 @@ declare function classifyMessage(records: readonly AstmRecord[]): AstmMessageCla
|
|
|
2896
3326
|
* unrecognized escape sequence encountered, so the caller can warn.
|
|
2897
3327
|
* @param onUnpairedEscape - Called (with the 0-based field index) for each escape
|
|
2898
3328
|
* character that heads no escape sequence and was therefore read as a literal.
|
|
3329
|
+
* @param onSwallowedDelimiter - Called (with the 0-based field index) for each
|
|
3330
|
+
* unrecognized escape sequence whose body is a splitting delimiter in force, so
|
|
3331
|
+
* that delimiter never became a boundary.
|
|
3332
|
+
* @param onAmbiguousAlignment - Called (with the 0-based field index) for each
|
|
3333
|
+
* unrecognized escape sequence whose closing escape character could instead have
|
|
3334
|
+
* opened one holding the delimiter that split, so the boundary is one of two
|
|
3335
|
+
* readings the bytes carry.
|
|
3336
|
+
* @param onAlignmentShiftedFields - Called (with the 0-based field index) for each
|
|
3337
|
+
* contested **field** boundary the reading took while resuming on an escape
|
|
3338
|
+
* character that heads no sequence, so every later field is one place further
|
|
3339
|
+
* right than the competing alignment puts it. Wired only to the field split: a
|
|
3340
|
+
* repeat or component boundary divides one field and so moves no field-indexed
|
|
3341
|
+
* slot. That is a choice and not a consequence, because components are modeled
|
|
3342
|
+
* inside a field; see {@link ShiftedFieldsSink}.
|
|
3343
|
+
* @param onAlignmentTruncatedField - Called (with the 0-based field index) for that
|
|
3344
|
+
* same condition on the **repeat** split, where no field-indexed slot moves and the
|
|
3345
|
+
* field is instead read as more repeats than the competing alignment gives it.
|
|
3346
|
+
* Where that boundary is the **first** in the field the field's own modeled reading
|
|
3347
|
+
* stops at it, because a field is modeled out of its first repeat alone; at a later
|
|
3348
|
+
* one nothing modeled moves and this still fires. See {@link TruncatedFieldSink}.
|
|
3349
|
+
* The component split is wired to neither, deliberately.
|
|
2899
3350
|
* @returns The record's fields.
|
|
2900
3351
|
* @example
|
|
2901
3352
|
* ```ts
|
|
@@ -2904,7 +3355,7 @@ declare function classifyMessage(records: readonly AstmRecord[]): AstmMessageCla
|
|
|
2904
3355
|
* fields[3].components[0]; // "28.6"
|
|
2905
3356
|
* ```
|
|
2906
3357
|
*/
|
|
2907
|
-
declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void): AstmField[];
|
|
3358
|
+
declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void, onSwallowedDelimiter?: (fieldIndex: number) => void, onAmbiguousAlignment?: (fieldIndex: number) => void, onAlignmentShiftedFields?: (fieldIndex: number) => void, onAlignmentTruncatedField?: (fieldIndex: number) => void): AstmField[];
|
|
2908
3359
|
/**
|
|
2909
3360
|
* Tokenize an `H` (header) record into its fields.
|
|
2910
3361
|
*
|
|
@@ -2929,6 +3380,20 @@ declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?:
|
|
|
2929
3380
|
* @param onUnpairedEscape - Called with the 0-based whole-record field index for
|
|
2930
3381
|
* each unpaired escape character in the data portion. The declaration itself is
|
|
2931
3382
|
* opaque, so the escape character it names never reports here.
|
|
3383
|
+
* @param onSwallowedDelimiter - Called with the 0-based whole-record field index
|
|
3384
|
+
* for each unrecognized escape sequence in the data portion whose body is a
|
|
3385
|
+
* splitting delimiter in force. The declaration is opaque, so the delimiters it
|
|
3386
|
+
* names literally never report here.
|
|
3387
|
+
* @param onAmbiguousAlignment - Called with the 0-based whole-record field index for
|
|
3388
|
+
* each competing escape alignment in the data portion. The declaration is opaque,
|
|
3389
|
+
* so the characters it names literally never report here either.
|
|
3390
|
+
* @param onAlignmentShiftedFields - Called with the 0-based whole-record field index
|
|
3391
|
+
* for each contested field boundary in the data portion whose reading resumes on
|
|
3392
|
+
* an escape character heading no sequence. The declaration is opaque, so it never
|
|
3393
|
+
* reports here either.
|
|
3394
|
+
* @param onAlignmentTruncatedField - Called with the 0-based whole-record field index
|
|
3395
|
+
* for each contested **repeat** boundary in the data portion on that same tail
|
|
3396
|
+
* test. The declaration is opaque, so it never reports here either.
|
|
2932
3397
|
* @returns The header's fields.
|
|
2933
3398
|
* @example
|
|
2934
3399
|
* ```ts
|
|
@@ -2938,7 +3403,7 @@ declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?:
|
|
|
2938
3403
|
* fields[4].raw; // "sender"
|
|
2939
3404
|
* ```
|
|
2940
3405
|
*/
|
|
2941
|
-
declare function tokenizeHeader(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void): AstmField[];
|
|
3406
|
+
declare function tokenizeHeader(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void, onSwallowedDelimiter?: (fieldIndex: number) => void, onAmbiguousAlignment?: (fieldIndex: number) => void, onAlignmentShiftedFields?: (fieldIndex: number) => void, onAlignmentTruncatedField?: (fieldIndex: number) => void): AstmField[];
|
|
2942
3407
|
/**
|
|
2943
3408
|
* The primary scalar of a field: its first repeat's first component, decoded.
|
|
2944
3409
|
* Returns `undefined` for a truly empty field so callers can distinguish
|
|
@@ -2990,7 +3455,19 @@ declare const FATAL_CODES: {
|
|
|
2990
3455
|
readonly EMPTY_INPUT: "EMPTY_INPUT";
|
|
2991
3456
|
/** The first record is not an `H` (header) record: an ASTM message must lead with `H`. */
|
|
2992
3457
|
readonly ASTM_RECORD_NO_HEADER: "ASTM_RECORD_NO_HEADER";
|
|
2993
|
-
/**
|
|
3458
|
+
/**
|
|
3459
|
+
* The first `H` record could not declare all four delimiters (field/repeat/component/escape).
|
|
3460
|
+
* One code, four reasons, and the message says which: the record is not a header, it is shorter
|
|
3461
|
+
* than a header plus a three-character definition, its definition field holds fewer than three
|
|
3462
|
+
* characters before the next field separator, or its field separator is also one of the other
|
|
3463
|
+
* three. Only the second of those is "too short", so read the message rather than assuming it.
|
|
3464
|
+
*
|
|
3465
|
+
* **Two of the four cannot be reached on this fatal, and are not dead code.** A first record that
|
|
3466
|
+
* is not an `H` raises `ASTM_RECORD_NO_HEADER` before the declaration is ever read, and a field
|
|
3467
|
+
* separator reused among the other three ends the delimiter definition where it appears, so the
|
|
3468
|
+
* truncation reason answers first. The reader names all four because it is also called directly
|
|
3469
|
+
* and on later headers, where the same conditions are a warning rather than this fatal.
|
|
3470
|
+
*/
|
|
2994
3471
|
readonly ASTM_RECORD_UNDECLARED_DELIMITERS: "ASTM_RECORD_UNDECLARED_DELIMITERS";
|
|
2995
3472
|
};
|
|
2996
3473
|
/**
|
|
@@ -3065,14 +3542,63 @@ declare class AstmParseError extends Error {
|
|
|
3065
3542
|
* next escape character with no bound, which meant a single unescaped `&` in a
|
|
3066
3543
|
* value merged every field after it into that value, in silence.
|
|
3067
3544
|
*
|
|
3068
|
-
* **The atom rule is unchanged and still costs a boundary in one case
|
|
3069
|
-
* is opaque by design, so where its body is
|
|
3070
|
-
* canonical set) that delimiter does not
|
|
3071
|
-
* keeps `&F&` one token under a declared
|
|
3072
|
-
*
|
|
3073
|
-
*
|
|
3074
|
-
*
|
|
3075
|
-
*
|
|
3545
|
+
* **The atom rule is unchanged and still costs a boundary in one case, which now
|
|
3546
|
+
* has a report of its own.** A triple is opaque by design, so where its body is
|
|
3547
|
+
* itself a delimiter (`&|&` under the canonical set) that delimiter does not
|
|
3548
|
+
* split. That is deliberate: it is what keeps `&F&` one token under a declared
|
|
3549
|
+
* set that names `F` as a delimiter. Where the body is not a recognized mnemonic
|
|
3550
|
+
* the parse layer reports it as `ASTM_UNKNOWN_ESCAPE_SEQUENCE`, and where that
|
|
3551
|
+
* unrecognized body is *also* one of the three splitting roles in force it
|
|
3552
|
+
* reports `ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE` **as well**, which is the
|
|
3553
|
+
* code that says a boundary went missing rather than merely that a body was not
|
|
3554
|
+
* recognized. Do not "fix" this by narrowing the atom: the narrowing would break
|
|
3555
|
+
* the guarantee the atom exists for. The split is unchanged; what changed is that
|
|
3556
|
+
* the loss is now reported by a code no profile may tolerate.
|
|
3557
|
+
*
|
|
3558
|
+
* **The match is greedy and leftmost, and that costs a boundary in the opposite
|
|
3559
|
+
* direction, which also has a report of its own.** Triples are taken left to
|
|
3560
|
+
* right, so the escape character that *closes* one triple cannot also *open* the
|
|
3561
|
+
* next. Where it could have (the character after a triple is a splitting
|
|
3562
|
+
* delimiter and the one after that is the escape character), the bytes carry two
|
|
3563
|
+
* alignments that disagree about that delimiter: under the reading taken it ends
|
|
3564
|
+
* a field, repeat or component, and under the other it sits inside an opaque
|
|
3565
|
+
* atom and ends nothing. Where the earlier triple's body was **not** a
|
|
3566
|
+
* recognized mnemonic, the reading taken rests on a triple this codec cannot
|
|
3567
|
+
* interpret at all, so nothing in its own vocabulary prefers that reading to the
|
|
3568
|
+
* competing one, and the parse layer reports
|
|
3569
|
+
* `ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT`. The split is unchanged
|
|
3570
|
+
* here too: the leftmost reading is kept, and what is new is that a boundary
|
|
3571
|
+
* which may not be the sender's is no longer reported only by codes a profile
|
|
3572
|
+
* may tolerate. Where that earlier body **was** a recognized mnemonic nothing is
|
|
3573
|
+
* reported, and the reason is narrower than it looks, so read
|
|
3574
|
+
* {@link AmbiguousAlignmentSink} before widening it.
|
|
3575
|
+
*
|
|
3576
|
+
* **A second question is asked at the same position, and it is a different one,
|
|
3577
|
+
* which is why it is a second code and not a widening of the first.** That report
|
|
3578
|
+
* asks whether this codec's vocabulary prefers the reading taken *at* the contested
|
|
3579
|
+
* position. It says nothing about what the reading taken makes of the bytes *after*
|
|
3580
|
+
* the boundary, and that is where the two alignments really diverge: they resume
|
|
3581
|
+
* one character apart, so they disagree about the whole tail. Where the escape
|
|
3582
|
+
* character the reading taken resumes on heads no sequence at all, the boundary was
|
|
3583
|
+
* bought with a byte this reading cannot read, while the competing alignment is
|
|
3584
|
+
* precisely the reading that can. On the **field** separator that gained boundary
|
|
3585
|
+
* shifts every later field one place, so a result record's units and **result
|
|
3586
|
+
* status** are read out of slots the competing alignment does not put them in, and
|
|
3587
|
+
* a status of `final` can be a consequence of the alignment rather than something
|
|
3588
|
+
* the sender wrote there. That is `ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS`; see
|
|
3589
|
+
* {@link ShiftedFieldsSink}, including the tail it deliberately stays silent on, and
|
|
3590
|
+
* why that silence is not a claim that nothing was lost there.
|
|
3591
|
+
*
|
|
3592
|
+
* **The same question asked on the repeat separator has a different answer, so it
|
|
3593
|
+
* has a code of its own.** A gained repeat boundary moves no field-indexed slot, and
|
|
3594
|
+
* where it is the **first** boundary in the field it still reaches a modeled slot,
|
|
3595
|
+
* because a field's modeled reading is taken from its first repeat alone: everything
|
|
3596
|
+
* past that boundary leaves the record, so a result value truncates and a Universal
|
|
3597
|
+
* Test ID or a patient name loses the components that sat after it. That is
|
|
3598
|
+
* `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD`; see {@link TruncatedFieldSink}, including
|
|
3599
|
+
* what it does at a later boundary, where it fires and no modeled slot moves. The
|
|
3600
|
+
* **component** separator is a third question again and is deliberately not answered
|
|
3601
|
+
* here, because there the components do not disappear, they shift.
|
|
3076
3602
|
*
|
|
3077
3603
|
* Re-escaping (the inverse, for spec-clean emit) lives in the serializer and is
|
|
3078
3604
|
* deliberately not implemented here.
|
|
@@ -3092,6 +3618,195 @@ type UnknownEscapeSink = () => void;
|
|
|
3092
3618
|
* `ASTM_UNPAIRED_ESCAPE_CHARACTER` warning. Optional so the codec can be used purely.
|
|
3093
3619
|
*/
|
|
3094
3620
|
type UnpairedEscapeSink = () => void;
|
|
3621
|
+
/**
|
|
3622
|
+
* A callback the codec calls when an **unrecognized** escape body is itself one of
|
|
3623
|
+
* the three splitting delimiters in force (field, repeat, component), so the atom
|
|
3624
|
+
* rule kept that character out of the split and a boundary the sender's bytes
|
|
3625
|
+
* carried never became one. The value is preserved verbatim either way; the
|
|
3626
|
+
* callback lets the parser surface a value-free
|
|
3627
|
+
* `ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE` warning. Optional so the codec can
|
|
3628
|
+
* be used purely.
|
|
3629
|
+
*
|
|
3630
|
+
* The **escape** character is deliberately not in that test: it is not a splitting
|
|
3631
|
+
* role, so `&&&` under the canonical set loses no boundary. A body that is a
|
|
3632
|
+
* *recognized* mnemonic is not in it either, whatever character it is: `&F&` under
|
|
3633
|
+
* a set naming `F` as the repeat delimiter is the escaped field delimiter the
|
|
3634
|
+
* sender wrote, and reading it as a swallowed repeat boundary would report the
|
|
3635
|
+
* escape mechanism working as a defect.
|
|
3636
|
+
*/
|
|
3637
|
+
type SwallowedDelimiterSink = () => void;
|
|
3638
|
+
/**
|
|
3639
|
+
* A callback the split calls when the escape character that **closed** an
|
|
3640
|
+
* unrecognized escape sequence could instead have **opened** one whose body is the
|
|
3641
|
+
* delimiter being split on, so the two alignments of the same bytes disagree about
|
|
3642
|
+
* whether that delimiter ends a field, repeat or component. The leftmost reading is
|
|
3643
|
+
* kept and nothing is re-split; the callback lets the parser surface a value-free
|
|
3644
|
+
* `ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT` warning. Optional so the split can be
|
|
3645
|
+
* used purely.
|
|
3646
|
+
*
|
|
3647
|
+
* Two exclusions, both deliberate. The first is what keeps this off conformant
|
|
3648
|
+
* streams, and it is **wider than the case it is justified on**, so the residue is
|
|
3649
|
+
* named here rather than left to be found:
|
|
3650
|
+
*
|
|
3651
|
+
* - **The earlier sequence's body must not be a recognized mnemonic.** The test is
|
|
3652
|
+
* which alignment this codec's own vocabulary supports, not which one is tidier.
|
|
3653
|
+
* Where the earlier body is unrecognized the reading taken rests on a triple the
|
|
3654
|
+
* codec cannot interpret, while the competitor's body is the delimiter character,
|
|
3655
|
+
* which it usually cannot interpret either: nothing prefers one, and that is what
|
|
3656
|
+
* this reports. Where the earlier body is a recognized mnemonic the reading taken
|
|
3657
|
+
* interprets a construct (`&F&` is the sender escaping a field separator, which is
|
|
3658
|
+
* what the mechanism is for) and the competitor **usually** interprets none, so the
|
|
3659
|
+
* vocabulary usually prefers one, and reporting it would report the escape
|
|
3660
|
+
* mechanism working. Usually, not always: see the second residue below.
|
|
3661
|
+
* **What that argument does not cover, measured.** It does not follow that the
|
|
3662
|
+
* reading taken is *conformant*: under `28.6&F&|&U/L` it is `&F&`, a real
|
|
3663
|
+
* separator, and then a bare escape character, which this package reports as a
|
|
3664
|
+
* deviation of its own. And where the declared set names a mnemonic letter as a
|
|
3665
|
+
* splitting delimiter, both alignments interpret exactly one construct and
|
|
3666
|
+
* neither is preferred, yet the exclusion still silences it. Both residues are
|
|
3667
|
+
* recorded with their measurements rather than closed by widening this test: the
|
|
3668
|
+
* criterion that would cover them is a different one (counting what each
|
|
3669
|
+
* alignment interprets), and swapping criteria moves which streams a published
|
|
3670
|
+
* package refuses.
|
|
3671
|
+
* - **The following character must be the delimiter this split is taken on.** The
|
|
3672
|
+
* split runs once per role, so a character that is a delimiter in a *later* role
|
|
3673
|
+
* is reported by that role's pass, on the segment it survives into, and never
|
|
3674
|
+
* twice. A delimiter with no escape character two positions past it is excluded by
|
|
3675
|
+
* the rule that defines the condition rather than by a judgement: the sequence the
|
|
3676
|
+
* competing alignment would need never closes, so there is no competitor.
|
|
3677
|
+
*
|
|
3678
|
+
* @param segmentIndex - The 0-based index of the segment being accumulated when the
|
|
3679
|
+
* ambiguity was seen, so a caller splitting a record into fields can report which
|
|
3680
|
+
* field it sat in. A caller splitting one field into repeats or components
|
|
3681
|
+
* already knows the field index and ignores this.
|
|
3682
|
+
*/
|
|
3683
|
+
type AmbiguousAlignmentSink = (segmentIndex: number) => void;
|
|
3684
|
+
/**
|
|
3685
|
+
* A callback the split calls when a competing escape alignment decided a boundary
|
|
3686
|
+
* **and the reading taken cannot read the byte immediately past it**: the escape
|
|
3687
|
+
* character the leftmost reading resumes on heads no escape sequence at all, so it
|
|
3688
|
+
* is kept as a bare literal (reported separately as
|
|
3689
|
+
* `ASTM_UNPAIRED_ESCAPE_CHARACTER`), while the competing alignment is exactly the
|
|
3690
|
+
* reading that gives that character a job, as the close of its own triple.
|
|
3691
|
+
*
|
|
3692
|
+
* Wired **only to the split taken on the field separator**, because that is the
|
|
3693
|
+
* whole of its claim: a gained *field* boundary shifts every later field one place,
|
|
3694
|
+
* so a record's modeled slots after it are decided by the alignment rather than by
|
|
3695
|
+
* the sender's own positions. On a result record that is the units slot and the
|
|
3696
|
+
* **result status** slot: the sender's trailing letter lands in field 9 under the
|
|
3697
|
+
* reading taken and in no field at all under the competing one, so a status of
|
|
3698
|
+
* `final` can be a consequence of the alignment rather than something the sender
|
|
3699
|
+
* put there. A gained *repeat* or *component* boundary divides one field and
|
|
3700
|
+
* reaches nothing outside it, so it moves no **field-indexed** slot and is
|
|
3701
|
+
* deliberately outside this. **That bound is a choice, not a consequence, and the
|
|
3702
|
+
* difference matters**: components are modeled *inside* a field (a Universal Test
|
|
3703
|
+
* ID's coding scheme and local code, a patient name's parts), so a gained repeat or
|
|
3704
|
+
* component boundary does reach a modeled slot. The **repeat** half of that is
|
|
3705
|
+
* reported by {@link TruncatedFieldSink}, on the same tail test and under its own
|
|
3706
|
+
* code, because what it costs is a different thing. The **component** half is
|
|
3707
|
+
* measured, reported by nothing, and open: not something either sink covers. The
|
|
3708
|
+
* callback lets the parser surface a value-free
|
|
3709
|
+
* `ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS` warning. Optional so the split can be used
|
|
3710
|
+
* purely.
|
|
3711
|
+
*
|
|
3712
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is
|
|
3713
|
+
* identical, and the status read is the status that was always read. What is new is
|
|
3714
|
+
* that the shift is reported by a code no profile may tolerate, where before it was
|
|
3715
|
+
* covered only by tolerable ones.
|
|
3716
|
+
*
|
|
3717
|
+
* **This is independent of {@link AmbiguousAlignmentSink}, and fires alongside it
|
|
3718
|
+
* rather than instead of it.** That one asks whether the codec's vocabulary prefers
|
|
3719
|
+
* the reading taken *at* the contested position and is silent where the earlier
|
|
3720
|
+
* body is a recognized mnemonic; this one asks what the reading taken makes of the
|
|
3721
|
+
* bytes *after* the boundary and does not consult the earlier body at all. The two
|
|
3722
|
+
* questions are different, so neither test is widened to answer the other.
|
|
3723
|
+
*
|
|
3724
|
+
* **The tail is weighed one construct deep, and that bound is stated rather than
|
|
3725
|
+
* left to be found.** Two tails are excluded, with the same reason: the bytes past
|
|
3726
|
+
* the boundary prefer the reading taken.
|
|
3727
|
+
*
|
|
3728
|
+
* - **The escape character heads a sequence this codec recognizes.** Then the
|
|
3729
|
+
* reading taken interprets it and leaves nothing bare, while the competing
|
|
3730
|
+
* alignment would leave it bare. Under a set naming the field separator `F`,
|
|
3731
|
+
* `28.6&F&F&F&U/L` is the sender escaping that separator, writing it, and
|
|
3732
|
+
* escaping it again: entirely well formed, and refusing it is the over-refusal
|
|
3733
|
+
* that sank the preceding candidate criterion for this family.
|
|
3734
|
+
* - **The escape character heads a sequence whose body this codec does not
|
|
3735
|
+
* recognize.** Then the reading taken still consumes it as a sequence and carries
|
|
3736
|
+
* one unreadable body, while the competing alignment would leave *two* escape
|
|
3737
|
+
* characters bare. The preference is stronger there, not weaker.
|
|
3738
|
+
*
|
|
3739
|
+
* What that bound leaves open is the second of those tails, where the field shift
|
|
3740
|
+
* is real and this stays silent. It is measured and named rather than closed by
|
|
3741
|
+
* widening the test, because widening it would report a boundary the bytes prefer.
|
|
3742
|
+
*
|
|
3743
|
+
* @param segmentIndex - The 0-based index of the field being accumulated when the
|
|
3744
|
+
* shifting boundary was taken. Every field after it is one place further right
|
|
3745
|
+
* than the competing alignment puts it.
|
|
3746
|
+
*/
|
|
3747
|
+
type ShiftedFieldsSink = (segmentIndex: number) => void;
|
|
3748
|
+
/**
|
|
3749
|
+
* A callback the split calls on **exactly the condition {@link ShiftedFieldsSink}
|
|
3750
|
+
* reports**, wired instead to the split taken on the **repeat** separator. The two
|
|
3751
|
+
* sinks test the same predicate, because the predicate is about the bytes and the
|
|
3752
|
+
* split does not know which role it is being taken on; they are separate so the
|
|
3753
|
+
* caller that does know can name what the gained boundary costs, which is not the
|
|
3754
|
+
* same thing in the two roles. Do not "simplify" them into one: the codes would then
|
|
3755
|
+
* have to make one claim covering both, and the claim is different.
|
|
3756
|
+
*
|
|
3757
|
+
* **What a gained repeat boundary costs, and it is not a shift.** No field-indexed
|
|
3758
|
+
* slot moves: the units slot and the result-status slot are read out of the same
|
|
3759
|
+
* field numbers under either alignment. What the report says is that **the field is
|
|
3760
|
+
* read as more repeats than the competing alignment gives it**, and that is true
|
|
3761
|
+
* wherever it fires. What that costs depends on **which** boundary was gained, and
|
|
3762
|
+
* the two cases are stated separately rather than folded into one claim:
|
|
3763
|
+
*
|
|
3764
|
+
* - **The gained boundary is the FIRST in the field, and then a modeled slot is
|
|
3765
|
+
* lost.** A field's modeled value and its components are taken from its **first
|
|
3766
|
+
* repeat alone**, so everything past the boundary is still on the wire, still in
|
|
3767
|
+
* `repeats`, and gone from every modeled slot. Under a set naming the repeat
|
|
3768
|
+
* separator `F`, the value `28.6&S&F&U/L` reads as the two repeats `28.6^` and
|
|
3769
|
+
* `&U/L`, and every value extractor answers `28.6^`, while the competing alignment
|
|
3770
|
+
* reads one repeat carrying all of it. The same boundary inside a Universal Test ID
|
|
3771
|
+
* field leaves the components of the first repeat only, so a UTID whose bytes carry
|
|
3772
|
+
* a test code can come back as a single component holding a decoded delimiter, with
|
|
3773
|
+
* the coding scheme and local code read from nothing at all. A patient name loses
|
|
3774
|
+
* its given and middle names the same way.
|
|
3775
|
+
* - **The gained boundary is a LATER one, and then no modeled slot moves.** The first
|
|
3776
|
+
* repeat is identical under both alignments, so the value, the components and every
|
|
3777
|
+
* slot taken from them read the same either way; what differs is the repeat
|
|
3778
|
+
* structure after the first. **This still fires there, deliberately and measurably**
|
|
3779
|
+
* (`5.0F28.6&S&F&U/L` under that same set), because the boundary is still one the
|
|
3780
|
+
* bytes do not force and a consumer reading `repeats` is still reading an alignment
|
|
3781
|
+
* guess. Relative to the modeled slots that is **over-reporting, never
|
|
3782
|
+
* under-reporting**, which is the direction this package errs in. Narrowing the sink
|
|
3783
|
+
* to the first boundary would change which streams a published package refuses and
|
|
3784
|
+
* wants its own measurement, so the bound is written down instead of guessed at.
|
|
3785
|
+
*
|
|
3786
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is
|
|
3787
|
+
* identical, `repeats` still carries every one of them, and the value read is the
|
|
3788
|
+
* value that was always read. What is new is that the gained boundary is reported by
|
|
3789
|
+
* a code no profile may tolerate, where before it was covered only by tolerable ones.
|
|
3790
|
+
*
|
|
3791
|
+
* **The tail is weighed one construct deep, exactly as in {@link ShiftedFieldsSink},
|
|
3792
|
+
* and for the same reason.** Where the escape character the reading taken resumes on
|
|
3793
|
+
* heads a sequence this codec recognizes, the bytes prefer the reading taken and the
|
|
3794
|
+
* two repeats are the ones the sender wrote: under a set naming the repeat separator
|
|
3795
|
+
* `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and escaped again.
|
|
3796
|
+
* Refusing that is the over-refusal that sank an earlier candidate criterion for this
|
|
3797
|
+
* family. Where it heads a sequence whose body is *unrecognized*, the competing
|
|
3798
|
+
* alignment would leave two escape characters bare, so the preference is stronger
|
|
3799
|
+
* again. **The truncation in that second case is real and this stays silent about
|
|
3800
|
+
* it**: a named residue, measured, not overlooked, and not closed by widening the
|
|
3801
|
+
* test, because widening it would report a boundary the bytes prefer.
|
|
3802
|
+
*
|
|
3803
|
+
* @param segmentIndex - The 0-based index of the repeat being accumulated when the
|
|
3804
|
+
* gained boundary was taken, so `0` is the boundary that ends the first repeat and
|
|
3805
|
+
* is the one that reaches a modeled slot. It is **not** a field index: the caller
|
|
3806
|
+
* splitting one field into repeats already knows which field it is in, and reports
|
|
3807
|
+
* that.
|
|
3808
|
+
*/
|
|
3809
|
+
type TruncatedFieldSink = (segmentIndex: number) => void;
|
|
3095
3810
|
/**
|
|
3096
3811
|
* Decode the four recognized ASTM escape mnemonics in a single already-split
|
|
3097
3812
|
* leaf (a component string), substituting the *active* delimiters read from the
|
|
@@ -3106,6 +3821,8 @@ type UnpairedEscapeSink = () => void;
|
|
|
3106
3821
|
* @param d - The delimiters resolved from the header.
|
|
3107
3822
|
* @param onUnknown - Called once per unrecognized escape body encountered.
|
|
3108
3823
|
* @param onUnpaired - Called once per escape character that heads no sequence.
|
|
3824
|
+
* @param onSwallowedDelimiter - Called once per unrecognized escape body that is
|
|
3825
|
+
* itself a splitting delimiter in force, so that delimiter never split.
|
|
3109
3826
|
* @returns The decoded string.
|
|
3110
3827
|
* @example
|
|
3111
3828
|
* ```ts
|
|
@@ -3114,7 +3831,7 @@ type UnpairedEscapeSink = () => void;
|
|
|
3114
3831
|
* decodeEscapes("O&Brien", CANONICAL_DELIMITERS); // "O&Brien" (literal, reported)
|
|
3115
3832
|
* ```
|
|
3116
3833
|
*/
|
|
3117
|
-
declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownEscapeSink, onUnpaired?: UnpairedEscapeSink): string;
|
|
3834
|
+
declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownEscapeSink, onUnpaired?: UnpairedEscapeSink, onSwallowedDelimiter?: SwallowedDelimiterSink): string;
|
|
3118
3835
|
/**
|
|
3119
3836
|
* Split `text` on `delimiter`, treating an escape sequence (escape, one body
|
|
3120
3837
|
* character, escape) as an opaque atom so a delimiter that appears *inside* an
|
|
@@ -3132,11 +3849,38 @@ declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownE
|
|
|
3132
3849
|
* closes is what used to merge the whole remainder of a record into one field.
|
|
3133
3850
|
* Decoding the resulting leaf is what reports it, so this function stays a pure
|
|
3134
3851
|
* split. Note the two rules together: a delimiter after such a character does
|
|
3135
|
-
* split, and a delimiter sitting inside a real three-character atom does not.
|
|
3852
|
+
* split, and a delimiter sitting inside a real three-character atom does not. The
|
|
3853
|
+
* second of those is reported, from the decode step rather than from here,
|
|
3854
|
+
* whenever the atom's body was not a recognized mnemonic.
|
|
3855
|
+
*
|
|
3856
|
+
* **Atoms are matched greedily, leftmost first, and where that choice decides a
|
|
3857
|
+
* boundary it is reported from here.** The escape character closing one triple
|
|
3858
|
+
* cannot also open the next, so `28.6&Z&|&U/L` is read as the atom `&Z&`, then a
|
|
3859
|
+
* field separator that splits, and not as `&Z`, then the atom `&|&` whose field
|
|
3860
|
+
* separator would not have split. Both alignments are in the bytes and they
|
|
3861
|
+
* disagree by one boundary. The reading is **not** changed (that would only pick
|
|
3862
|
+
* the other alignment, with no more evidence for it); it is reported through
|
|
3863
|
+
* `onAmbiguousAlignment`, and only where the earlier body was unrecognized, since
|
|
3864
|
+
* a recognized mnemonic is a construct this codec can interpret and the
|
|
3865
|
+
* competitor's body usually is not. That exclusion is wider than that argument:
|
|
3866
|
+
* see {@link AmbiguousAlignmentSink}.
|
|
3136
3867
|
*
|
|
3137
3868
|
* @param text - The field or repeat string to split.
|
|
3138
3869
|
* @param delimiter - The delimiter to split on.
|
|
3139
3870
|
* @param escape - The active escape character.
|
|
3871
|
+
* @param onAmbiguousAlignment - Called once per unrecognized escape sequence whose
|
|
3872
|
+
* closing escape character could instead have opened a sequence holding this
|
|
3873
|
+
* delimiter, so the boundary taken here is not the only reading of the bytes.
|
|
3874
|
+
* @param onShiftedFields - Called once per contested boundary where the escape
|
|
3875
|
+
* character this reading resumes on heads no sequence of its own, so the boundary
|
|
3876
|
+
* was bought with a byte the reading cannot read. Wired only by the split taken on
|
|
3877
|
+
* the field separator: see {@link ShiftedFieldsSink} for why, and for the tail it
|
|
3878
|
+
* deliberately does not report.
|
|
3879
|
+
* @param onTruncatedField - Called on that same condition, wired only by the split
|
|
3880
|
+
* taken on the repeat separator, where the cost is not a shift but a field read as
|
|
3881
|
+
* more repeats than the competing alignment gives it. Where that boundary is the
|
|
3882
|
+
* **first** in the field the field's modeled reading stops at it; at a later one
|
|
3883
|
+
* nothing modeled moves and this still fires. See {@link TruncatedFieldSink}.
|
|
3140
3884
|
* @returns The raw segments, in order.
|
|
3141
3885
|
* @example
|
|
3142
3886
|
* ```ts
|
|
@@ -3146,7 +3890,7 @@ declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownE
|
|
|
3146
3890
|
* splitEscapeAware("O&Brien^John", "^", "&"); // ["O&Brien", "John"] (unpaired: literal)
|
|
3147
3891
|
* ```
|
|
3148
3892
|
*/
|
|
3149
|
-
declare function splitEscapeAware(text: string, delimiter: string, escape: string): string[];
|
|
3893
|
+
declare function splitEscapeAware(text: string, delimiter: string, escape: string, onAmbiguousAlignment?: AmbiguousAlignmentSink, onShiftedFields?: ShiftedFieldsSink, onTruncatedField?: TruncatedFieldSink): string[];
|
|
3150
3894
|
|
|
3151
3895
|
/**
|
|
3152
3896
|
* Deep-freeze helper for the immutable ASTM model.
|
|
@@ -3479,9 +4223,81 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
3479
4223
|
* field separator was absent outright, so it is a reader of record structure that
|
|
3480
4224
|
* cannot be relied on as a sweep.
|
|
3481
4225
|
*
|
|
4226
|
+
* **A fourth and a fifth reader of record structure landed together, and the
|
|
4227
|
+
* allow-list was re-derived against them.** `ASTM_RECORD_DELIMITER_ROLE_COLLISION`
|
|
4228
|
+
* reports a header declaring one character in two of the repeat / component /
|
|
4229
|
+
* escape roles, so the boundary between those two roles is not in the bytes;
|
|
4230
|
+
* `ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE` reports an unrecognized escape body
|
|
4231
|
+
* that is itself a splitting delimiter, so the atom rule kept it out of the split.
|
|
4232
|
+
* Both are safety-critical by construction, because that side is computed rather
|
|
4233
|
+
* than listed. What they required was re-reading the survivors below against them,
|
|
4234
|
+
* and two of the four needed their arguments rewritten rather than merely
|
|
4235
|
+
* re-checked: each was, on its own, the *only* warning a stream exhibiting one of
|
|
4236
|
+
* these conditions raised. That is the state this file exists to prevent, and it is
|
|
4237
|
+
* why the remedy in both cases was a **second, narrower, non-tolerable code**
|
|
4238
|
+
* rather than striking the tolerable one off. Striking it off would have changed
|
|
4239
|
+
* behavior for every profile naming it while still leaving the two conditions
|
|
4240
|
+
* reported by codes that also fire on cases costing nothing.
|
|
4241
|
+
*
|
|
4242
|
+
* **A sixth reader landed with `ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT`, and it is
|
|
4243
|
+
* the mirror of the fifth.** That code reports that the escape character closing an
|
|
4244
|
+
* unrecognized sequence could instead have opened one holding the delimiter that
|
|
4245
|
+
* split, so the bytes carry two alignments disagreeing by one boundary and the
|
|
4246
|
+
* leftmost was taken. It is safety-critical by construction, and the direction is
|
|
4247
|
+
* why it could not be left to the codes below: the fifth reports a boundary the
|
|
4248
|
+
* reading **lost**, this one a boundary the reading may have **gained**, and a
|
|
4249
|
+
* gained boundary hands back a value the sender's bytes do not unambiguously carry.
|
|
4250
|
+
* Both of the codes that condition previously raised are on the list below, so a
|
|
4251
|
+
* profile naming them left `{ strict: true }` accepting it. Re-reading the survivors
|
|
4252
|
+
* against it changed no admission and rewrote no argument: none of the four is a
|
|
4253
|
+
* statement about where a boundary was taken, and the new code is not tolerable, so
|
|
4254
|
+
* the same "a profile re-badges the code it names and no other" reason already
|
|
4255
|
+
* written under the two escape entries covers it.
|
|
4256
|
+
*
|
|
4257
|
+
* **A further reader landed with `ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS`, and it is
|
|
4258
|
+
* the first one that reaches a MODELED SLOT rather than a boundary.** It reports
|
|
4259
|
+
* that a contested alignment decided a **field** boundary while the reading taken
|
|
4260
|
+
* resumes on an escape character heading no sequence, so every later field sits one
|
|
4261
|
+
* place further right than the competing alignment puts it. On a result record that
|
|
4262
|
+
* moves the units and the **result status**: the sender's trailing letter is read out
|
|
4263
|
+
* of field 9 under the reading taken and out of no field at all under the other, so a
|
|
4264
|
+
* status of `final` can be a consequence of the alignment rather than something the
|
|
4265
|
+
* sender wrote there. Until it existed the only warning on that stream was the
|
|
4266
|
+
* **tolerable** `ASTM_UNPAIRED_ESCAPE_CHARACTER`, so the widest gate-legal profile
|
|
4267
|
+
* plus `{ strict: true }` accepted a fabricated `final` on a lab result. It is
|
|
4268
|
+
* safety-critical by construction and must never be admitted. Re-reading the
|
|
4269
|
+
* survivors against it moved no admission, and the reason is worth stating because it
|
|
4270
|
+
* is not last time's: `ASTM_UNPAIRED_ESCAPE_CHARACTER` stays on the list because it
|
|
4271
|
+
* remains true and benign of every case that costs nothing (a bare escape character
|
|
4272
|
+
* in a value alters no boundary at all), and the case where it accompanies a shifted
|
|
4273
|
+
* field is now reported by a code no profile may re-badge.
|
|
4274
|
+
*
|
|
4275
|
+
* **`ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD` landed next, on the same contested
|
|
4276
|
+
* position and the same tail test, and it reaches a modeled slot by the other
|
|
4277
|
+
* route.** It reports a contested alignment deciding a **repeat** boundary. Nothing
|
|
4278
|
+
* shifts: no field-indexed slot moves, which is exactly why the shift report could
|
|
4279
|
+
* not cover it. What it reports is that the field is read as more repeats than the
|
|
4280
|
+
* competing alignment gives it, and **where that gained boundary is the first one in
|
|
4281
|
+
* the field** it reaches a modeled slot, because a field's modeled value and
|
|
4282
|
+
* components are taken from its **first repeat alone**: everything past it stays in
|
|
4283
|
+
* `repeats` and leaves every modeled slot, so a result value truncates and a
|
|
4284
|
+
* Universal Test ID or a patient name comes back holding only the components that sat
|
|
4285
|
+
* before it. Until it existed the warnings on those streams were
|
|
4286
|
+
* `ASTM_NONSTANDARD_DELIMITERS` and `ASTM_UNPAIRED_ESCAPE_CHARACTER`, both on the list
|
|
4287
|
+
* below, so the widest gate-legal profile plus `{ strict: true }` accepted a truncated
|
|
4288
|
+
* value and an emptied test identity. **At a later boundary it fires and no modeled
|
|
4289
|
+
* slot moves**, which is over-reporting relative to those slots and never
|
|
4290
|
+
* under-reporting: measured, and stated on the code itself rather than left to be
|
|
4291
|
+
* found. It is safety-critical by construction and must never be admitted.
|
|
4292
|
+
* Re-reading the survivors against it moved no admission, and the reason is the one
|
|
4293
|
+
* the entry above already turns on: each of them re-badges the code it names and no
|
|
4294
|
+
* other, and what this condition now raises is a code no profile may name.
|
|
4295
|
+
*
|
|
3482
4296
|
* **So the ones that remain are recorded with the reading each one survives**, not
|
|
3483
|
-
* merely with the value it preserves. Each was re-derived against
|
|
3484
|
-
* record structure above
|
|
4297
|
+
* merely with the value it preserves. Each was re-derived against **every** reader of
|
|
4298
|
+
* record structure named above (this sentence deliberately names no count: it has
|
|
4299
|
+
* been corrected upward at every slice that landed one), plus the single-message /
|
|
4300
|
+
* single-patient guards:
|
|
3485
4301
|
*
|
|
3486
4302
|
* - `ASTM_NONSTANDARD_DELIMITERS`: the header's own declared delimiters are read
|
|
3487
4303
|
* and honored either way, and this notes only that they differ from the canonical
|
|
@@ -3493,41 +4309,102 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
3493
4309
|
* force. A stream may be wholly non-canonical and split perfectly, and a wholly
|
|
3494
4310
|
* canonical stream can still carry a record that does not split, so tolerating the
|
|
3495
4311
|
* first can never quiet the second.
|
|
4312
|
+
*
|
|
4313
|
+
* **Its admission was resting on a set of declarations it does not distinguish,
|
|
4314
|
+
* and no longer is.** A declaration naming one character in two roles is
|
|
4315
|
+
* necessarily non-canonical, so this code fired on it, and until
|
|
4316
|
+
* `ASTM_RECORD_DELIMITER_ROLE_COLLISION` existed it was the *only* warning such a
|
|
4317
|
+
* stream raised: tolerating "the declared set is unusual" therefore quieted
|
|
4318
|
+
* "the declared set cannot express the boundary it was read with". That second
|
|
4319
|
+
* condition now has its own code, which is not tolerable, so what is left here is
|
|
4320
|
+
* the honest statement it always meant to be: the set differs from the canonical
|
|
4321
|
+
* one and was honored. The reason tolerating this one cannot quiet that one is the
|
|
4322
|
+
* profile mechanism and nothing subtler: a profile re-badges the code it names and
|
|
4323
|
+
* no other. It is **not** that the two conditions are independent. They are not:
|
|
4324
|
+
* every colliding set is non-canonical, which is exactly why this bullet needed
|
|
4325
|
+
* rewriting.
|
|
3496
4326
|
* - `ASTM_UNKNOWN_ESCAPE_SEQUENCE`: an unrecognized escape body is **preserved
|
|
3497
4327
|
* byte-for-byte** in the decoded value (the escape codec does not guess at one),
|
|
3498
|
-
* so the value is identical with or without the profile.
|
|
3499
|
-
*
|
|
3500
|
-
*
|
|
3501
|
-
*
|
|
3502
|
-
*
|
|
3503
|
-
* **
|
|
3504
|
-
*
|
|
3505
|
-
*
|
|
3506
|
-
*
|
|
3507
|
-
*
|
|
3508
|
-
*
|
|
4328
|
+
* so the value is identical with or without the profile. A decoded field value is
|
|
4329
|
+
* never cut back into records, the type letter is read before decoding, and the
|
|
4330
|
+
* split reader counts fields the escape-aware tokenizer has already finished
|
|
4331
|
+
* dividing, so none of those three sees it.
|
|
4332
|
+
*
|
|
4333
|
+
* **Part 2 acquired a reader here, and it is named rather than left to be found.**
|
|
4334
|
+
* `isSplittingDelimiter` in `../common/escapes.ts` runs **only** on the
|
|
4335
|
+
* unrecognized-body condition this code reports, and decides out of it whether to
|
|
4336
|
+
* raise `ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE`. That is a reader of the
|
|
4337
|
+
* reported condition, which is exactly what part 2 asks about, so this entry no
|
|
4338
|
+
* longer survives on "nothing reads it". It survives on a narrower and checkable
|
|
4339
|
+
* claim: a profile tolerating a code re-badges **that code and no other**, so
|
|
4340
|
+
* tolerating this one cannot quiet what the reader raises, and what it raises is
|
|
4341
|
+
* not tolerable in any case. If a future reader of this same condition ever
|
|
4342
|
+
* produces something a profile **can** tolerate, this entry stops being admissible
|
|
4343
|
+
* and comes off the list.
|
|
4344
|
+
*
|
|
4345
|
+
* **A second reader of the same condition landed, and it is named here for the
|
|
4346
|
+
* same reason.** The mnemonic test in `../common/escapes.ts` is now asked twice:
|
|
4347
|
+
* once by the decoder, to decide whether a body is recognized, and once by the
|
|
4348
|
+
* escape-aware split, which raises `ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT` only
|
|
4349
|
+
* where an **unrecognized** body sits in a sequence a competing alignment would
|
|
4350
|
+
* have aligned differently. That is the condition this code reports, read a second
|
|
4351
|
+
* time, and this entry survives it on the same narrower claim: what that reader
|
|
4352
|
+
* raises is a different code, which a profile cannot name and cannot tolerate.
|
|
4353
|
+
*
|
|
4354
|
+
* **This entry used to be recorded as questionable, and what made it questionable
|
|
4355
|
+
* is now a separate code that is not on this list.** The argument above is about
|
|
4356
|
+
* the *decoded value*, and it holds. What it did not cover is that the
|
|
4357
|
+
* escape-aware split itself treats an `&X&` triple as opaque, so where `X` is a
|
|
4358
|
+
* delimiter that delimiter never became a boundary: the split the argument says
|
|
4359
|
+
* has "already finished dividing" divided one time too few, and this code was the
|
|
4360
|
+
* only report of it. Measured on the canonical set:
|
|
3509
4361
|
* `R|1|^^^687|28.6&|&U/L||||F` yields a value of `28.6&|&U/L`, no units, and status
|
|
3510
|
-
* `unspecified` rather than `final`,
|
|
3511
|
-
* tolerating it (the shipped `referenceCorpus` does)
|
|
3512
|
-
* it. That
|
|
3513
|
-
*
|
|
3514
|
-
*
|
|
3515
|
-
*
|
|
3516
|
-
*
|
|
3517
|
-
*
|
|
3518
|
-
*
|
|
4362
|
+
* `unspecified` rather than `final`, and this was the sole warning, so a profile
|
|
4363
|
+
* tolerating it (the shipped `referenceCorpus` does) let `{ strict: true }` accept
|
|
4364
|
+
* it. That failed part 1 of the two-clause test on the reading, not on the value.
|
|
4365
|
+
* `ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE` now fires **alongside** this code on
|
|
4366
|
+
* exactly that subset and is safety-critical, so the lost boundary is reported by
|
|
4367
|
+
* something no profile may tolerate and that same stream is refused under
|
|
4368
|
+
* `{ strict: true }`. This code was **not** removed, for two reasons: removing it
|
|
4369
|
+
* changes behavior for every profile naming it, and it remains true and benign of
|
|
4370
|
+
* every unrecognized body that is not a delimiter in force, where the sequence is
|
|
4371
|
+
* preserved byte-for-byte and no boundary is lost. What is scoped away here is a
|
|
4372
|
+
* claim, not a guard. Do not close the underlying condition by narrowing the atom,
|
|
4373
|
+
* which is what keeps `&F&` one token under a delimiter set that names `F` as a
|
|
4374
|
+
* delimiter, and do not read the new code as a repair: the value is byte-identical
|
|
4375
|
+
* to what it was, and only the reporting changed.
|
|
3519
4376
|
* - `ASTM_UNPAIRED_ESCAPE_CHARACTER`: an escape character heading no escape
|
|
3520
4377
|
* sequence, read as the **literal character it is** and kept byte-for-byte in the
|
|
3521
4378
|
* decoded value, so the value is identical with or without the profile. Note what
|
|
3522
4379
|
* makes this admissible is a property of the *parse*, not of the warning: reading
|
|
3523
4380
|
* the character as a literal is unconditional, and tolerating the code changes
|
|
3524
|
-
* nothing about how the record splits.
|
|
3525
|
-
*
|
|
4381
|
+
* nothing about how the record splits. None of the three readers named at the top of
|
|
4382
|
+
* this file sees it: the type letter is read before any decoding, the split reader
|
|
3526
4383
|
* counts fields the escape-aware tokenizer has already finished dividing, and a
|
|
3527
|
-
* decoded value is never cut back into records.
|
|
4384
|
+
* decoded value is never cut back into records. **Two later readers DO see it**, and
|
|
4385
|
+
* they are named below rather than left out of this bullet. Contrast the condition it
|
|
3528
4386
|
* replaced, which **would** have failed part 1: reading the same character as an
|
|
3529
4387
|
* unterminated sequence merged every field after it into one, and a code reporting
|
|
3530
4388
|
* that would have been a report of lost values.
|
|
4389
|
+
*
|
|
4390
|
+
* **The alignment reader is not a reader of this condition**, and that much still
|
|
4391
|
+
* holds: it asks whether the character two positions past a sequence *is* the escape
|
|
4392
|
+
* character, never whether that character heads a sequence of its own. The two are
|
|
4393
|
+
* independent in both directions, measured: `28.6&Z&|&U/L` raises this code and the
|
|
4394
|
+
* alignment code together, and `28.6&Z&|&U&L` raises the alignment code with no
|
|
4395
|
+
* unpaired character anywhere in it.
|
|
4396
|
+
*
|
|
4397
|
+
* **The two TAIL readers are a different matter, and "no reader sees it" is
|
|
4398
|
+
* therefore FALSE of this entry and must not be restored.** Both
|
|
4399
|
+
* `ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS` and
|
|
4400
|
+
* `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD` fire on `!isEscapeSequenceAt(text, i + 4,
|
|
4401
|
+
* escape)`, which is exactly the question this code answers, asked about the
|
|
4402
|
+
* character just past a contested boundary. That is a reader of the reported
|
|
4403
|
+
* condition, so this entry survives on the same narrower claim the unknown-sequence
|
|
4404
|
+
* entry above already uses, not on nothing reading it: a profile re-badges the code
|
|
4405
|
+
* it names and no other, and what those two readers raise is not tolerable in any
|
|
4406
|
+
* case. If a future reader of this condition ever produces something a profile
|
|
4407
|
+
* **can** tolerate, this entry stops being admissible and comes off the list.
|
|
3531
4408
|
* - `ASTM_RECORD_UNINTERPRETED_QUERY_STATUS`: a request-information status carried
|
|
3532
4409
|
* verbatim as a leaf field on a `Q` record; the code set is paywalled and is not
|
|
3533
4410
|
* interpreted, profile or not. Classification reads whether a `Q` record is
|
|
@@ -3539,8 +4416,12 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
3539
4416
|
* undefined abnormal flag or result status, an unparseable reference range, absent
|
|
3540
4417
|
* units, a mis-attached comment, a partial timestamp, a query-vs-result ambiguity,
|
|
3541
4418
|
* an unrecognized record type, a record the delimiters in force could not split, a
|
|
3542
|
-
*
|
|
3543
|
-
*
|
|
4419
|
+
* declaration naming one character in two roles, a delimiter an unrecognized escape
|
|
4420
|
+
* sequence kept out of the split, a boundary a competing escape alignment disagrees
|
|
4421
|
+
* about, a field whose modeled reading stops at a boundary only one alignment has,
|
|
4422
|
+
* a bad frame checksum / sequence gap / unterminated
|
|
4423
|
+
* / oversize frame, an ambiguous transport, an unexpected protocol event, or a
|
|
4424
|
+
* rejected frame: is forbidden.
|
|
3544
4425
|
*
|
|
3545
4426
|
* **What this file cannot do for you.** Part 2 is a review obligation, not a
|
|
3546
4427
|
* mechanical one. There is no honest automatic check for "nothing load-bearing
|
|
@@ -3571,6 +4452,29 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
3571
4452
|
* reports a record the delimiters in force could not split, so every modeled field
|
|
3572
4453
|
* of that record is missing: it fails the first half of the test outright.
|
|
3573
4454
|
*
|
|
4455
|
+
* `ASTM_RECORD_DELIMITER_ROLE_COLLISION`,
|
|
4456
|
+
* `ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE` and
|
|
4457
|
+
* `ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT` are not on this list and must not be
|
|
4458
|
+
* added to it. Each reports a boundary the reading cannot defend from the bytes (the
|
|
4459
|
+
* first two a boundary that is not in the reading, the third one that may not be in
|
|
4460
|
+
* the bytes), and each exists precisely because the only warnings its condition
|
|
4461
|
+
* previously raised were among the four below.
|
|
4462
|
+
*
|
|
4463
|
+
* `ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS` is not on this list and must not be added
|
|
4464
|
+
* to it either, and it is the strongest case of the four: it reports not merely a
|
|
4465
|
+
* boundary but a modeled slot changing hands, up to and including a result status
|
|
4466
|
+
* reading `final` that the competing alignment of the same bytes puts in no field at
|
|
4467
|
+
* all.
|
|
4468
|
+
*
|
|
4469
|
+
* `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD` is not on this list and must not be added
|
|
4470
|
+
* to it either. It reports the same contested alignment deciding a **repeat**
|
|
4471
|
+
* boundary, where no slot changes hands and a modeled reading is cut off instead: the
|
|
4472
|
+
* field is read out of its first repeat alone, so where that boundary is the first
|
|
4473
|
+
* one a value truncates and a Universal Test ID's coding scheme and local code are
|
|
4474
|
+
* read from bytes that are no longer in any modeled slot. Reporting a value that is
|
|
4475
|
+
* not the whole of what the sender wrote fails the first half of the two-clause test
|
|
4476
|
+
* outright.
|
|
4477
|
+
*
|
|
3574
4478
|
* @example
|
|
3575
4479
|
* ```ts
|
|
3576
4480
|
* import { TOLERABLE_CODES } from "@cosyte/astm";
|
|
@@ -3675,7 +4579,10 @@ declare const defaultProfile: AstmProfile;
|
|
|
3675
4579
|
* only re-badges the *warning*: no value, flag, status, or identifier is altered
|
|
3676
4580
|
* or dropped. And by the safety gate in `defineAstmProfile`, it **cannot** tolerate
|
|
3677
4581
|
* a result value / flag / status / range / units, a patient or comment context, a
|
|
3678
|
-
* message-kind ambiguity, an unrecognized record type,
|
|
4582
|
+
* message-kind ambiguity, an unrecognized record type, a declaration naming one
|
|
4583
|
+
* character in two delimiter roles, a delimiter an unrecognized
|
|
4584
|
+
* escape sequence kept out of the split, a field boundary a competing escape
|
|
4585
|
+
* alignment disagrees about, or any frame / LTP integrity
|
|
3679
4586
|
* warning: those always surface, profile or not. The unrecognized-record-type case
|
|
3680
4587
|
* matters because message grouping reads a record's type letter, so a letter the
|
|
3681
4588
|
* reader does not recognize may be a header, and a header read as something else
|
|
@@ -4912,4 +5819,4 @@ declare class AstmFrameStrictError extends Error {
|
|
|
4912
5819
|
*/
|
|
4913
5820
|
declare const VERSION: string;
|
|
4914
5821
|
|
|
4915
|
-
export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, type AbnormalFlag, type AbnormalFlagCode, type AbnormalFlagMeaning, type AmbiguousCode, type AnyAstmWarningCode, AstmAmbiguousStreamError, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFrameEncodeErrorCode, type AstmFramePosition, AstmFrameStrictError, type AstmFrameWarning, type AstmFraming, type AstmLivdWarning, type AstmLtpWarning, type AstmMessage, type AstmMessageClassification, type AstmMessageKind, AstmParseError, type AstmParseOptions, type AstmPosition, type AstmProfile, AstmProfileDefinitionError, type AstmProfileProvenance, type AstmQuirkMatch, type AstmQuirkTolerance, type AstmRecord, type AstmRecordInput, type AstmRecordWarning, AstmSerializeError, type AstmSerializeErrorCode, type AstmStreamMessage, AstmStrictError, CANONICAL_DELIMITERS, type CommentInput, type CommentRecord, type ComposeFramesOptions, type DecodeAstmFramesResult, type DefineAstmProfileOptions, type Delimiters, type DetectFramingOptions, type DetectFramingResult, FATAL_CODES, FRAME_WARNING_CODES, type FatalCode, type FrameChecksum, type FrameOptions, type FrameTerminator, type FrameWarningCode, type FramedAstmResult, type HeaderInput, type HeaderRecord, LIVD_WARNING_CODES, LTP_WARNING_CODES, type LivdAnnotation, type LivdCatalog, type LivdEntry, type LivdLookup, type LivdMapping, type LivdResult, type LivdWarningCode, type LtpAction, type LtpEvent, type LtpPhase, type LtpState, type LtpTransition, type LtpWarningCode, type ManufacturerRecord, type MessageInput, type OrderInput, type OrderRecord, type PatientInput, type PatientName, type PatientNameInput, type PatientRecord, type QueryInput, type QueryRecord, type ReferenceRange, type ReferenceRangeKind, type ResultInput, type ResultRecord, type ResultStatus, type ResultStatusCode, type ResultStatusMeaning, SAFETY_CRITICAL_CODES, type ScientificRecord, TOLERABLE_CODES, type TerminatorRecord, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnpairedEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, WARNING_CODES, type WarningCode, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, messages, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|
|
5822
|
+
export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, type AbnormalFlag, type AbnormalFlagCode, type AbnormalFlagMeaning, type AmbiguousAlignmentSink, type AmbiguousCode, type AnyAstmWarningCode, AstmAmbiguousStreamError, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFrameEncodeErrorCode, type AstmFramePosition, AstmFrameStrictError, type AstmFrameWarning, type AstmFraming, type AstmLivdWarning, type AstmLtpWarning, type AstmMessage, type AstmMessageClassification, type AstmMessageKind, AstmParseError, type AstmParseOptions, type AstmPosition, type AstmProfile, AstmProfileDefinitionError, type AstmProfileProvenance, type AstmQuirkMatch, type AstmQuirkTolerance, type AstmRecord, type AstmRecordInput, type AstmRecordWarning, AstmSerializeError, type AstmSerializeErrorCode, type AstmStreamMessage, AstmStrictError, CANONICAL_DELIMITERS, type CommentInput, type CommentRecord, type ComposeFramesOptions, type DecodeAstmFramesResult, type DefineAstmProfileOptions, type DelimiterDeclarationFault, type DelimiterReadResult, type Delimiters, type DetectFramingOptions, type DetectFramingResult, FATAL_CODES, FRAME_WARNING_CODES, type FatalCode, type FrameChecksum, type FrameOptions, type FrameTerminator, type FrameWarningCode, type FramedAstmResult, type HeaderInput, type HeaderRecord, LIVD_WARNING_CODES, LTP_WARNING_CODES, type LivdAnnotation, type LivdCatalog, type LivdEntry, type LivdLookup, type LivdMapping, type LivdResult, type LivdWarningCode, type LtpAction, type LtpEvent, type LtpPhase, type LtpState, type LtpTransition, type LtpWarningCode, type ManufacturerRecord, type MessageInput, type OrderInput, type OrderRecord, type PatientInput, type PatientName, type PatientNameInput, type PatientRecord, type QueryInput, type QueryRecord, type ReferenceRange, type ReferenceRangeKind, type ResultInput, type ResultRecord, type ResultStatus, type ResultStatusCode, type ResultStatusMeaning, SAFETY_CRITICAL_CODES, type ScientificRecord, type ShiftedFieldsSink, type SwallowedDelimiterSink, TOLERABLE_CODES, type TerminatorRecord, type TruncatedFieldSink, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnpairedEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, WARNING_CODES, type WarningCode, alignmentShiftedFields, alignmentTruncatedField, ambiguousEscapeAlignment, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimiterRoleCollision, delimiterSwallowedByEscape, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, hasCollidingRoles, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, messages, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiterDeclaration, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|