fhirpath-ts 0.0.0-bootstrap.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.
Files changed (414) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +391 -0
  3. package/THIRD-PARTY-NOTICES.md +296 -0
  4. package/dist/analyzer/analyze-dto.d.ts +45 -0
  5. package/dist/analyzer/analyze-dto.d.ts.map +1 -0
  6. package/dist/analyzer/analyze-dto.js +147 -0
  7. package/dist/analyzer/analyze-dto.js.map +1 -0
  8. package/dist/analyzer/analyze.d.ts +98 -0
  9. package/dist/analyzer/analyze.d.ts.map +1 -0
  10. package/dist/analyzer/analyze.js +925 -0
  11. package/dist/analyzer/analyze.js.map +1 -0
  12. package/dist/analyzer/expression-policy.d.ts +210 -0
  13. package/dist/analyzer/expression-policy.d.ts.map +1 -0
  14. package/dist/analyzer/expression-policy.js +254 -0
  15. package/dist/analyzer/expression-policy.js.map +1 -0
  16. package/dist/analyzer/index.d.ts +3 -0
  17. package/dist/analyzer/index.d.ts.map +1 -0
  18. package/dist/analyzer/index.js +6 -0
  19. package/dist/analyzer/index.js.map +1 -0
  20. package/dist/analyzer/regex-safety.d.ts +10 -0
  21. package/dist/analyzer/regex-safety.d.ts.map +1 -0
  22. package/dist/analyzer/regex-safety.js +84 -0
  23. package/dist/analyzer/regex-safety.js.map +1 -0
  24. package/dist/analyzer/signatures.d.ts +72 -0
  25. package/dist/analyzer/signatures.d.ts.map +1 -0
  26. package/dist/analyzer/signatures.js +195 -0
  27. package/dist/analyzer/signatures.js.map +1 -0
  28. package/dist/api/bundle.d.ts +29 -0
  29. package/dist/api/bundle.d.ts.map +1 -0
  30. package/dist/api/bundle.js +108 -0
  31. package/dist/api/bundle.js.map +1 -0
  32. package/dist/api/cache.d.ts +10 -0
  33. package/dist/api/cache.d.ts.map +1 -0
  34. package/dist/api/cache.js +38 -0
  35. package/dist/api/cache.js.map +1 -0
  36. package/dist/api/column-signature.d.ts +53 -0
  37. package/dist/api/column-signature.d.ts.map +1 -0
  38. package/dist/api/column-signature.js +43 -0
  39. package/dist/api/column-signature.js.map +1 -0
  40. package/dist/api/compile.d.ts +120 -0
  41. package/dist/api/compile.d.ts.map +1 -0
  42. package/dist/api/compile.js +167 -0
  43. package/dist/api/compile.js.map +1 -0
  44. package/dist/api/constraints.d.ts +48 -0
  45. package/dist/api/constraints.d.ts.map +1 -0
  46. package/dist/api/constraints.js +52 -0
  47. package/dist/api/constraints.js.map +1 -0
  48. package/dist/api/dto.d.ts +134 -0
  49. package/dist/api/dto.d.ts.map +1 -0
  50. package/dist/api/dto.js +295 -0
  51. package/dist/api/dto.js.map +1 -0
  52. package/dist/api/engine.d.ts +136 -0
  53. package/dist/api/engine.d.ts.map +1 -0
  54. package/dist/api/engine.js +183 -0
  55. package/dist/api/engine.js.map +1 -0
  56. package/dist/api/evaluate.d.ts +7 -0
  57. package/dist/api/evaluate.d.ts.map +1 -0
  58. package/dist/api/evaluate.js +10 -0
  59. package/dist/api/evaluate.js.map +1 -0
  60. package/dist/api/project.d.ts +89 -0
  61. package/dist/api/project.d.ts.map +1 -0
  62. package/dist/api/project.js +150 -0
  63. package/dist/api/project.js.map +1 -0
  64. package/dist/api/tagged.d.ts +14 -0
  65. package/dist/api/tagged.d.ts.map +1 -0
  66. package/dist/api/tagged.js +13 -0
  67. package/dist/api/tagged.js.map +1 -0
  68. package/dist/cli/dto-check.d.ts +25 -0
  69. package/dist/cli/dto-check.d.ts.map +1 -0
  70. package/dist/cli/dto-check.js +77 -0
  71. package/dist/cli/dto-check.js.map +1 -0
  72. package/dist/cli/fhirpath-check.d.ts +3 -0
  73. package/dist/cli/fhirpath-check.d.ts.map +1 -0
  74. package/dist/cli/fhirpath-check.js +149 -0
  75. package/dist/cli/fhirpath-check.js.map +1 -0
  76. package/dist/cli/ts-loader.d.mts +3 -0
  77. package/dist/cli/ts-loader.d.mts.map +1 -0
  78. package/dist/cli/ts-loader.mjs +48 -0
  79. package/dist/cli/ts-loader.mjs.map +1 -0
  80. package/dist/engine/context.d.ts +173 -0
  81. package/dist/engine/context.d.ts.map +1 -0
  82. package/dist/engine/context.js +118 -0
  83. package/dist/engine/context.js.map +1 -0
  84. package/dist/engine/evaluator.d.ts +7 -0
  85. package/dist/engine/evaluator.d.ts.map +1 -0
  86. package/dist/engine/evaluator.js +196 -0
  87. package/dist/engine/evaluator.js.map +1 -0
  88. package/dist/engine/navigation.d.ts +11 -0
  89. package/dist/engine/navigation.d.ts.map +1 -0
  90. package/dist/engine/navigation.js +102 -0
  91. package/dist/engine/navigation.js.map +1 -0
  92. package/dist/engine/operators/collections.d.ts +12 -0
  93. package/dist/engine/operators/collections.d.ts.map +1 -0
  94. package/dist/engine/operators/collections.js +31 -0
  95. package/dist/engine/operators/collections.js.map +1 -0
  96. package/dist/engine/operators/comparison.d.ts +13 -0
  97. package/dist/engine/operators/comparison.d.ts.map +1 -0
  98. package/dist/engine/operators/comparison.js +77 -0
  99. package/dist/engine/operators/comparison.js.map +1 -0
  100. package/dist/engine/operators/equality.d.ts +19 -0
  101. package/dist/engine/operators/equality.d.ts.map +1 -0
  102. package/dist/engine/operators/equality.js +203 -0
  103. package/dist/engine/operators/equality.js.map +1 -0
  104. package/dist/engine/operators/index.d.ts +18 -0
  105. package/dist/engine/operators/index.d.ts.map +1 -0
  106. package/dist/engine/operators/index.js +30 -0
  107. package/dist/engine/operators/index.js.map +1 -0
  108. package/dist/engine/operators/logic.d.ts +8 -0
  109. package/dist/engine/operators/logic.d.ts.map +1 -0
  110. package/dist/engine/operators/logic.js +38 -0
  111. package/dist/engine/operators/logic.js.map +1 -0
  112. package/dist/engine/operators/math.d.ts +13 -0
  113. package/dist/engine/operators/math.d.ts.map +1 -0
  114. package/dist/engine/operators/math.js +198 -0
  115. package/dist/engine/operators/math.js.map +1 -0
  116. package/dist/engine/operators/strings.d.ts +8 -0
  117. package/dist/engine/operators/strings.d.ts.map +1 -0
  118. package/dist/engine/operators/strings.js +22 -0
  119. package/dist/engine/operators/strings.js.map +1 -0
  120. package/dist/engine/operators/types.d.ts +3 -0
  121. package/dist/engine/operators/types.d.ts.map +1 -0
  122. package/dist/engine/operators/types.js +15 -0
  123. package/dist/engine/operators/types.js.map +1 -0
  124. package/dist/engine/type-matching.d.ts +20 -0
  125. package/dist/engine/type-matching.d.ts.map +1 -0
  126. package/dist/engine/type-matching.js +122 -0
  127. package/dist/engine/type-matching.js.map +1 -0
  128. package/dist/errors.d.ts +25 -0
  129. package/dist/errors.d.ts.map +1 -0
  130. package/dist/errors.js +21 -0
  131. package/dist/errors.js.map +1 -0
  132. package/dist/eslint/index.d.ts +12 -0
  133. package/dist/eslint/index.d.ts.map +1 -0
  134. package/dist/eslint/index.js +316 -0
  135. package/dist/eslint/index.js.map +1 -0
  136. package/dist/fhir/html-checks.d.ts +7 -0
  137. package/dist/fhir/html-checks.d.ts.map +1 -0
  138. package/dist/fhir/html-checks.js +256 -0
  139. package/dist/fhir/html-checks.js.map +1 -0
  140. package/dist/fhir/model-navigation.d.ts +11 -0
  141. package/dist/fhir/model-navigation.d.ts.map +1 -0
  142. package/dist/fhir/model-navigation.js +142 -0
  143. package/dist/fhir/model-navigation.js.map +1 -0
  144. package/dist/functions/aggregate.d.ts +2 -0
  145. package/dist/functions/aggregate.d.ts.map +1 -0
  146. package/dist/functions/aggregate.js +127 -0
  147. package/dist/functions/aggregate.js.map +1 -0
  148. package/dist/functions/boundary.d.ts +2 -0
  149. package/dist/functions/boundary.d.ts.map +1 -0
  150. package/dist/functions/boundary.js +189 -0
  151. package/dist/functions/boundary.js.map +1 -0
  152. package/dist/functions/combining.d.ts +2 -0
  153. package/dist/functions/combining.d.ts.map +1 -0
  154. package/dist/functions/combining.js +13 -0
  155. package/dist/functions/combining.js.map +1 -0
  156. package/dist/functions/control.d.ts +2 -0
  157. package/dist/functions/control.d.ts.map +1 -0
  158. package/dist/functions/control.js +25 -0
  159. package/dist/functions/control.js.map +1 -0
  160. package/dist/functions/conversion.d.ts +3 -0
  161. package/dist/functions/conversion.d.ts.map +1 -0
  162. package/dist/functions/conversion.js +316 -0
  163. package/dist/functions/conversion.js.map +1 -0
  164. package/dist/functions/date-components.d.ts +2 -0
  165. package/dist/functions/date-components.d.ts.map +1 -0
  166. package/dist/functions/date-components.js +81 -0
  167. package/dist/functions/date-components.js.map +1 -0
  168. package/dist/functions/existence.d.ts +4 -0
  169. package/dist/functions/existence.d.ts.map +1 -0
  170. package/dist/functions/existence.js +97 -0
  171. package/dist/functions/existence.js.map +1 -0
  172. package/dist/functions/fhir.d.ts +2 -0
  173. package/dist/functions/fhir.d.ts.map +1 -0
  174. package/dist/functions/fhir.js +222 -0
  175. package/dist/functions/fhir.js.map +1 -0
  176. package/dist/functions/filtering.d.ts +2 -0
  177. package/dist/functions/filtering.d.ts.map +1 -0
  178. package/dist/functions/filtering.js +112 -0
  179. package/dist/functions/filtering.js.map +1 -0
  180. package/dist/functions/install.d.ts +18 -0
  181. package/dist/functions/install.d.ts.map +1 -0
  182. package/dist/functions/install.js +20 -0
  183. package/dist/functions/install.js.map +1 -0
  184. package/dist/functions/iteration.d.ts +7 -0
  185. package/dist/functions/iteration.d.ts.map +1 -0
  186. package/dist/functions/iteration.js +9 -0
  187. package/dist/functions/iteration.js.map +1 -0
  188. package/dist/functions/math.d.ts +2 -0
  189. package/dist/functions/math.d.ts.map +1 -0
  190. package/dist/functions/math.js +152 -0
  191. package/dist/functions/math.js.map +1 -0
  192. package/dist/functions/reflection.d.ts +2 -0
  193. package/dist/functions/reflection.d.ts.map +1 -0
  194. package/dist/functions/reflection.js +23 -0
  195. package/dist/functions/reflection.js.map +1 -0
  196. package/dist/functions/registry.d.ts +28 -0
  197. package/dist/functions/registry.d.ts.map +1 -0
  198. package/dist/functions/registry.js +46 -0
  199. package/dist/functions/registry.js.map +1 -0
  200. package/dist/functions/sort.d.ts +2 -0
  201. package/dist/functions/sort.d.ts.map +1 -0
  202. package/dist/functions/sort.js +48 -0
  203. package/dist/functions/sort.js.map +1 -0
  204. package/dist/functions/string.d.ts +2 -0
  205. package/dist/functions/string.d.ts.map +1 -0
  206. package/dist/functions/string.js +353 -0
  207. package/dist/functions/string.js.map +1 -0
  208. package/dist/functions/subsetting.d.ts +2 -0
  209. package/dist/functions/subsetting.d.ts.map +1 -0
  210. package/dist/functions/subsetting.js +72 -0
  211. package/dist/functions/subsetting.js.map +1 -0
  212. package/dist/functions/tree.d.ts +8 -0
  213. package/dist/functions/tree.d.ts.map +1 -0
  214. package/dist/functions/tree.js +109 -0
  215. package/dist/functions/tree.js.map +1 -0
  216. package/dist/functions/type-specifier.d.ts +8 -0
  217. package/dist/functions/type-specifier.d.ts.map +1 -0
  218. package/dist/functions/type-specifier.js +27 -0
  219. package/dist/functions/type-specifier.js.map +1 -0
  220. package/dist/functions/utility.d.ts +2 -0
  221. package/dist/functions/utility.d.ts.map +1 -0
  222. package/dist/functions/utility.js +76 -0
  223. package/dist/functions/utility.js.map +1 -0
  224. package/dist/functions/variables.d.ts +2 -0
  225. package/dist/functions/variables.d.ts.map +1 -0
  226. package/dist/functions/variables.js +35 -0
  227. package/dist/functions/variables.js.map +1 -0
  228. package/dist/index.d.ts +25 -0
  229. package/dist/index.d.ts.map +1 -0
  230. package/dist/index.js +11 -0
  231. package/dist/index.js.map +1 -0
  232. package/dist/lexer/lexer.d.ts +4 -0
  233. package/dist/lexer/lexer.d.ts.map +1 -0
  234. package/dist/lexer/lexer.js +318 -0
  235. package/dist/lexer/lexer.js.map +1 -0
  236. package/dist/lexer/tokens.d.ts +23 -0
  237. package/dist/lexer/tokens.d.ts.map +1 -0
  238. package/dist/lexer/tokens.js +40 -0
  239. package/dist/lexer/tokens.js.map +1 -0
  240. package/dist/model/provider.d.ts +36 -0
  241. package/dist/model/provider.d.ts.map +1 -0
  242. package/dist/model/provider.js +2 -0
  243. package/dist/model/provider.js.map +1 -0
  244. package/dist/parser/ast.d.ts +104 -0
  245. package/dist/parser/ast.d.ts.map +1 -0
  246. package/dist/parser/ast.js +2 -0
  247. package/dist/parser/ast.js.map +1 -0
  248. package/dist/parser/parser.d.ts +4 -0
  249. package/dist/parser/parser.d.ts.map +1 -0
  250. package/dist/parser/parser.js +299 -0
  251. package/dist/parser/parser.js.map +1 -0
  252. package/dist/parser/precedence.d.ts +24 -0
  253. package/dist/parser/precedence.d.ts.map +1 -0
  254. package/dist/parser/precedence.js +52 -0
  255. package/dist/parser/precedence.js.map +1 -0
  256. package/dist/parser/printer.d.ts +4 -0
  257. package/dist/parser/printer.d.ts.map +1 -0
  258. package/dist/parser/printer.js +112 -0
  259. package/dist/parser/printer.js.map +1 -0
  260. package/dist/r4/generated/resources-data.d.ts +3 -0
  261. package/dist/r4/generated/resources-data.d.ts.map +1 -0
  262. package/dist/r4/generated/resources-data.js +28628 -0
  263. package/dist/r4/generated/resources-data.js.map +1 -0
  264. package/dist/r4/generated/type-maps.d.ts +43153 -0
  265. package/dist/r4/generated/type-maps.d.ts.map +1 -0
  266. package/dist/r4/generated/type-maps.js +4 -0
  267. package/dist/r4/generated/type-maps.js.map +1 -0
  268. package/dist/r4/generated/types-data.d.ts +3 -0
  269. package/dist/r4/generated/types-data.d.ts.map +1 -0
  270. package/dist/r4/generated/types-data.js +2205 -0
  271. package/dist/r4/generated/types-data.js.map +1 -0
  272. package/dist/r4/index.d.ts +9 -0
  273. package/dist/r4/index.d.ts.map +1 -0
  274. package/dist/r4/index.js +94 -0
  275. package/dist/r4/index.js.map +1 -0
  276. package/dist/r4/model-data.d.ts +17 -0
  277. package/dist/r4/model-data.d.ts.map +1 -0
  278. package/dist/r4/model-data.js +2 -0
  279. package/dist/r4/model-data.js.map +1 -0
  280. package/dist/sites/index.d.ts +31 -0
  281. package/dist/sites/index.d.ts.map +1 -0
  282. package/dist/sites/index.js +205 -0
  283. package/dist/sites/index.js.map +1 -0
  284. package/dist/typed/infer.d.ts +234 -0
  285. package/dist/typed/infer.d.ts.map +1 -0
  286. package/dist/typed/infer.js +85 -0
  287. package/dist/typed/infer.js.map +1 -0
  288. package/dist/typed/perf-fixture.types.d.ts +136 -0
  289. package/dist/typed/perf-fixture.types.d.ts.map +1 -0
  290. package/dist/typed/perf-fixture.types.js +2 -0
  291. package/dist/typed/perf-fixture.types.js.map +1 -0
  292. package/dist/values/collection.d.ts +24 -0
  293. package/dist/values/collection.d.ts.map +1 -0
  294. package/dist/values/collection.js +51 -0
  295. package/dist/values/collection.js.map +1 -0
  296. package/dist/values/datetime.d.ts +54 -0
  297. package/dist/values/datetime.d.ts.map +1 -0
  298. package/dist/values/datetime.js +226 -0
  299. package/dist/values/datetime.js.map +1 -0
  300. package/dist/values/decimal.d.ts +48 -0
  301. package/dist/values/decimal.d.ts.map +1 -0
  302. package/dist/values/decimal.js +188 -0
  303. package/dist/values/decimal.js.map +1 -0
  304. package/dist/values/numeric.d.ts +24 -0
  305. package/dist/values/numeric.d.ts.map +1 -0
  306. package/dist/values/numeric.js +53 -0
  307. package/dist/values/numeric.js.map +1 -0
  308. package/dist/values/quantity.d.ts +49 -0
  309. package/dist/values/quantity.d.ts.map +1 -0
  310. package/dist/values/quantity.js +297 -0
  311. package/dist/values/quantity.js.map +1 -0
  312. package/dist/values/temporal-arithmetic.d.ts +17 -0
  313. package/dist/values/temporal-arithmetic.d.ts.map +1 -0
  314. package/dist/values/temporal-arithmetic.js +183 -0
  315. package/dist/values/temporal-arithmetic.js.map +1 -0
  316. package/dist/values/temporal-compare.d.ts +9 -0
  317. package/dist/values/temporal-compare.d.ts.map +1 -0
  318. package/dist/values/temporal-compare.js +59 -0
  319. package/dist/values/temporal-compare.js.map +1 -0
  320. package/dist/values/type-compat.d.ts +47 -0
  321. package/dist/values/type-compat.d.ts.map +1 -0
  322. package/dist/values/type-compat.js +100 -0
  323. package/dist/values/type-compat.js.map +1 -0
  324. package/dist/values/typed-value.d.ts +48 -0
  325. package/dist/values/typed-value.d.ts.map +1 -0
  326. package/dist/values/typed-value.js +117 -0
  327. package/dist/values/typed-value.js.map +1 -0
  328. package/dist/values/ucum.d.ts +14 -0
  329. package/dist/values/ucum.d.ts.map +1 -0
  330. package/dist/values/ucum.js +146 -0
  331. package/dist/values/ucum.js.map +1 -0
  332. package/package.json +137 -0
  333. package/src/analyzer/analyze-dto.ts +195 -0
  334. package/src/analyzer/analyze.ts +1181 -0
  335. package/src/analyzer/expression-policy.ts +434 -0
  336. package/src/analyzer/index.ts +23 -0
  337. package/src/analyzer/regex-safety.ts +85 -0
  338. package/src/analyzer/signatures.ts +260 -0
  339. package/src/api/bundle.ts +132 -0
  340. package/src/api/cache.ts +40 -0
  341. package/src/api/column-signature.ts +71 -0
  342. package/src/api/compile.ts +308 -0
  343. package/src/api/constraints.ts +104 -0
  344. package/src/api/dto.ts +445 -0
  345. package/src/api/engine.ts +328 -0
  346. package/src/api/evaluate.ts +11 -0
  347. package/src/api/project.ts +255 -0
  348. package/src/api/tagged.ts +35 -0
  349. package/src/cli/dto-check.ts +94 -0
  350. package/src/cli/fhirpath-check.ts +168 -0
  351. package/src/cli/ts-loader.mjs +49 -0
  352. package/src/engine/context.ts +282 -0
  353. package/src/engine/evaluator.ts +246 -0
  354. package/src/engine/navigation.ts +106 -0
  355. package/src/engine/operators/collections.ts +34 -0
  356. package/src/engine/operators/comparison.ts +82 -0
  357. package/src/engine/operators/equality.ts +232 -0
  358. package/src/engine/operators/index.ts +65 -0
  359. package/src/engine/operators/logic.ts +53 -0
  360. package/src/engine/operators/math.ts +225 -0
  361. package/src/engine/operators/strings.ts +25 -0
  362. package/src/engine/operators/types.ts +16 -0
  363. package/src/engine/type-matching.ts +148 -0
  364. package/src/errors.ts +33 -0
  365. package/src/eslint/index.ts +388 -0
  366. package/src/fhir/html-checks.ts +266 -0
  367. package/src/fhir/model-navigation.ts +148 -0
  368. package/src/functions/aggregate.ts +136 -0
  369. package/src/functions/boundary.ts +209 -0
  370. package/src/functions/combining.ts +15 -0
  371. package/src/functions/control.ts +25 -0
  372. package/src/functions/conversion.ts +349 -0
  373. package/src/functions/date-components.ts +88 -0
  374. package/src/functions/existence.ts +107 -0
  375. package/src/functions/fhir.ts +247 -0
  376. package/src/functions/filtering.ts +120 -0
  377. package/src/functions/install.ts +19 -0
  378. package/src/functions/iteration.ts +21 -0
  379. package/src/functions/math.ts +167 -0
  380. package/src/functions/reflection.ts +24 -0
  381. package/src/functions/registry.ts +72 -0
  382. package/src/functions/sort.ts +68 -0
  383. package/src/functions/string.ts +413 -0
  384. package/src/functions/subsetting.ts +89 -0
  385. package/src/functions/tree.ts +112 -0
  386. package/src/functions/type-specifier.ts +29 -0
  387. package/src/functions/utility.ts +82 -0
  388. package/src/functions/variables.ts +36 -0
  389. package/src/index.ts +30 -0
  390. package/src/lexer/lexer.ts +341 -0
  391. package/src/lexer/tokens.ts +69 -0
  392. package/src/model/provider.ts +36 -0
  393. package/src/parser/ast.ts +164 -0
  394. package/src/parser/parser.ts +321 -0
  395. package/src/parser/precedence.ts +52 -0
  396. package/src/parser/printer.ts +114 -0
  397. package/src/r4/generated/resources-data.ts +28631 -0
  398. package/src/r4/generated/type-maps.ts +22145 -0
  399. package/src/r4/generated/types-data.ts +2208 -0
  400. package/src/r4/index.ts +107 -0
  401. package/src/r4/model-data.ts +17 -0
  402. package/src/sites/index.ts +285 -0
  403. package/src/typed/infer.ts +433 -0
  404. package/src/typed/perf-fixture.types.ts +143 -0
  405. package/src/values/collection.ts +57 -0
  406. package/src/values/datetime.ts +285 -0
  407. package/src/values/decimal.ts +213 -0
  408. package/src/values/numeric.ts +63 -0
  409. package/src/values/quantity.ts +358 -0
  410. package/src/values/temporal-arithmetic.ts +231 -0
  411. package/src/values/temporal-compare.ts +65 -0
  412. package/src/values/type-compat.ts +133 -0
  413. package/src/values/typed-value.ts +151 -0
  414. package/src/values/ucum.ts +181 -0
