src/parseBundle.js
import assert from 'node:assert';
import * as tape from '@async-abstraction/tape';
import {asyncIterableToArray} from '@async-iterable-iterator/async-iterable-to-array';
import {ll1, ast} from '@formal-language/grammar';
import tokens from './tokens.js';
import grammar from './grammar.js';
import simplify from './transform/simplify.js';
import {iter, next, map as asyncMap} from './transform/lib.js';
const parseTape = (inputTape) => {
const parser = ll1.from(grammar);
const inputTokens = tokens(inputTape);
const inputTokensTape = tape.fromAsyncIterable(inputTokens);
const tree = parser.parse(inputTokensTape);
const ctx = {};
return ast.transform(tree, simplify, ctx);
};
const parseString = (string) => {
const inputCharacterTape = tape.fromString(string);
return parseTape(inputCharacterTape);
};
const parseBlockBegin = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === '#R');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return {
...tree,
parsed: {
type: line.slice(2).trim(),
},
};
};
const parseBlockTitle = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'R-title');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return {
...tree,
parsed: {
title: line.trim(),
},
};
};
const parseBlockType = (type) => {
switch (type) {
case 'd': {
return {TimeIndication: 'Days'};
}
case 'h': {
return {TimeIndication: 'Hours'};
}
case 'm': {
return {TimeIndication: 'Minutes'};
}
case 's': {
return {TimeIndication: 'Seconds'};
}
default: {
assert(type === 'a');
return {};
}
}
};
const parseIntensity = (symbol) => {
switch (symbol) {
case '--':
case 'LL':
case '1': {
return 'GreatlyReduced';
}
case '-':
case 'L':
case '2': {
return 'Reduced';
}
case '=':
case 'N':
case '3':
case '': {
return 'Normal';
}
case '+':
case 'H':
case '4': {
return 'Increased';
}
case '++':
case 'HH':
case '5': {
return 'GreatlyIncreased';
}
default: {
return 'Unknown';
}
}
};
const parseBlockComments = (comments) => {
if (comments.length > 0 && comments[0].slice(0, 1) === '\\') {
return {
referenceValue: comments[0].slice(1),
comments: comments.slice(1),
};
}
return {
comments,
};
};
const parseBlockBody = (type, tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'R-body');
if (type === 'c') {
return {
...tree,
parsed: {
comments: tree.lines.map(({contents}) => contents),
},
};
}
if (type === 'b') {
return {
...tree,
parsed: {
text: tree.lines.map(({contents}) => contents),
},
};
}
assert(tree.lines.length >= 3);
const [line1, line2, line3, ...rest] = tree.lines.map(
({contents}) => contents,
);
const intensitySymbol = line3.trim();
return {
...tree,
parsed: {
...parseBlockType(type),
relation: line1.slice(0, 1),
value: line1.slice(1).trim(),
unit: line2.trim(),
intensity: parseIntensity(intensitySymbol),
intensitySymbol,
...parseBlockComments(rest),
},
};
};
const parseBlock = async (block) => {
assert(block.type === 'node');
assert(block.nonterminal === 'R');
const it = iter(block.children);
const begin = parseBlockBegin(await next(it));
const title = parseBlockTitle(await next(it));
const contents = parseBlockBody(begin.parsed.type, await next(it));
const end = await next(it);
return {
begin,
title,
contents,
end,
};
};
const parseSex = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'A-sex');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
const sex = line === 'X' ? 'female' : line === 'Y' ? 'male' : 'other';
return {
...tree,
parsed: {
sex,
},
};
};
const parseReference = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'A-reference');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return {
...tree,
parsed: {
reference: line.trim(),
},
};
};
const parseProtocolCode = (code) => {
switch (code) {
case 'P':
case 'S':
case 'L': {
return 'partial';
}
case 'C': {
return 'complete';
}
default: {
return '';
}
}
};
const parseCode = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'A-code');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
const code = line.trim();
const status = parseProtocolCode(code);
return status === ''
? {
...tree,
parsed: {
code,
},
}
: {
...tree,
parsed: {
code,
status,
},
};
};
const parseReportIdentifier = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === '#A');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
const identifier = line.slice(2);
return identifier.length === 11
? {
...tree,
parsed: {
nn: identifier,
},
}
: identifier.length === 13
? {
...tree,
parsed: {
dossier: identifier,
},
}
: {
...tree,
parsed: {
identifier,
},
};
};
const parseReportFooter = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === '#A/');
assert(tree.lines.length === 1);
return tree;
};
const parseReport = async (report) => {
assert(report.type === 'node');
assert(report.nonterminal === 'A');
const it = iter(report.children);
const identifier = parseReportIdentifier(await next(it));
const name = parseName(await next(it));
const birthdate = parseDate(await next(it));
const sex = parseSex(await next(it));
const requestDate = parseDate(await next(it));
const reference = parseReference(await next(it));
const code = parseCode(await next(it));
const extra = parseExtra(await next(it));
const header = {
identifier,
name,
birthdate,
sex,
requestDate,
reference,
code,
extra,
};
const blocks = await next(it);
const parsedBlocks = await asyncIterableToArray(
asyncMap(parseBlock, blocks.children),
);
const footer = parseReportFooter(await next(it));
return {
header,
blocks: parsedBlocks,
footer,
};
};
const parseNIHDI = (tree) => {
assert(tree.type === 'leaf');
assert(
tree.terminal === 'doctor-nihdi' || tree.terminal === 'requestor-nihdi',
);
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return {
...tree,
parsed: {
nihdi: line.replaceAll('/', ''),
},
};
};
const parseName = (tree) => {
assert(tree.type === 'leaf');
assert(
tree.terminal === 'doctor-name' ||
tree.terminal === 'requestor-name' ||
tree.terminal === 'A-name',
);
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return {
...tree,
parsed: {
firstname: line.slice(24).trim(),
lastname: line.slice(0, 24).trim(),
},
};
};
const parseDoctorAddress = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'doctor-address');
assert(tree.lines.length === 2);
const [line1, line2] = tree.lines.map(({contents}) => contents);
return {
...tree,
parsed: {
streetName: line1.slice(0, 35).trim(),
streetNumber: line1.slice(35).trim(),
postalCode: line2.slice(0, 10).trim(),
townName: line2.slice(10).trim(),
},
};
};
const parseLabAddress = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'lab-address');
assert(tree.lines.length === 2);
const [address1, address2] = tree.lines.map(({contents}) => contents);
return {
...tree,
parsed: {
address1,
address2,
},
};
};
const parsePhone = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'doctor-phone');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return {
...tree,
parsed: {
phone: line.trim(),
},
};
};
const parseExtra = (tree) => tree;
const parseLabIdentifier = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'lab-identifier');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return {
...tree,
parsed: {
identifier: line.trim(),
},
};
};
const parseLabName = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'lab-name');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return {
...tree,
parsed: {
name: line.trim(),
},
};
};
const parseLab = async (tree) => {
assert(tree.type === 'node');
assert(tree.nonterminal === 'lab');
const it = iter(tree.children);
const identifier = parseLabIdentifier(await next(it));
const name = parseLabName(await next(it));
const address = parseLabAddress(await next(it));
const extra = parseExtra(await next(it));
return {
type: 'leaf',
terminal: 'lab',
identifier,
name,
address,
extra,
};
};
const parseDoctor = async (tree) => {
assert(tree.type === 'node');
assert(tree.nonterminal === 'doctor');
const it = iter(tree.children);
const nihdi = parseNIHDI(await next(it));
const name = parseName(await next(it));
const address = parseDoctorAddress(await next(it));
const phone = parsePhone(await next(it));
const extra = parseExtra(await next(it));
return {
type: 'leaf',
terminal: 'doctor',
nihdi,
name,
address,
phone,
extra,
};
};
const parseDatetime = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'date');
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return line.length >= 9
? {
...tree,
parsed: {
date: line.slice(0, 8),
time: line.slice(8),
},
}
: parseDate(tree);
};
const parseDate = (tree) => {
assert(tree.type === 'leaf');
assert(
tree.terminal === 'date' ||
tree.terminal === 'A-birthdate' ||
tree.terminal === 'A-date',
);
assert(tree.lines.length === 1);
const line = tree.lines[0].contents;
return {
...tree,
parsed: {
date: line,
},
};
};
const parseRequestor = async (tree) => {
assert(tree.type === 'node');
assert(tree.nonterminal === 'requestor');
const it = iter(tree.children);
const nihdi = parseNIHDI(await next(it));
const name = parseName(await next(it));
return {
type: 'leaf',
terminal: 'requestor',
nihdi,
name,
};
};
const parseDocumentFooter = (tree) => {
assert(tree.type === 'leaf');
assert(tree.terminal === 'footer');
assert(tree.lines.length === 1);
return tree;
};
const parseDocument = async (document) => {
assert(document.type === 'node');
assert(document.nonterminal === 'document');
const kind = document.production;
const it = iter(document.children);
const requesteeKind = kind === 'lab' ? 'lab' : 'doctor';
const parseRequestee = kind === 'lab' ? parseLab : parseDoctor;
const requestee = await parseRequestee(await next(it));
const date = parseDatetime(await next(it));
const requestor = await parseRequestor(await next(it));
const header = {
kind,
[requesteeKind]: requestee,
date,
requestor,
};
const reports = await next(it);
const parsedReports = await asyncIterableToArray(
asyncMap(parseReport, reports.children),
);
const footer = parseDocumentFooter(await next(it));
return {
header,
reports: parsedReports,
footer,
};
};
const parseTree = (tree) => {
assert(tree.type === 'node');
assert(tree.nonterminal === 'documents');
return asyncMap(parseDocument, tree.children);
};
const parseBundle = async (string) => {
const root = await parseString(string);
const tree = await next(iter(root.children));
return parseTree(tree);
};
export default parseBundle;