@smile-digital-health/crl 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +502 -0
- package/dist/ast/builder.d.ts +45 -0
- package/dist/ast/builder.js +389 -0
- package/dist/ast/builder.js.map +1 -0
- package/dist/ast/types.d.ts +223 -0
- package/dist/ast/types.js +73 -0
- package/dist/ast/types.js.map +1 -0
- package/dist/ast/utils.d.ts +14 -0
- package/dist/ast/utils.js +65 -0
- package/dist/ast/utils.js.map +1 -0
- package/dist/cli/run-ast.d.ts +1 -0
- package/dist/cli/run-ast.js +24 -0
- package/dist/cli/run-ast.js.map +1 -0
- package/dist/cli/run-lexer.d.ts +1 -0
- package/dist/cli/run-lexer.js +41 -0
- package/dist/cli/run-lexer.js.map +1 -0
- package/dist/cli/run-parser.d.ts +1 -0
- package/dist/cli/run-parser.js +33 -0
- package/dist/cli/run-parser.js.map +1 -0
- package/dist/cli/run-transformer-fsh-to-crl.d.ts +1 -0
- package/dist/cli/run-transformer-fsh-to-crl.js +20 -0
- package/dist/cli/run-transformer-fsh-to-crl.js.map +1 -0
- package/dist/cli/run-validator.d.ts +1 -0
- package/dist/cli/run-validator.js +37 -0
- package/dist/cli/run-validator.js.map +1 -0
- package/dist/grammar/activityTypes.d.ts +2 -0
- package/dist/grammar/activityTypes.js +9 -0
- package/dist/grammar/activityTypes.js.map +1 -0
- package/dist/grammar/conceptTypes.d.ts +2 -0
- package/dist/grammar/conceptTypes.js +9 -0
- package/dist/grammar/conceptTypes.js.map +1 -0
- package/dist/grammar/conceptValueTypes.d.ts +2 -0
- package/dist/grammar/conceptValueTypes.js +9 -0
- package/dist/grammar/conceptValueTypes.js.map +1 -0
- package/dist/grammar/generated/antlr/CRLLexer.d.ts +84 -0
- package/dist/grammar/generated/antlr/CRLLexer.js +505 -0
- package/dist/grammar/generated/antlr/CRLLexer.js.map +1 -0
- package/dist/grammar/generated/antlr/CRLParser.d.ts +653 -0
- package/dist/grammar/generated/antlr/CRLParser.js +3466 -0
- package/dist/grammar/generated/antlr/CRLParser.js.map +1 -0
- package/dist/grammar/generated/antlr/CRLParserListener.d.ts +162 -0
- package/dist/grammar/generated/antlr/CRLParserListener.js +3 -0
- package/dist/grammar/generated/antlr/CRLParserListener.js.map +1 -0
- package/dist/grammar/generated/antlr/CRLParserVisitor.d.ts +109 -0
- package/dist/grammar/generated/antlr/CRLParserVisitor.js +3 -0
- package/dist/grammar/generated/antlr/CRLParserVisitor.js.map +1 -0
- package/dist/grammar/generated/types/activityTypes.json +16 -0
- package/dist/grammar/generated/types/conceptTypes.json +26 -0
- package/dist/grammar/generated/types/conceptValueTypes.json +14 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +147 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/CRLLexerErrorListener.d.ts +16 -0
- package/dist/lexer/CRLLexerErrorListener.js +168 -0
- package/dist/lexer/CRLLexerErrorListener.js.map +1 -0
- package/dist/lexer/createLexer.d.ts +6 -0
- package/dist/lexer/createLexer.js +14 -0
- package/dist/lexer/createLexer.js.map +1 -0
- package/dist/lexer/index.d.ts +1 -0
- package/dist/lexer/index.js +6 -0
- package/dist/lexer/index.js.map +1 -0
- package/dist/parser/CustomParserErrorListener.d.ts +8 -0
- package/dist/parser/CustomParserErrorListener.js +37 -0
- package/dist/parser/CustomParserErrorListener.js.map +1 -0
- package/dist/parser/createParser.d.ts +7 -0
- package/dist/parser/createParser.js +17 -0
- package/dist/parser/createParser.js.map +1 -0
- package/dist/transformer/fsh-to-crl/mapping/activityDefinition.d.ts +41 -0
- package/dist/transformer/fsh-to-crl/mapping/activityDefinition.js +229 -0
- package/dist/transformer/fsh-to-crl/mapping/activityDefinition.js.map +1 -0
- package/dist/transformer/fsh-to-crl/mapping/concept.d.ts +1 -0
- package/dist/transformer/fsh-to-crl/mapping/concept.js +14 -0
- package/dist/transformer/fsh-to-crl/mapping/concept.js.map +1 -0
- package/dist/transformer/fsh-to-crl/mapping/planDefinition.d.ts +14 -0
- package/dist/transformer/fsh-to-crl/mapping/planDefinition.js +235 -0
- package/dist/transformer/fsh-to-crl/mapping/planDefinition.js.map +1 -0
- package/dist/transformer/fsh-to-crl/mapping/terminology.d.ts +1 -0
- package/dist/transformer/fsh-to-crl/mapping/terminology.js +10 -0
- package/dist/transformer/fsh-to-crl/mapping/terminology.js.map +1 -0
- package/dist/transformer/fsh-to-crl/transformer.d.ts +1 -0
- package/dist/transformer/fsh-to-crl/transformer.js +172 -0
- package/dist/transformer/fsh-to-crl/transformer.js.map +1 -0
- package/dist/transformer/fsh-to-crl/utils/activityDeduplication.d.ts +11 -0
- package/dist/transformer/fsh-to-crl/utils/activityDeduplication.js +23 -0
- package/dist/transformer/fsh-to-crl/utils/activityDeduplication.js.map +1 -0
- package/dist/transformer/fsh-to-crl/utils/fshPathFunctions.d.ts +12 -0
- package/dist/transformer/fsh-to-crl/utils/fshPathFunctions.js +73 -0
- package/dist/transformer/fsh-to-crl/utils/fshPathFunctions.js.map +1 -0
- package/dist/types/errors.d.ts +8 -0
- package/dist/types/errors.js +3 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/validator/actionUniquenessValidator.d.ts +20 -0
- package/dist/validator/actionUniquenessValidator.js +280 -0
- package/dist/validator/actionUniquenessValidator.js.map +1 -0
- package/dist/validator/cycleDetector.d.ts +13 -0
- package/dist/validator/cycleDetector.js +79 -0
- package/dist/validator/cycleDetector.js.map +1 -0
- package/dist/validator/nameUniquenessValidator.d.ts +5 -0
- package/dist/validator/nameUniquenessValidator.js +87 -0
- package/dist/validator/nameUniquenessValidator.js.map +1 -0
- package/dist/validator/unusedDeclarationsValidator.d.ts +21 -0
- package/dist/validator/unusedDeclarationsValidator.js +190 -0
- package/dist/validator/unusedDeclarationsValidator.js.map +1 -0
- package/dist/validator/validator.d.ts +27 -0
- package/dist/validator/validator.js +30 -0
- package/dist/validator/validator.js.map +1 -0
- package/package.json +92 -0
package/README.md
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# @smile-digital-health/crl
|
|
2
|
+
|
|
3
|
+
Clinical Reasoning Language (CRL) parser and validator
|
|
4
|
+
|
|
5
|
+
Clinical Reasoning Language (CRL) provides a structured way to model clinical decision logic, concept derivations, clinical activities, measurements, and summaries — while remaining simple, readable, and guideline-compatible.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
See the [User Guide](./USER_GUIDE.md) for a comprehensive introduction to the CRL language, syntax, and authoring best practices.
|
|
10
|
+
|
|
11
|
+
CRL is a domain-specific language designed for expressing clinical practice guidelines in a structured and machine-readable format. The language is implemented in TypeScript and provides a comprehensive set of tools for processing CRL documents.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
This is a private package. To install it, you need:
|
|
16
|
+
|
|
17
|
+
1. An npm account with access to `@smile-digital-health` packages
|
|
18
|
+
- Contact your team lead to request access
|
|
19
|
+
- You will receive an invitation to join the organization
|
|
20
|
+
- Each developer should use their own npm account
|
|
21
|
+
|
|
22
|
+
2. Set up authentication:
|
|
23
|
+
```bash
|
|
24
|
+
# Log in to npm - recommended for individual developers
|
|
25
|
+
npm login
|
|
26
|
+
|
|
27
|
+
# Or create/edit ~/.npmrc with your personal access token
|
|
28
|
+
//registry.npmjs.org/:_authToken=YOUR_NPM_TOKEN
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Security Note:** Always use your personal npm token. Do not share tokens between team members.
|
|
32
|
+
|
|
33
|
+
Once authenticated, install the package:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @smile-digital-health/crl
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or add it to your `package.json`:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@smile-digital-health/crl": "^0.6.1"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### For Package Maintainers
|
|
48
|
+
|
|
49
|
+
#### Publishing to npm
|
|
50
|
+
|
|
51
|
+
The package is automatically published to npm when a new GitHub release is created (non-draft, non-prerelease). To verify what will be published:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# See what files would be included in the package
|
|
55
|
+
npm publish --dry-run
|
|
56
|
+
|
|
57
|
+
# Create the package locally without publishing
|
|
58
|
+
npm pack
|
|
59
|
+
|
|
60
|
+
# Examine contents of the generated .tgz file
|
|
61
|
+
tar -ztvf <package-name>.tgz
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
66
|
+
### Language Features
|
|
67
|
+
|
|
68
|
+
* Decision blocks with nested conditions
|
|
69
|
+
* Concept definitions with type and value specifications
|
|
70
|
+
* Activity statements with perform types
|
|
71
|
+
* Terminology statements with valueset, system/code, and unknown definitions
|
|
72
|
+
* FHIR resource type support
|
|
73
|
+
* String literals with proper escaping
|
|
74
|
+
* Comments (single-line and block)
|
|
75
|
+
|
|
76
|
+
### Processing Features
|
|
77
|
+
|
|
78
|
+
* Lexical analysis with detailed token information
|
|
79
|
+
* Syntax parsing with error recovery
|
|
80
|
+
* AST generation with type safety
|
|
81
|
+
* Semantic validation with comprehensive error reporting
|
|
82
|
+
* Cross-platform compatibility (Windows, Mac, Linux)
|
|
83
|
+
|
|
84
|
+
### Development Features
|
|
85
|
+
|
|
86
|
+
* TypeScript implementation for type safety
|
|
87
|
+
* Comprehensive test suite
|
|
88
|
+
* Detailed documentation
|
|
89
|
+
* Example implementations
|
|
90
|
+
* Development tools and utilities
|
|
91
|
+
|
|
92
|
+
## CLI Usage
|
|
93
|
+
|
|
94
|
+
The CRL package includes command-line tools for processing CRL files. Each tool can be run with the `--raw` flag to output raw JSON data instead of formatted output.
|
|
95
|
+
|
|
96
|
+
### Lexer Tool
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx ts-node src/cli/run-lexer.ts
|
|
100
|
+
npx ts-node src/cli/run-lexer.ts --raw
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Parser Tool
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx ts-node src/cli/run-parser.ts
|
|
107
|
+
npx ts-node src/cli/run-parser.ts --raw
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### AST Tool
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx ts-node src/cli/run-ast.ts
|
|
114
|
+
npx ts-node src/cli/run-ast.ts --raw
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Validator Tool
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npx ts-node src/cli/run-validator.ts
|
|
121
|
+
npx ts-node src/cli/run-validator.ts --raw
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### FSH-to-CRL Transformer Tool
|
|
125
|
+
|
|
126
|
+
The FSH-to-CRL transformer converts FHIR Shorthand (FSH) files into Clinical Reasoning Language (CRL) files. It supports advanced mapping and deduplication logic for activities, concepts, and terminology blocks.
|
|
127
|
+
|
|
128
|
+
#### Activity Mapping Enhancements
|
|
129
|
+
|
|
130
|
+
- **Conditional `do not perform`**: If an activity in FSH has `doNotPerform = true`, the generated CRL will emit `do not perform` instead of `perform` for that activity.
|
|
131
|
+
- **Activity Terminology Block Emission**: For activities with a `medicationCodeableConcept`, the terminology block uses the `system` and `code` properties directly from the FSH object. For activities using `dynamicValue.expression.expression` with `path = "code.coding"`, the code and system are extracted from the CQL code expression.
|
|
132
|
+
- **Deduplication and Suffixing**: Terminology blocks are unique by identifier and body. If a duplicate identifier is encountered with a different body, a numeric suffix (e.g., `_2`) is added to the identifier. If both identifier and body are the same, the block is not duplicated.
|
|
133
|
+
- **Extraction Logic**: For `medicationCodeableConcept`, the transformer uses `system`, `code`, and `identifier` from the FSH object. For `dynamicValue.expression.expression` (where `path = "code.coding"`), the transformer uses `system` and `code` from the CQL code expression string and `identifier` from the corresponding description.
|
|
134
|
+
|
|
135
|
+
For more details, see the [User Guide](./USER_GUIDE.md) and the technical mapping documentation.
|
|
136
|
+
|
|
137
|
+
## API Usage & Reference
|
|
138
|
+
|
|
139
|
+
**All API functions return a `ParseResult` object. If there are any lexical or syntax errors, these are collected and returned in the `errors` array (not just printed to the console). You should always check `result.success` and handle errors accordingly.**
|
|
140
|
+
|
|
141
|
+
The package provides four main functions for processing CRL code:
|
|
142
|
+
|
|
143
|
+
### 1. Tokenization
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { tokenizeCRL } from '@smile-digital-health/crl';
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
const result = tokenizeCRL(`
|
|
150
|
+
decision "Test":
|
|
151
|
+
when "Condition" then do "Action".
|
|
152
|
+
done
|
|
153
|
+
`);
|
|
154
|
+
|
|
155
|
+
if (result.success) {
|
|
156
|
+
// Access the tokens
|
|
157
|
+
console.log(result.result);
|
|
158
|
+
} else {
|
|
159
|
+
// Handle errors
|
|
160
|
+
console.error(result.errors);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 2. Parsing
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { parseCRL } from '@smile-digital-health/crl';
|
|
168
|
+
|
|
169
|
+
const result = parseCRL(`
|
|
170
|
+
decision "Test":
|
|
171
|
+
when "Condition" then do "Action".
|
|
172
|
+
done
|
|
173
|
+
`);
|
|
174
|
+
|
|
175
|
+
if (result.success) {
|
|
176
|
+
// Access the parse tree
|
|
177
|
+
console.log(result.result);
|
|
178
|
+
} else {
|
|
179
|
+
// Handle errors
|
|
180
|
+
console.error(result.errors);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 3. AST Building
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { buildCRL } from '@smile-digital-health/crl';
|
|
188
|
+
|
|
189
|
+
const result = buildCRL(`
|
|
190
|
+
decision "Test":
|
|
191
|
+
when "Condition" then do "Action".
|
|
192
|
+
done
|
|
193
|
+
`);
|
|
194
|
+
|
|
195
|
+
if (result.success) {
|
|
196
|
+
// Access the AST
|
|
197
|
+
console.log(result.result);
|
|
198
|
+
} else {
|
|
199
|
+
// Handle errors
|
|
200
|
+
console.error(result.errors);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 4. Validation
|
|
205
|
+
|
|
206
|
+
> ⚠️ **Note:** Validation functionality is not yet implemented. The `validateCRL` function is a placeholder and will likely throw or return an error if used.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { validateCRL } from '@smile-digital-health/crl';
|
|
210
|
+
|
|
211
|
+
const result = validateCRL(`
|
|
212
|
+
decision "Test":
|
|
213
|
+
when "Condition" then do "Action".
|
|
214
|
+
done
|
|
215
|
+
`);
|
|
216
|
+
|
|
217
|
+
if (result.success) {
|
|
218
|
+
// Access the validated AST
|
|
219
|
+
console.log(result.result);
|
|
220
|
+
} else {
|
|
221
|
+
// Handle validation errors
|
|
222
|
+
console.error(result.errors);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Core Functions & Types
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
interface Token {
|
|
230
|
+
line: number;
|
|
231
|
+
column: number;
|
|
232
|
+
type: string;
|
|
233
|
+
text: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface ParseResult<T> {
|
|
237
|
+
success: boolean;
|
|
238
|
+
result?: T;
|
|
239
|
+
errors?: string[];
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Examples
|
|
244
|
+
|
|
245
|
+
### Example AST Comparison
|
|
246
|
+
|
|
247
|
+
To compare the generated AST with the expected AST, you can run:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
npx ts-node --log-error src/examples/compare-ast.ts
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
This will:
|
|
254
|
+
|
|
255
|
+
1. Parse the example CRL file (`docs/Measles Immunization Decision.crl`)
|
|
256
|
+
2. Generate an AST from the parsed input
|
|
257
|
+
3. Compare it with the expected AST (`docs/Expected AST.ast`)
|
|
258
|
+
4. Display any differences between the two ASTs
|
|
259
|
+
|
|
260
|
+
The comparison includes:
|
|
261
|
+
|
|
262
|
+
* Line count matching
|
|
263
|
+
* Whitespace-normalized matching
|
|
264
|
+
* Structure matching
|
|
265
|
+
* Detailed line-by-line comparison of differences
|
|
266
|
+
|
|
267
|
+
### FSH-to-CRL Example Data
|
|
268
|
+
|
|
269
|
+
The example FSH files and CRL outputs in `src/examples/fsh/who/smart-example-immz/` and `src/examples/crl/who/smart-example-immz/` are derived from the [WHO SMART Guidelines - Example IG for Measles Immunization](https://github.com/WorldHealthOrganization/smart-example-immz).
|
|
270
|
+
|
|
271
|
+
- **Source repository:** [WorldHealthOrganization/smart-example-immz](https://github.com/WorldHealthOrganization/smart-example-immz)
|
|
272
|
+
- **License:** [CC BY-IGO 3.0](https://github.com/WorldHealthOrganization/smart-example-immz/blob/main/LICENSE.md)
|
|
273
|
+
|
|
274
|
+
These examples are used for development, testing, and demonstration of the transformer.
|
|
275
|
+
|
|
276
|
+
## Updating Grammar & Generated Files
|
|
277
|
+
|
|
278
|
+
### Updating grammar files
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npm run generate
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
This will extract all grammar-driven types and regenerate the lexer and parser.
|
|
285
|
+
|
|
286
|
+
### Grammar-Driven Types
|
|
287
|
+
|
|
288
|
+
* The lists of valid activity types, concept types, and concept value types are defined in the `validTypes` arrays in the `ACTIVITY_TYPE`, `CONCEPT_TYPE`, and `CONCEPT_VALUE_TYPE` rules of `src/grammar/CRLLexer.g4`.
|
|
289
|
+
* Scripts (`scripts/extractActivityTypes.js`, `scripts/extractConceptTypes.js`, `scripts/extractConceptValueTypes.js`) automatically extract these lists and write them to `src/grammar/activityTypes.json`, `src/grammar/conceptTypes.json`, and `src/grammar/conceptValueTypes.json`.
|
|
290
|
+
* TypeScript modules (`src/grammar/activityTypes.ts`, `src/grammar/conceptTypes.ts`, `src/grammar/conceptValueTypes.ts`) import these JSON files and export both the arrays and type-safe union types.
|
|
291
|
+
* **All code (lexer, AST, error listener, etc.) should import from these modules to avoid drift.**
|
|
292
|
+
|
|
293
|
+
### ANTLR-Generated Files
|
|
294
|
+
|
|
295
|
+
The following files are generated by ANTLR and should not be edited manually or tracked in git:
|
|
296
|
+
|
|
297
|
+
* `src/grammar/generated/antlr/CRLLexer.ts`
|
|
298
|
+
* `src/grammar/generated/antlr/CRLParser.ts`
|
|
299
|
+
* `src/grammar/generated/antlr/CRLParserVisitor.ts`
|
|
300
|
+
* `src/grammar/generated/antlr/CRLParserListener.ts`
|
|
301
|
+
* `src/grammar/generated/antlr/CRLLexer.tokens`
|
|
302
|
+
* `src/grammar/generated/antlr/CRLLexer.interp`
|
|
303
|
+
* `src/grammar/generated/antlr/CRLParser.tokens`
|
|
304
|
+
* `src/grammar/generated/antlr/CRLParser.interp`
|
|
305
|
+
|
|
306
|
+
To regenerate these files, run:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
npm run generate
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
These files are ignored by git via `.gitignore` and will be recreated as needed.
|
|
313
|
+
|
|
314
|
+
## Development
|
|
315
|
+
|
|
316
|
+
### Building
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
npm run build
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Test Documentation
|
|
323
|
+
|
|
324
|
+
The project includes a comprehensive test suite to ensure the correctness of the lexer and parser. Here's how to run and work with the tests:
|
|
325
|
+
|
|
326
|
+
#### Running Tests
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# Run all tests
|
|
330
|
+
npm test
|
|
331
|
+
|
|
332
|
+
# Run tests with coverage
|
|
333
|
+
npm run test:coverage
|
|
334
|
+
|
|
335
|
+
# Run specific test files
|
|
336
|
+
npx jest src/ast/tests/decision-structure.test.ts --verbose --no-cache --colors --forceExit --detectOpenHandles --watchAll=false
|
|
337
|
+
|
|
338
|
+
# Run tests in watch mode (useful during development)
|
|
339
|
+
npm run test:watch
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
#### Test Categories
|
|
343
|
+
|
|
344
|
+
The test suite is organized into several categories:
|
|
345
|
+
|
|
346
|
+
1. **Basic Tokens** (`basic-tokens.test.ts`)
|
|
347
|
+
* Tests for fundamental language tokens
|
|
348
|
+
* Keywords, punctuation, and basic syntax
|
|
349
|
+
|
|
350
|
+
2. **FHIR Types** (`fhir-types.test.ts`)
|
|
351
|
+
* Tests for FHIR-specific activity types
|
|
352
|
+
* Validation of CPG-prefixed activity types
|
|
353
|
+
* Error handling for invalid types
|
|
354
|
+
|
|
355
|
+
3. **Grammar Example** (`grammar-example.test.ts`)
|
|
356
|
+
* Tests the lexer against the complete grammar example
|
|
357
|
+
* Validates real-world usage scenarios
|
|
358
|
+
|
|
359
|
+
4. **Error Handling** (`error-handling.test.ts`)
|
|
360
|
+
* Tests for invalid input handling
|
|
361
|
+
* Error message validation
|
|
362
|
+
* Recovery from syntax errors
|
|
363
|
+
|
|
364
|
+
5. **Comments** (`comments.test.ts`)
|
|
365
|
+
* Tests for comment handling
|
|
366
|
+
* Single-line and block comments
|
|
367
|
+
* Comment placement and nesting
|
|
368
|
+
|
|
369
|
+
#### Writing Tests
|
|
370
|
+
|
|
371
|
+
When adding new tests:
|
|
372
|
+
|
|
373
|
+
1. Place test files in the appropriate directory, for example `src/lexer/tests/`
|
|
374
|
+
2. Follow the existing test patterns
|
|
375
|
+
3. Include both positive and negative test cases
|
|
376
|
+
4. Use descriptive test names
|
|
377
|
+
5. Add comments explaining complex test scenarios
|
|
378
|
+
|
|
379
|
+
Example test structure:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
describe('Feature Name', () => {
|
|
383
|
+
it('should handle valid input', () => {
|
|
384
|
+
// Test valid cases
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('should handle invalid input', () => {
|
|
388
|
+
// Test error cases
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
#### Test Utilities
|
|
394
|
+
|
|
395
|
+
The test suite includes several utility functions:
|
|
396
|
+
|
|
397
|
+
* `getAllTokens`: Retrieves all tokens from a lexer
|
|
398
|
+
* `verifyTokenSequence`: Validates token sequences
|
|
399
|
+
* `getActionTokenSequence`: Helper for activity type tests
|
|
400
|
+
* `getCaseFeatureTokenSequence`: Helper for concept type tests
|
|
401
|
+
* `getValueTypeTokenSequence`: Helper for value type tests
|
|
402
|
+
|
|
403
|
+
### Linting
|
|
404
|
+
|
|
405
|
+
The project uses ESLint for code quality checks. To run the linter:
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
# Lint all TypeScript files
|
|
409
|
+
npx eslint . --ext .ts
|
|
410
|
+
|
|
411
|
+
# Lint and automatically fix issues where possible
|
|
412
|
+
npx eslint . --ext .ts --fix
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Running Examples
|
|
416
|
+
|
|
417
|
+
The project includes several example files that demonstrate different features and validation scenarios:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
# Run the basic usage example
|
|
421
|
+
npm run example
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Regenerating ANTLR Files
|
|
425
|
+
|
|
426
|
+
If you modify the grammar in `src/grammar/CRL.g4`, you'll need to regenerate the lexer and parser:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
cd src/grammar
|
|
430
|
+
antlr4ts -Xforce-atn -o src/grammar/generated src/grammar/CRLLexer.g4 && antlr4ts -Xforce-atn -o src/grammar/generated src/grammar/CRLParser.g4
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Note: This project uses a custom AST implementation that uses ANTLR's visitor pattern. The generated files are used only for lexing and parsing directly, while semantic analysis and interpretation are handled by our custom AST implementation.
|
|
434
|
+
|
|
435
|
+
## 📦 Release Process & Checklist
|
|
436
|
+
|
|
437
|
+
> **Release Process:**
|
|
438
|
+
>
|
|
439
|
+
> The authoritative release checklist and instructions are maintained in a single location:
|
|
440
|
+
>
|
|
441
|
+
> **[.github/PULL_REQUEST_TEMPLATE/release.md](.github/PULL_REQUEST_TEMPLATE/release.md)**
|
|
442
|
+
>
|
|
443
|
+
> Always follow the steps in this file when preparing a new release. This ensures consistency and reduces maintenance overhead. If you need to update the release process, update the PR template only.
|
|
444
|
+
|
|
445
|
+
## Developer Notes & Error Handling
|
|
446
|
+
|
|
447
|
+
### Developer Notes: Release Checklist & GitHub Automation
|
|
448
|
+
|
|
449
|
+
- **Pull Request Templates:** The project uses GitHub's PR template system to standardize release PRs and ensure all required steps are followed.
|
|
450
|
+
- **Release Automation:** The release process is automated via scripts and GitHub Actions. Always ensure your working directory is clean and up-to-date before running release scripts.
|
|
451
|
+
- **CI/CD:** All tests and builds are run in GitHub Actions before a release is published. If any step fails, the release will be blocked.
|
|
452
|
+
- **Generated Files:** Auto-generated files (e.g., ANTLR outputs, grammar-driven types) are ignored by git and should never be edited manually. Always use the provided scripts to regenerate them.
|
|
453
|
+
- **Documentation:** Keep all documentation referencing the release checklist up-to-date by linking to the PR template.
|
|
454
|
+
|
|
455
|
+
For more details, see the [release PR template](.github/PULL_REQUEST_TEMPLATE/release.md) and the automation scripts in `.github/scripts/`.
|
|
456
|
+
|
|
457
|
+
### Error Handling Strategy
|
|
458
|
+
|
|
459
|
+
This project implements robust, layered error handling across all stages of the CRL pipeline:
|
|
460
|
+
|
|
461
|
+
| Layer | Responsibility | Error Type | How Errors Are Reported |
|
|
462
|
+
|-----------|-------------------------------------------------|--------------------|----------------------------------------|
|
|
463
|
+
| Lexer | Tokenization, valid characters/tokens | LexicalError | Collected by error listener, JSON |
|
|
464
|
+
| Parser | Syntax, structure, required/optional elements | ParserError | Collected by error listener, JSON |
|
|
465
|
+
| AST | Parse tree → AST, structural integrity | AstError | Collected by AST builder, JSON |
|
|
466
|
+
| Validator | Domain/semantic rules, cross-references, logic | ValidationError | Collected by validator, plain text |
|
|
467
|
+
|
|
468
|
+
- **Lexical and parser errors** are collected by their respective error listeners and reported as JSON strings with a `type` property (e.g., `"LexicalError"`, `"ParserError"`).
|
|
469
|
+
- **AST errors** are reported as `"AstError"` if the AST builder encounters a structural problem not caught by the parser.
|
|
470
|
+
- **Validation errors** are reported by the validator after AST construction.
|
|
471
|
+
|
|
472
|
+
#### Example Error Object
|
|
473
|
+
|
|
474
|
+
```json
|
|
475
|
+
{
|
|
476
|
+
"type": "ParserError",
|
|
477
|
+
"line": 1,
|
|
478
|
+
"column": 51,
|
|
479
|
+
"message": "Syntax error: missing 'done' at '<EOF>'",
|
|
480
|
+
"details": {
|
|
481
|
+
"offendingSymbol": { "text": "<EOF>", ... }
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
#### Clean Test Output: Suppressing Console Errors in Jest
|
|
487
|
+
|
|
488
|
+
To keep test output clean and focused on actual test failures, all `console.error` output is globally suppressed during tests. This is achieved by adding the following to `jest.setup.js`:
|
|
489
|
+
|
|
490
|
+
```js
|
|
491
|
+
beforeAll(() => {
|
|
492
|
+
jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
And registering it in `jest.config.js`:
|
|
497
|
+
|
|
498
|
+
```js
|
|
499
|
+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
This ensures that error logs are visible during development and CLI runs, but do not clutter test output.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ParserRuleContext } from "antlr4ts/ParserRuleContext";
|
|
2
|
+
import { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor";
|
|
3
|
+
import { CrlContext, DecisionStatementContext, DecisionBodyContext, WhenWithBodyContext, WhenSingleActionContext, NestedWhenBlockContext, BlockActionContext, BlockBodyContext, SingleActionStatementContext, DoStatementContext, UseStatementContext, TerminologyStatementContext, TerminologyValuesetContext, TerminologySystemCodeContext, ActivityStatementContext, ConceptStatementContext, InferredFromLineContext, DefinitionConceptContext, DefinitionLogicContext, InferredFromExpressionContext, InformalOrContext, InformalAndContext, InformalNotContext, ConceptAtomContext, GroupExpressionContext } from "../grammar/generated/antlr/CRLParser";
|
|
4
|
+
import { CRLParserVisitor } from "../grammar/generated/antlr/CRLParserVisitor";
|
|
5
|
+
import { CRLError } from "../types/errors";
|
|
6
|
+
import { ASTNode, CRL, Decision, DecisionBody, WhenBlock, BlockBody, SingleAction, ActionStatement, DoActivity, UseDecision, Terminology, TerminologyValueset, TerminologySystemCode, Activity, Concept, InferredFromDefinition, ConceptReference, InformalAnd, InformalOr, NotExpression, GroupExpression, InferredFromConcept, InferredFromExpression } from "./types";
|
|
7
|
+
type InformalNode = GroupExpression | ConceptReference | InformalAnd | NotExpression | InformalOr;
|
|
8
|
+
export declare class CRLAstBuilder extends AbstractParseTreeVisitor<ASTNode> implements CRLParserVisitor<ASTNode> {
|
|
9
|
+
private readonly errors;
|
|
10
|
+
protected defaultResult(): ASTNode;
|
|
11
|
+
getErrors(): CRLError[];
|
|
12
|
+
protected reportError(message: string, ctx: ParserRuleContext, details?: Record<string, unknown>): void;
|
|
13
|
+
visitCrl(ctx: CrlContext): CRL;
|
|
14
|
+
visitDecisionStatement(ctx: DecisionStatementContext): Decision;
|
|
15
|
+
visitDecisionBody(ctx: DecisionBodyContext): DecisionBody;
|
|
16
|
+
visitWhenWithBody(ctx: WhenWithBodyContext): WhenBlock;
|
|
17
|
+
visitWhenSingleAction(ctx: WhenSingleActionContext): WhenBlock;
|
|
18
|
+
visitNestedWhenBlock(ctx: NestedWhenBlockContext): WhenBlock;
|
|
19
|
+
visitBlockAction(ctx: BlockActionContext): ActionStatement;
|
|
20
|
+
visitBlockBody(ctx: BlockBodyContext): BlockBody;
|
|
21
|
+
visitSingleActionStatement(ctx: SingleActionStatementContext): SingleAction;
|
|
22
|
+
visitActionStatement(ctx: import("../grammar/generated/antlr/CRLParser").ActionStatementContext): ActionStatement;
|
|
23
|
+
visitDoStatement(ctx: DoStatementContext): DoActivity;
|
|
24
|
+
visitUseStatement(ctx: UseStatementContext): UseDecision;
|
|
25
|
+
visitTerminologyStatement(ctx: TerminologyStatementContext): Terminology;
|
|
26
|
+
visitTerminologyValueset(ctx: TerminologyValuesetContext): TerminologyValueset;
|
|
27
|
+
visitTerminologySystemCode(ctx: TerminologySystemCodeContext): TerminologySystemCode;
|
|
28
|
+
private parseWithClause;
|
|
29
|
+
private parseRationaleClause;
|
|
30
|
+
visitActivityStatement(ctx: ActivityStatementContext): Activity;
|
|
31
|
+
private parseConceptTypes;
|
|
32
|
+
private parseEvidence;
|
|
33
|
+
private parseConceptDefinition;
|
|
34
|
+
visitConceptStatement(ctx: ConceptStatementContext): Concept;
|
|
35
|
+
visitInferredFromLine(ctx: InferredFromLineContext): InferredFromDefinition;
|
|
36
|
+
visitDefinitionConcept(ctx: DefinitionConceptContext): InferredFromConcept;
|
|
37
|
+
visitDefinitionLogic(ctx: DefinitionLogicContext): GroupExpression;
|
|
38
|
+
visitInferredFromExpression(ctx: InferredFromExpressionContext): InformalNode;
|
|
39
|
+
visitInformalOr(ctx: InformalOrContext): InformalOr;
|
|
40
|
+
visitInformalAnd(ctx: InformalAndContext): InformalAnd | NotExpression | GroupExpression | ConceptReference;
|
|
41
|
+
visitInformalNot(ctx: InformalNotContext): InferredFromExpression;
|
|
42
|
+
visitConceptAtom(ctx: ConceptAtomContext): ConceptReference;
|
|
43
|
+
visitGroupExpression(ctx: GroupExpressionContext): GroupExpression;
|
|
44
|
+
}
|
|
45
|
+
export {};
|