@@ -0,0 +1,112 @@
1
+ import { pairEquals } from '../engine/operators/equality.ts'
2
+ import { readModelProperty } from '../fhir/model-navigation.ts'
3
+ import type { ModelProvider } from '../model/provider.ts'
4
+ import { SYSTEM_BOOLEAN, SYSTEM_STRING, systemTypeOf, toTypedValue, type TypedValue } from '../values/typed-value.ts'
5
+ import { registerFunction } from './registry.ts'
6
+
7
+ /**
8
+ * All immediate child nodes of an item. `resourceType` is a JSON discriminator, not
9
+ * an element, and `_field` keys are primitive-extension metadata handled elsewhere.
10
+ */
11
+ export function childrenOf(item: TypedValue, model?: ModelProvider): TypedValue[] {
12
+ const value = item.value
13
+ if (typeof value !== 'object' || value === null) {
14
+ // A primitive's children are its element id and extensions (FHIR spec).
15
+ return primitiveMetadataChildren(item)
16
+ }
17
+ // With a model, children keep their element types (so `code as Coding` works
18
+ // downstream) and elements present only through a _field sibling still appear.
19
+ if (model && item.type.startsWith(`${model.namespace}.`)) {
20
+ const elements = model.listElements?.(item.type)
21
+ if (elements !== undefined) {
22
+ const typed: TypedValue[] = []
23
+ for (const element of elements) {
24
+ typed.push(...(readModelProperty(model, item, element) ?? []))
25
+ }
26
+ return typed
27
+ }
28
+ }
29
+ const result: TypedValue[] = []
30
+ for (const [key, child] of Object.entries(value)) {
31
+ if (key === 'resourceType' || key.startsWith('_') || child === null || child === undefined) {
32
+ continue
33
+ }
34
+ if (Array.isArray(child)) {
35
+ for (const element of child) {
36
+ if (element !== null && element !== undefined) {
37
+ result.push(toTypedValue(element))
38
+ }
39
+ }
40
+ } else {
41
+ result.push(toTypedValue(child))
42
+ }
43
+ }
44
+ return result
45
+ }
46
+
47
+ function primitiveMetadataChildren(item: TypedValue): TypedValue[] {
48
+ const metadata = item.primitiveElement as { id?: unknown; extension?: unknown } | undefined
49
+ if (metadata === undefined || metadata === null) {
50
+ return []
51
+ }
52
+ const result: TypedValue[] = []
53
+ if (metadata.id !== undefined && metadata.id !== null) {
54
+ result.push({ type: 'System.String', value: metadata.id })
55
+ }
56
+ if (Array.isArray(metadata.extension)) {
57
+ result.push(...metadata.extension.map(extension => ({ type: 'FHIR.Extension', value: extension })))
58
+ }
59
+ return result
60
+ }
61
+
62
+ registerFunction('children', {
63
+ minArity: 0,
64
+ maxArity: 0,
65
+ evaluate: (context, input) => input.flatMap(item => childrenOf(item, context.model)),
66
+ })
67
+
68
+ registerFunction('descendants', {
69
+ minArity: 0,
70
+ maxArity: 0,
71
+ evaluate: (context, input) => {
72
+ // repeat(children()): collect transitively, excluding the input itself. The
73
+ // dedup granularity differs from repeat() on purpose and is pinned by the
74
+ // official suites: descendants() keeps `=`-equal siblings produced in the same
75
+ // round (a batch filter against prior rounds only), where repeat() collapses
76
+ // them (incremental within-round dedup). They cannot share one closure.
77
+ //
78
+ // Dedup is `existing.value === item.value || pairEquals(existing, item)` against
79
+ // prior rounds, indexed for O(1) on the common case: `seenValues` (a Set of raw
80
+ // values) resolves the `===` branch, and it also covers `pairEquals` for
81
+ // String/Boolean, whose equality *is* `===`. The remaining classes (numeric,
82
+ // temporal, quantity, complex) can be pairEquals-equal without being `===`-equal,
83
+ // so those fall back to a scan of same-class prior items. The index updates only
84
+ // between rounds, preserving the batch semantics.
85
+ const collected: TypedValue[] = []
86
+ const seenValues = new Set<unknown>()
87
+ const fallback: TypedValue[] = []
88
+ const needsFallback = (item: TypedValue): boolean => {
89
+ const type = systemTypeOf(item)
90
+ return type !== SYSTEM_STRING && type !== SYSTEM_BOOLEAN
91
+ }
92
+ const isDuplicate = (item: TypedValue): boolean => {
93
+ if (seenValues.has(item.value)) {
94
+ return true
95
+ }
96
+ return needsFallback(item) && fallback.some(existing => pairEquals(existing, item) === true)
97
+ }
98
+ let current = input.flatMap(item => childrenOf(item, context.model))
99
+ while (current.length > 0) {
100
+ const fresh = current.filter(item => !isDuplicate(item))
101
+ for (const item of fresh) {
102
+ seenValues.add(item.value)
103
+ if (needsFallback(item)) {
104
+ fallback.push(item)
105
+ }
106
+ }
107
+ collected.push(...fresh)
108
+ current = fresh.flatMap(item => childrenOf(item, context.model))
109
+ }
110
+ return collected
111
+ },
112
+ })
@@ -0,0 +1,29 @@
1
+ import { FhirPathTypeError } from '../errors.ts'
2
+ import type { AstNode } from '../parser/ast.ts'
3
+
4
+ /**
5
+ * `ofType(Quantity)`, `is(System.Boolean)`, and `as(FHIR.Patient)` receive their type
6
+ * as an expression that parsed as an identifier or dotted identifier chain; read it
7
+ * back as a qualified type name.
8
+ */
9
+ export function typePartsFromArgument(functionName: string, node: AstNode): string[] {
10
+ const parts = collect(node)
11
+ if (!parts) {
12
+ throw new FhirPathTypeError(`Function '${functionName}' expects a type name argument`)
13
+ }
14
+ return parts
15
+ }
16
+
17
+ function collect(node: AstNode): string[] | undefined {
18
+ if (node.kind === 'identifier') {
19
+ return [node.name]
20
+ }
21
+ if (node.kind === 'dot') {
22
+ const left = collect(node.left)
23
+ const right = node.right.kind === 'identifier' ? node.right.name : undefined
24
+ if (left && right !== undefined) {
25
+ return [...left, right]
26
+ }
27
+ }
28
+ return undefined
29
+ }
@@ -0,0 +1,82 @@
1
+ import { withFrame } from '../engine/context.ts'
2
+ import { booleanSingleton, wrapBoolean } from '../values/collection.ts'
3
+ import { Temporal } from '../values/datetime.ts'
4
+ import { SYSTEM_DATE, SYSTEM_DATETIME, SYSTEM_TIME, type TypedValue } from '../values/typed-value.ts'
5
+ import { argAt, registerFunction } from './registry.ts'
6
+
7
+ registerFunction('not', {
8
+ minArity: 0,
9
+ maxArity: 0,
10
+ evaluate: (_context, input) => {
11
+ const value = booleanSingleton(input)
12
+ return wrapBoolean(value === undefined ? undefined : !value)
13
+ },
14
+ })
15
+
16
+ registerFunction('trace', {
17
+ minArity: 1,
18
+ maxArity: 2,
19
+ evaluate: (context, input, args, evaluateNode) => {
20
+ const name = evaluateNode(argAt(args, 0), context, input)
21
+ const label = typeof name[0]?.value === 'string' ? (name[0].value as string) : ''
22
+ const traced =
23
+ args.length === 2
24
+ ? withFrame(context, { thisValue: input }, frameContext => evaluateNode(argAt(args, 1), frameContext, input))
25
+ : input
26
+ context.trace(label, traced)
27
+ return input
28
+ },
29
+ })
30
+
31
+ /** The evaluation clock is fixed per evaluation, so these are deterministic within one run. */
32
+ function clockParts(now: Date): { date: string; time: string; offset: number } {
33
+ const pad = (value: number): string => String(value).padStart(2, '0')
34
+ const date = `${String(now.getFullYear()).padStart(4, '0')}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
35
+ const millis = String(now.getMilliseconds()).padStart(3, '0')
36
+ const time = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${millis}`
37
+ return { date, time, offset: -now.getTimezoneOffset() }
38
+ }
39
+
40
+ registerFunction('now', {
41
+ minArity: 0,
42
+ maxArity: 0,
43
+ evaluate: context => {
44
+ const { date, time, offset } = clockParts(context.now)
45
+ const value = Temporal.parseDateTime(`${date}T${time}`)
46
+ /* v8 ignore next 3 -- the clock always renders a valid dateTime */
47
+ if (!value) {
48
+ return []
49
+ }
50
+ const withOffset = Temporal.fromFields('dateTime', {
51
+ year: value.year,
52
+ month: value.month,
53
+ day: value.day,
54
+ hour: value.hour,
55
+ minute: value.minute,
56
+ second: value.second,
57
+ fraction: value.fraction,
58
+ timezoneOffsetMinutes: offset,
59
+ }) as Temporal
60
+ return [{ type: SYSTEM_DATETIME, value: withOffset }] as TypedValue[]
61
+ },
62
+ })
63
+
64
+ registerFunction('today', {
65
+ minArity: 0,
66
+ maxArity: 0,
67
+ evaluate: context => {
68
+ const { date } = clockParts(context.now)
69
+ const value = Temporal.parseDate(date) as Temporal
70
+ return [{ type: SYSTEM_DATE, value }] as TypedValue[]
71
+ },
72
+ })
73
+
74
+ registerFunction('timeOfDay', {
75
+ minArity: 0,
76
+ maxArity: 0,
77
+ evaluate: context => {
78
+ const { time } = clockParts(context.now)
79
+ const value = Temporal.parseTime(time) as Temporal
80
+ return [{ type: SYSTEM_TIME, value }] as TypedValue[]
81
+ },
82
+ })
@@ -0,0 +1,36 @@
1
+ import { withFrame } from '../engine/context.ts'
2
+ import { FhirPathTypeError } from '../errors.ts'
3
+ import { singleton } from '../values/collection.ts'
4
+ import { SYSTEM_STRING, systemTypeOf, type TypedValue } from '../values/typed-value.ts'
5
+ import { argAt, registerFunction } from './registry.ts'
6
+
7
+ /**
8
+ * defineVariable(name [, value]) — ballot STU. The variable joins the current
9
+ * expression chain's scope (see EvaluationContext.variables): later links of the
10
+ * same dot chain see it, sibling operator operands and arguments do not.
11
+ */
12
+ registerFunction('defineVariable', {
13
+ minArity: 1,
14
+ maxArity: 2,
15
+ evaluate: (context, input, args, evaluateNode) => {
16
+ const nameValue = singleton(evaluateNode(argAt(args, 0), context, input))
17
+ if (nameValue === undefined || systemTypeOf(nameValue) !== SYSTEM_STRING) {
18
+ throw new FhirPathTypeError('defineVariable() expects a String name')
19
+ }
20
+ const name = nameValue.value as string
21
+ if (context.env.has(name)) {
22
+ throw new FhirPathTypeError(`Cannot override the environment variable %${name}`)
23
+ }
24
+ if (context.variables.has(name)) {
25
+ throw new FhirPathTypeError(`Variable %${name} is already defined in this scope`)
26
+ }
27
+ // The value expression evaluates against the function input, e.g.
28
+ // Patient.name.defineVariable('n2', skip(1).first()).
29
+ const value: TypedValue[] =
30
+ args.length === 2
31
+ ? withFrame(context, { thisValue: input }, frameContext => evaluateNode(argAt(args, 1), frameContext, input))
32
+ : input
33
+ context.variables.set(name, value)
34
+ return input
35
+ },
36
+ })
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ export type { CustomFunctionSignature, ValueArgSpec } from './analyzer/signatures.ts'
2
+ export type { BundleLike } from './api/bundle.ts'
3
+ export type {
4
+ AnyExpression,
5
+ CustomFunction,
6
+ EvaluateOptions,
7
+ OverloadedCustomFunction,
8
+ SingleCustomFunction,
9
+ } from './api/compile.ts'
10
+ export { compile, CompiledExpression, DEFAULT_PARSE_CACHE_SIZE } from './api/compile.ts'
11
+ export type { ConstraintCheckResult, ConstraintIssue, FhirConstraint, OperationOutcome } from './api/constraints.ts'
12
+ export type { ColumnTypeMismatch, DtoBase, DtoClass, DtoEnv, DtoInstance, DtoOptions, DtoRow } from './api/dto.ts'
13
+ export { column, criteria, defineDto } from './api/dto.ts'
14
+ export type { EngineInput, EngineOptions, TypedEvaluateOptions } from './api/engine.ts'
15
+ export { BoundExpression, FhirPathEngine, recordEngines } from './api/engine.ts'
16
+ export { evaluate } from './api/evaluate.ts'
17
+ export type { ColumnOptions, ColumnResult, Projection, ProjectionColumn, ProjectionColumns } from './api/project.ts'
18
+ export { fhirpath } from './api/tagged.ts'
19
+ export type { HostNativeFunction, RegexEngine } from './engine/context.ts'
20
+ export type { SourceSpan } from './errors.ts'
21
+ export { FhirPathError, FhirPathRuntimeError, FhirPathSyntaxError, FhirPathTypeError } from './errors.ts'
22
+ export type { ElementInfo, ModelProvider } from './model/provider.ts'
23
+ export type { AstNode } from './parser/ast.ts'
24
+ export { parse } from './parser/parser.ts'
25
+ export { printExpression } from './parser/printer.ts'
26
+ export type { FhirpathInput, FhirpathResult, FhirpathResultIn, FhirTypeName } from './typed/infer.ts'
27
+ export { Temporal } from './values/datetime.ts'
28
+ export { Decimal } from './values/decimal.ts'
29
+ export type { ValueKind } from './values/type-compat.ts'
30
+ export type { QuantityValue, TypedValue } from './values/typed-value.ts'
@@ -0,0 +1,341 @@
1
+ import { FhirPathSyntaxError, type SourceSpan } from '../errors.ts'
2
+ import { KEYWORDS, SPECIAL_VARIABLES, type Token, type TokenKind } from './tokens.ts'
3
+
4
+ const isDigit = (ch: string): boolean => ch >= '0' && ch <= '9'
5
+ const isIdentifierStart = (ch: string): boolean => (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch === '_'
6
+ const isIdentifierPart = (ch: string): boolean => isIdentifierStart(ch) || isDigit(ch)
7
+ const isWhitespace = (ch: string): boolean => ch === ' ' || ch === '\t' || ch === '\r' || ch === '\n' || ch === '\f'
8
+ const isHexDigit = (ch: string): boolean => isDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
9
+
10
+ const ESCAPES: Readonly<Record<string, string>> = {
11
+ "'": "'",
12
+ '"': '"',
13
+ '`': '`',
14
+ '/': '/',
15
+ '\\': '\\',
16
+ f: '\f',
17
+ n: '\n',
18
+ r: '\r',
19
+ t: '\t',
20
+ }
21
+
22
+ const TWO_CHAR_OPERATORS: ReadonlySet<string> = new Set(['!=', '!~', '<=', '>='])
23
+ const ONE_CHAR_OPERATORS: ReadonlySet<string> = new Set(['=', '~', '<', '>', '+', '-', '*', '/', '|', '&'])
24
+ const PUNCT: ReadonlySet<string> = new Set(['(', ')', '[', ']', '{', '}', '.', ',', '%'])
25
+
26
+ class Lexer {
27
+ private readonly source: string
28
+ private pos = 0
29
+
30
+ constructor(source: string) {
31
+ this.source = source
32
+ }
33
+
34
+ tokenize(): Token[] {
35
+ const tokens: Token[] = []
36
+ for (;;) {
37
+ const token = this.next()
38
+ tokens.push(token)
39
+ if (token.kind === 'end') {
40
+ return tokens
41
+ }
42
+ }
43
+ }
44
+
45
+ private next(): Token {
46
+ this.skipTrivia()
47
+ if (this.pos >= this.source.length) {
48
+ return this.token('end', this.pos, '')
49
+ }
50
+ const start = this.pos
51
+ const ch = this.source[this.pos] as string
52
+ if (isIdentifierStart(ch)) {
53
+ return this.readIdentifier(start)
54
+ }
55
+ if (isDigit(ch)) {
56
+ return this.readNumber(start)
57
+ }
58
+ switch (ch) {
59
+ case "'":
60
+ return this.readString(start)
61
+ case '`':
62
+ return this.readDelimitedIdentifier(start)
63
+ case '@':
64
+ return this.readDateTimeLiteral(start)
65
+ case '$':
66
+ return this.readSpecialVariable(start)
67
+ default:
68
+ return this.readOperatorOrPunct(start)
69
+ }
70
+ }
71
+
72
+ private skipTrivia(): void {
73
+ while (this.pos < this.source.length) {
74
+ const ch = this.source[this.pos] as string
75
+ if (isWhitespace(ch)) {
76
+ this.advance()
77
+ } else if (ch === '/' && this.source[this.pos + 1] === '/') {
78
+ while (this.pos < this.source.length && this.source[this.pos] !== '\n') {
79
+ this.advance()
80
+ }
81
+ } else if (ch === '/' && this.source[this.pos + 1] === '*') {
82
+ const start = this.spanFrom(this.pos)
83
+ this.advance()
84
+ this.advance()
85
+ while (this.pos < this.source.length && !(this.source[this.pos] === '*' && this.source[this.pos + 1] === '/')) {
86
+ this.advance()
87
+ }
88
+ if (this.pos >= this.source.length) {
89
+ throw new FhirPathSyntaxError('Unterminated comment', start)
90
+ }
91
+ this.advance()
92
+ this.advance()
93
+ } else {
94
+ return
95
+ }
96
+ }
97
+ }
98
+
99
+ private readIdentifier(start: number): Token {
100
+ while (this.pos < this.source.length && isIdentifierPart(this.source[this.pos] as string)) {
101
+ this.advance()
102
+ }
103
+ const text = this.source.slice(start, this.pos)
104
+ return this.token(KEYWORDS.has(text) ? 'keyword' : 'identifier', start, text)
105
+ }
106
+
107
+ private readNumber(start: number): Token {
108
+ while (this.pos < this.source.length && isDigit(this.source[this.pos] as string)) {
109
+ this.advance()
110
+ }
111
+ // A '.' continues the number only when a digit follows; `5.single()` is Integer 5 then '.'.
112
+ if (this.source[this.pos] === '.' && isDigit(this.source[this.pos + 1] ?? '')) {
113
+ this.advance()
114
+ while (this.pos < this.source.length && isDigit(this.source[this.pos] as string)) {
115
+ this.advance()
116
+ }
117
+ } else if (this.source[this.pos] === 'L') {
118
+ // Long literal suffix (ballot STU): 5L is a System.Long.
119
+ this.advance()
120
+ }
121
+ const text = this.source.slice(start, this.pos)
122
+ return this.token('number', start, text)
123
+ }
124
+
125
+ private readString(start: number): Token {
126
+ const value = this.readQuoted(start, "'", 'string literal')
127
+ return this.token('string', start, value)
128
+ }
129
+
130
+ private readDelimitedIdentifier(start: number): Token {
131
+ const value = this.readQuoted(start, '`', 'delimited identifier')
132
+ if (value === '') {
133
+ throw new FhirPathSyntaxError('Empty delimited identifier', this.spanFrom(start))
134
+ }
135
+ return this.token('delimitedIdentifier', start, value)
136
+ }
137
+
138
+ private readQuoted(start: number, quote: string, what: string): string {
139
+ this.advance()
140
+ let value = ''
141
+ while (this.pos < this.source.length) {
142
+ const ch = this.source[this.pos] as string
143
+ if (ch === quote) {
144
+ this.advance()
145
+ return value
146
+ }
147
+ if (ch === '\\') {
148
+ value += this.readEscape()
149
+ } else {
150
+ value += ch
151
+ this.advance()
152
+ }
153
+ }
154
+ throw new FhirPathSyntaxError(`Unterminated ${what}`, this.spanFrom(start))
155
+ }
156
+
157
+ private readEscape(): string {
158
+ const start = this.pos
159
+ this.advance()
160
+ const ch = this.source[this.pos]
161
+ if (ch === undefined) {
162
+ throw new FhirPathSyntaxError('Unterminated escape sequence', this.spanFrom(start))
163
+ }
164
+ if (ch === 'u') {
165
+ this.advance()
166
+ let code = ''
167
+ for (let i = 0; i < 4; i++) {
168
+ const hex = this.source[this.pos]
169
+ if (hex === undefined || !isHexDigit(hex)) {
170
+ throw new FhirPathSyntaxError('Unicode escape must be \\u followed by 4 hex digits', this.spanFrom(start))
171
+ }
172
+ code += hex
173
+ this.advance()
174
+ }
175
+ return String.fromCharCode(Number.parseInt(code, 16))
176
+ }
177
+ const resolved = ESCAPES[ch]
178
+ if (resolved === undefined) {
179
+ throw new FhirPathSyntaxError(`Invalid escape sequence \\${ch}`, this.spanFrom(start))
180
+ }
181
+ this.advance()
182
+ return resolved
183
+ }
184
+
185
+ private readDateTimeLiteral(start: number): Token {
186
+ this.advance()
187
+ if (this.source[this.pos] === 'T') {
188
+ this.advance()
189
+ this.readTimeFormat(start, 'time literal')
190
+ return this.token('time', start, this.source.slice(start + 2, this.pos))
191
+ }
192
+ this.readDigits(2, start, 'date literal')
193
+ this.readDigits(2, start, 'date literal')
194
+ // Like the reference ANTLR lexer, a component is consumed only when complete:
195
+ // `@2014-5` is the date @2014 followed by the operator '-' and the number 5.
196
+ if (this.hasTwoDigitComponent('-')) {
197
+ this.advance()
198
+ this.readDigits(2, start, 'date literal')
199
+ if (this.hasTwoDigitComponent('-')) {
200
+ this.advance()
201
+ this.readDigits(2, start, 'date literal')
202
+ }
203
+ }
204
+ if (this.source[this.pos] !== 'T') {
205
+ return this.token('date', start, this.source.slice(start + 1, this.pos))
206
+ }
207
+ this.advance()
208
+ if (isDigit(this.source[this.pos] ?? '')) {
209
+ this.readTimeFormat(start, 'dateTime literal')
210
+ this.readTimezoneOffset()
211
+ }
212
+ return this.token('dateTime', start, this.source.slice(start + 1, this.pos))
213
+ }
214
+
215
+ private readTimeFormat(start: number, what: string): void {
216
+ this.readDigits(2, start, what)
217
+ if (this.hasTwoDigitComponent(':')) {
218
+ this.advance()
219
+ this.readDigits(2, start, what)
220
+ if (this.hasTwoDigitComponent(':')) {
221
+ this.advance()
222
+ this.readDigits(2, start, what)
223
+ if (this.source[this.pos] === '.' && isDigit(this.source[this.pos + 1] ?? '')) {
224
+ this.advance()
225
+ while (isDigit(this.source[this.pos] ?? '')) {
226
+ this.advance()
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ private hasTwoDigitComponent(separator: string): boolean {
234
+ return (
235
+ this.source[this.pos] === separator &&
236
+ isDigit(this.source[this.pos + 1] ?? '') &&
237
+ isDigit(this.source[this.pos + 2] ?? '')
238
+ )
239
+ }
240
+
241
+ private readTimezoneOffset(): void {
242
+ const ch = this.source[this.pos]
243
+ if (ch === 'Z') {
244
+ this.advance()
245
+ return
246
+ }
247
+ // '+'/'-' is an offset only in the full hh:mm shape; otherwise it is an operator, e.g. `@2014-01-01T10 - 3 days`.
248
+ if (
249
+ (ch === '+' || ch === '-') &&
250
+ isDigit(this.source[this.pos + 1] ?? '') &&
251
+ isDigit(this.source[this.pos + 2] ?? '') &&
252
+ this.source[this.pos + 3] === ':' &&
253
+ isDigit(this.source[this.pos + 4] ?? '') &&
254
+ isDigit(this.source[this.pos + 5] ?? '')
255
+ ) {
256
+ for (let i = 0; i < 6; i++) {
257
+ this.advance()
258
+ }
259
+ }
260
+ }
261
+
262
+ private readDigits(count: number, start: number, what: string): void {
263
+ for (let i = 0; i < count; i++) {
264
+ const ch = this.source[this.pos]
265
+ if (ch === undefined || !isDigit(ch)) {
266
+ throw new FhirPathSyntaxError(`Invalid ${what}`, this.spanFrom(start))
267
+ }
268
+ this.advance()
269
+ }
270
+ }
271
+
272
+ private readSpecialVariable(start: number): Token {
273
+ this.advance()
274
+ while (this.pos < this.source.length && isIdentifierPart(this.source[this.pos] as string)) {
275
+ this.advance()
276
+ }
277
+ const text = this.source.slice(start, this.pos)
278
+ if (!SPECIAL_VARIABLES.has(text)) {
279
+ throw new FhirPathSyntaxError(`Unknown special variable ${text}`, this.spanFrom(start))
280
+ }
281
+ return this.token('specialVariable', start, text.slice(1))
282
+ }
283
+
284
+ private readOperatorOrPunct(start: number): Token {
285
+ const two = this.source.slice(this.pos, this.pos + 2)
286
+ if (TWO_CHAR_OPERATORS.has(two)) {
287
+ this.advance()
288
+ this.advance()
289
+ return this.token('operator', start, two)
290
+ }
291
+ const ch = this.source[this.pos] as string
292
+ if (ONE_CHAR_OPERATORS.has(ch)) {
293
+ this.advance()
294
+ return this.token('operator', start, ch)
295
+ }
296
+ if (PUNCT.has(ch)) {
297
+ this.advance()
298
+ return this.token('punct', start, ch)
299
+ }
300
+ throw new FhirPathSyntaxError(`Unexpected character '${ch}'`, this.spanFrom(start))
301
+ }
302
+
303
+ private advance(): void {
304
+ this.pos += 1
305
+ }
306
+
307
+ private token(kind: TokenKind, start: number, value: string): Token {
308
+ return {
309
+ kind,
310
+ text: this.source.slice(start, this.pos),
311
+ value,
312
+ span: this.spanFrom(start),
313
+ }
314
+ }
315
+
316
+ // Tokens are emitted in source order, so line/column advance incrementally from
317
+ // the previous span instead of rescanning from offset 0 (which made tokenizing
318
+ // quadratic in expression length).
319
+ private lineCursor = 0
320
+ private cursorLine = 1
321
+ private cursorColumn = 1
322
+
323
+ /** Span from `start` to the current position; line/column point at `start`. */
324
+ private spanFrom(start: number): SourceSpan {
325
+ for (let i = this.lineCursor; i < start; i++) {
326
+ if (this.source[i] === '\n') {
327
+ this.cursorLine += 1
328
+ this.cursorColumn = 1
329
+ } else {
330
+ this.cursorColumn += 1
331
+ }
332
+ }
333
+ this.lineCursor = Math.max(this.lineCursor, start)
334
+ return { start, end: this.pos, line: this.cursorLine, column: this.cursorColumn }
335
+ }
336
+ }
337
+
338
+ /** Split a FHIRPath expression into tokens. The last token always has kind `end`. */
339
+ export function tokenize(source: string): Token[] {
340
+ return new Lexer(source).tokenize()
341
+ }