@tradik/xslt3 0.0.0-stage → 1.0.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 (286) hide show
  1. package/LICENSE.md +28 -0
  2. package/README.md +98 -2
  3. package/package.json +43 -4
  4. package/src/errors.js +20 -0
  5. package/src/functions/aggregates.js +144 -0
  6. package/src/functions/arraySort.js +33 -0
  7. package/src/functions/arrays.js +181 -0
  8. package/src/functions/attributeTypes.js +58 -0
  9. package/src/functions/baseUris.js +82 -0
  10. package/src/functions/collations.js +127 -0
  11. package/src/functions/collections.js +84 -0
  12. package/src/functions/context.js +83 -0
  13. package/src/functions/core.js +167 -0
  14. package/src/functions/datetime.js +203 -0
  15. package/src/functions/deepEqual.js +132 -0
  16. package/src/functions/diagnostics.js +88 -0
  17. package/src/functions/focus.js +34 -0
  18. package/src/functions/format/dateComponents.js +176 -0
  19. package/src/functions/format/datePicture.js +115 -0
  20. package/src/functions/format/decimalFormat.js +65 -0
  21. package/src/functions/format/digitPattern.js +145 -0
  22. package/src/functions/format/formatDateTime.js +204 -0
  23. package/src/functions/format/formatInteger.js +109 -0
  24. package/src/functions/format/formatNumber.js +165 -0
  25. package/src/functions/format/fractionFormat.js +80 -0
  26. package/src/functions/format/germanWords.js +89 -0
  27. package/src/functions/format/numberLanguages.js +41 -0
  28. package/src/functions/format/numberPicture.js +202 -0
  29. package/src/functions/format/numberWords.js +155 -0
  30. package/src/functions/format/place.js +74 -0
  31. package/src/functions/format/timezoneFormat.js +89 -0
  32. package/src/functions/higherOrder.js +106 -0
  33. package/src/functions/ids.js +127 -0
  34. package/src/functions/json/index.js +17 -0
  35. package/src/functions/json/jsonChars.js +110 -0
  36. package/src/functions/json/jsonParser.js +203 -0
  37. package/src/functions/json/jsonStrings.js +116 -0
  38. package/src/functions/json/jsonToXml.js +116 -0
  39. package/src/functions/json/parseJson.js +119 -0
  40. package/src/functions/json/xmlRepresentation.js +103 -0
  41. package/src/functions/json/xmlToJson.js +147 -0
  42. package/src/functions/library.js +47 -0
  43. package/src/functions/mapArrayEqual.js +41 -0
  44. package/src/functions/maps.js +152 -0
  45. package/src/functions/math.js +73 -0
  46. package/src/functions/nodePaths.js +181 -0
  47. package/src/functions/nodes.js +199 -0
  48. package/src/functions/numeric.js +148 -0
  49. package/src/functions/options.js +58 -0
  50. package/src/functions/parseIetfDate.js +104 -0
  51. package/src/functions/parseXml.js +77 -0
  52. package/src/functions/qnames.js +68 -0
  53. package/src/functions/random.js +128 -0
  54. package/src/functions/regex/analyzeString.js +144 -0
  55. package/src/functions/regex/charClass.js +112 -0
  56. package/src/functions/regex/compile.js +81 -0
  57. package/src/functions/regex/escapes.js +109 -0
  58. package/src/functions/regex/parser.js +169 -0
  59. package/src/functions/regex/reader.js +37 -0
  60. package/src/functions/regex/regexFunctions.js +140 -0
  61. package/src/functions/regex/translate.js +102 -0
  62. package/src/functions/regex/unicodeBlocks.js +194 -0
  63. package/src/functions/registry.js +140 -0
  64. package/src/functions/sequences.js +173 -0
  65. package/src/functions/serializeFunction.js +53 -0
  66. package/src/functions/signatures.js +105 -0
  67. package/src/functions/sort.js +102 -0
  68. package/src/functions/stringMatch.js +45 -0
  69. package/src/functions/strings.js +194 -0
  70. package/src/functions/stringsCore.js +66 -0
  71. package/src/functions/support.js +106 -0
  72. package/src/functions/textDecoding.js +117 -0
  73. package/src/functions/ucaCollation.js +127 -0
  74. package/src/functions/unparsedText.js +71 -0
  75. package/src/functions/uri.js +166 -0
  76. package/src/functions/xqueryModule.js +24 -0
  77. package/src/index.js +38 -0
  78. package/src/items/array.js +84 -0
  79. package/src/items/function.js +45 -0
  80. package/src/items/map.js +120 -0
  81. package/src/items/mapKey.js +82 -0
  82. package/src/serialize/index.js +129 -0
  83. package/src/serialize/markup/content.js +74 -0
  84. package/src/serialize/markup/dialect.js +178 -0
  85. package/src/serialize/markup/htmlData.js +70 -0
  86. package/src/serialize/markup/namespaces.js +145 -0
  87. package/src/serialize/markup/prolog.js +91 -0
  88. package/src/serialize/markup/writer.js +183 -0
  89. package/src/serialize/methods/adaptive.js +131 -0
  90. package/src/serialize/methods/json.js +158 -0
  91. package/src/serialize/methods/text.js +43 -0
  92. package/src/serialize/output/buffer.js +48 -0
  93. package/src/serialize/output/encoding.js +146 -0
  94. package/src/serialize/output/expander.js +204 -0
  95. package/src/serialize/params/fromElement.js +141 -0
  96. package/src/serialize/params/fromMap.js +128 -0
  97. package/src/serialize/params/names.js +69 -0
  98. package/src/serialize/params/settings.js +153 -0
  99. package/src/serialize/params/values.js +163 -0
  100. package/src/serialize/sequence.js +111 -0
  101. package/src/xdm/arithmetic.js +155 -0
  102. package/src/xdm/atomic.js +142 -0
  103. package/src/xdm/binary.js +92 -0
  104. package/src/xdm/calendar.js +71 -0
  105. package/src/xdm/cast.js +176 -0
  106. package/src/xdm/compare.js +192 -0
  107. package/src/xdm/datetime.js +206 -0
  108. package/src/xdm/decimal.js +200 -0
  109. package/src/xdm/duration.js +128 -0
  110. package/src/xdm/generalCompare.js +152 -0
  111. package/src/xdm/index.js +47 -0
  112. package/src/xdm/lexical.js +126 -0
  113. package/src/xdm/nodes.js +170 -0
  114. package/src/xdm/numeric.js +122 -0
  115. package/src/xdm/qname.js +65 -0
  116. package/src/xdm/strings.js +52 -0
  117. package/src/xdm/temporalArithmetic.js +180 -0
  118. package/src/xdm/timeline.js +68 -0
  119. package/src/xdm/types.js +178 -0
  120. package/src/xpath/eval/atomics.js +45 -0
  121. package/src/xpath/eval/axes.js +212 -0
  122. package/src/xpath/eval/calls.js +168 -0
  123. package/src/xpath/eval/coercion.js +146 -0
  124. package/src/xpath/eval/compiler.js +71 -0
  125. package/src/xpath/eval/constructors.js +129 -0
  126. package/src/xpath/eval/decimalFormats.js +93 -0
  127. package/src/xpath/eval/descendantMemo.js +77 -0
  128. package/src/xpath/eval/documentOrder.js +142 -0
  129. package/src/xpath/eval/domNodes.js +174 -0
  130. package/src/xpath/eval/dynamicCall.js +27 -0
  131. package/src/xpath/eval/dynamicContext.js +161 -0
  132. package/src/xpath/eval/flow.js +115 -0
  133. package/src/xpath/eval/functionItems.js +194 -0
  134. package/src/xpath/eval/groupedChildren.js +88 -0
  135. package/src/xpath/eval/inline.js +71 -0
  136. package/src/xpath/eval/namespaceNodes.js +90 -0
  137. package/src/xpath/eval/nodeTests.js +164 -0
  138. package/src/xpath/eval/operators.js +190 -0
  139. package/src/xpath/eval/paths.js +163 -0
  140. package/src/xpath/eval/positional.js +81 -0
  141. package/src/xpath/eval/predicates.js +56 -0
  142. package/src/xpath/eval/primary.js +99 -0
  143. package/src/xpath/eval/resources.js +149 -0
  144. package/src/xpath/eval/scope.js +108 -0
  145. package/src/xpath/eval/sequenceType.js +193 -0
  146. package/src/xpath/eval/staticContext.js +142 -0
  147. package/src/xpath/eval/steps.js +98 -0
  148. package/src/xpath/eval/subtype.js +154 -0
  149. package/src/xpath/eval/typeExprs.js +147 -0
  150. package/src/xpath/eval/typeNames.js +101 -0
  151. package/src/xpath/eval/uris.js +21 -0
  152. package/src/xpath/eval/values.js +64 -0
  153. package/src/xpath/index.js +217 -0
  154. package/src/xpath/syntax/ast.js +130 -0
  155. package/src/xpath/syntax/chars.js +58 -0
  156. package/src/xpath/syntax/constructors.js +144 -0
  157. package/src/xpath/syntax/expressions.js +107 -0
  158. package/src/xpath/syntax/index.js +44 -0
  159. package/src/xpath/syntax/kindTests.js +134 -0
  160. package/src/xpath/syntax/lexer.js +220 -0
  161. package/src/xpath/syntax/operators.js +111 -0
  162. package/src/xpath/syntax/paths.js +102 -0
  163. package/src/xpath/syntax/postfix.js +91 -0
  164. package/src/xpath/syntax/primaries.js +149 -0
  165. package/src/xpath/syntax/steps.js +146 -0
  166. package/src/xpath/syntax/syntaxError.js +31 -0
  167. package/src/xpath/syntax/tokenStream.js +163 -0
  168. package/src/xpath/syntax/typeAst.js +76 -0
  169. package/src/xpath/syntax/typeOperators.js +127 -0
  170. package/src/xpath/syntax/types.js +158 -0
  171. package/src/xslt/api.js +153 -0
  172. package/src/xslt/compiler/acceptance.js +178 -0
  173. package/src/xslt/compiler/accumulatorDecl.js +146 -0
  174. package/src/xslt/compiler/attributeValues.js +174 -0
  175. package/src/xslt/compiler/attributes.js +211 -0
  176. package/src/xslt/compiler/avt.js +103 -0
  177. package/src/xslt/compiler/body.js +154 -0
  178. package/src/xslt/compiler/children.js +105 -0
  179. package/src/xslt/compiler/compatibility.js +130 -0
  180. package/src/xslt/compiler/components.js +179 -0
  181. package/src/xslt/compiler/contextItem.js +95 -0
  182. package/src/xslt/compiler/decimalFormats.js +132 -0
  183. package/src/xslt/compiler/declarationKinds.js +82 -0
  184. package/src/xslt/compiler/elementInfo.js +246 -0
  185. package/src/xslt/compiler/expressions.js +175 -0
  186. package/src/xslt/compiler/globalContextItem.js +92 -0
  187. package/src/xslt/compiler/globals.js +209 -0
  188. package/src/xslt/compiler/linker.js +134 -0
  189. package/src/xslt/compiler/miscDecl.js +171 -0
  190. package/src/xslt/compiler/modeDecl.js +88 -0
  191. package/src/xslt/compiler/moduleLoading.js +111 -0
  192. package/src/xslt/compiler/modules.js +158 -0
  193. package/src/xslt/compiler/outputDecl.js +209 -0
  194. package/src/xslt/compiler/overrideLinks.js +141 -0
  195. package/src/xslt/compiler/packageChecks.js +84 -0
  196. package/src/xslt/compiler/packageRegistry.js +95 -0
  197. package/src/xslt/compiler/packageTable.js +181 -0
  198. package/src/xslt/compiler/packageVersions.js +143 -0
  199. package/src/xslt/compiler/packages.js +138 -0
  200. package/src/xslt/compiler/registry.js +234 -0
  201. package/src/xslt/compiler/staticStage.js +197 -0
  202. package/src/xslt/compiler/staticVariables.js +93 -0
  203. package/src/xslt/compiler/stylesheet.js +205 -0
  204. package/src/xslt/compiler/tailPosition.js +74 -0
  205. package/src/xslt/compiler/templatesDecl.js +180 -0
  206. package/src/xslt/compiler/useAccumulators.js +76 -0
  207. package/src/xslt/compiler/useWhen.js +94 -0
  208. package/src/xslt/compiler/visibility.js +160 -0
  209. package/src/xslt/index.js +11 -0
  210. package/src/xslt/instructions/analyzeString.js +137 -0
  211. package/src/xslt/instructions/assert.js +72 -0
  212. package/src/xslt/instructions/attributeSets.js +115 -0
  213. package/src/xslt/instructions/conditional.js +99 -0
  214. package/src/xslt/instructions/control.js +123 -0
  215. package/src/xslt/instructions/copy.js +151 -0
  216. package/src/xslt/instructions/elements.js +161 -0
  217. package/src/xslt/instructions/evaluate.js +189 -0
  218. package/src/xslt/instructions/fork.js +35 -0
  219. package/src/xslt/instructions/grouping.js +106 -0
  220. package/src/xslt/instructions/index.js +94 -0
  221. package/src/xslt/instructions/iterate.js +182 -0
  222. package/src/xslt/instructions/literal.js +142 -0
  223. package/src/xslt/instructions/maps.js +72 -0
  224. package/src/xslt/instructions/merge.js +190 -0
  225. package/src/xslt/instructions/number.js +205 -0
  226. package/src/xslt/instructions/output.js +102 -0
  227. package/src/xslt/instructions/params.js +110 -0
  228. package/src/xslt/instructions/resultDocument.js +143 -0
  229. package/src/xslt/instructions/sort.js +114 -0
  230. package/src/xslt/instructions/sourceDocument.js +73 -0
  231. package/src/xslt/instructions/templates.js +153 -0
  232. package/src/xslt/instructions/text.js +151 -0
  233. package/src/xslt/instructions/try.js +186 -0
  234. package/src/xslt/instructions/variables.js +119 -0
  235. package/src/xslt/names.js +204 -0
  236. package/src/xslt/patterns/axisOrigins.js +121 -0
  237. package/src/xslt/patterns/compile.js +151 -0
  238. package/src/xslt/patterns/general.js +132 -0
  239. package/src/xslt/patterns/grammar.js +190 -0
  240. package/src/xslt/patterns/modes.js +153 -0
  241. package/src/xslt/patterns/paths.js +130 -0
  242. package/src/xslt/patterns/patternChecks.js +29 -0
  243. package/src/xslt/patterns/steps.js +162 -0
  244. package/src/xslt/processor.js +178 -0
  245. package/src/xslt/runtime/accumulatorFunctions.js +48 -0
  246. package/src/xslt/runtime/accumulators.js +174 -0
  247. package/src/xslt/runtime/apply.js +153 -0
  248. package/src/xslt/runtime/atomicKeys.js +36 -0
  249. package/src/xslt/runtime/availability.js +162 -0
  250. package/src/xslt/runtime/baseUri.js +64 -0
  251. package/src/xslt/runtime/compilerHook.js +27 -0
  252. package/src/xslt/runtime/conditional.js +110 -0
  253. package/src/xslt/runtime/context.js +106 -0
  254. package/src/xslt/runtime/copy.js +123 -0
  255. package/src/xslt/runtime/copyFunctions.js +71 -0
  256. package/src/xslt/runtime/copyOrigins.js +60 -0
  257. package/src/xslt/runtime/defaultMethod.js +42 -0
  258. package/src/xslt/runtime/documentContent.js +93 -0
  259. package/src/xslt/runtime/dynamicXPath.js +90 -0
  260. package/src/xslt/runtime/functionCache.js +65 -0
  261. package/src/xslt/runtime/functionCall.js +57 -0
  262. package/src/xslt/runtime/functions.js +191 -0
  263. package/src/xslt/runtime/groups.js +108 -0
  264. package/src/xslt/runtime/invocation.js +156 -0
  265. package/src/xslt/runtime/iteration.js +107 -0
  266. package/src/xslt/runtime/keys.js +141 -0
  267. package/src/xslt/runtime/lazy.js +97 -0
  268. package/src/xslt/runtime/locations.js +86 -0
  269. package/src/xslt/runtime/machine.js +137 -0
  270. package/src/xslt/runtime/merging.js +167 -0
  271. package/src/xslt/runtime/namespaceScope.js +141 -0
  272. package/src/xslt/runtime/numberFormat.js +89 -0
  273. package/src/xslt/runtime/numbering.js +168 -0
  274. package/src/xslt/runtime/params.js +59 -0
  275. package/src/xslt/runtime/properties.js +103 -0
  276. package/src/xslt/runtime/rawResults.js +82 -0
  277. package/src/xslt/runtime/sequenceReceiver.js +102 -0
  278. package/src/xslt/runtime/snapshot.js +66 -0
  279. package/src/xslt/runtime/sortKeys.js +119 -0
  280. package/src/xslt/runtime/streamAvailable.js +60 -0
  281. package/src/xslt/runtime/strip.js +102 -0
  282. package/src/xslt/runtime/transformFunction.js +169 -0
  283. package/src/xslt/runtime/transformation.js +168 -0
  284. package/src/xslt/runtime/transformationContext.js +85 -0
  285. package/src/xslt/runtime/treeReceiver.js +194 -0
  286. package/src/xslt/runtime/values.js +92 -0
@@ -0,0 +1,217 @@
1
+ /**
2
+ * XPath 3.1: compile an expression once, evaluate it many times.
3
+ *
4
+ * ```js
5
+ * import { compileXPath, evaluateXPath } from "@tradik/xslt3/xpath";
6
+ * const expr = compileXPath("//item[@price > $min]", { variables: ["min"] });
7
+ * expr.evaluate(document, { variables: { min: 10 } }); // [Element, ...]
8
+ * evaluateXPath("sum(1 to 10)", null); // [AtomicValue xs:integer 55]
9
+ * ```
10
+ *
11
+ * Results are arrays of XDM items: DOM nodes, AtomicValue (xdm/atomic.js),
12
+ * XdmMap, XdmArray and FunctionItem (items/). JavaScript values given as
13
+ * variables or context item are converted (see eval/values.js).
14
+ *
15
+ * @module @tradik/xslt3/xpath
16
+ */
17
+
18
+ import { XPathError } from "../errors.js";
19
+ import { coreFunctions } from "../functions/core.js";
20
+ import {
21
+ createFunctionLibrary,
22
+ FunctionLibrary,
23
+ } from "../functions/registry.js";
24
+ import { compileNode, withinLimits } from "./eval/compiler.js";
25
+ import { createDescendantMemo } from "./eval/descendantMemo.js";
26
+ import { createDynamicContext } from "./eval/dynamicContext.js";
27
+ import { rootScope } from "./eval/scope.js";
28
+ import { createStaticContext, variableKey } from "./eval/staticContext.js";
29
+ import { toSequence } from "./eval/values.js";
30
+ import { parseXPath } from "./syntax/index.js";
31
+
32
+ /** The library of the built-in functions. */
33
+ export const defaultFunctionLibrary = createFunctionLibrary(coreFunctions);
34
+
35
+ /**
36
+ * @typedef {object} StaticOptions
37
+ * @property {object|Map|Array<{prefix: string, uri: string}>} [namespaces]
38
+ * - Prefix bindings added to the predeclared ones (xml, xs, xsi, fn,
39
+ * map, array, math, err); the prefix "" sets the default element namespace
40
+ * @property {string} [defaultElementNamespace] - Default element/type namespace
41
+ * @property {string} [defaultFunctionNamespace] - Default: the fn namespace
42
+ * @property {string[]|object} [variables] - Names of the external
43
+ * variables (`name`, `prefix:name` or `Q{uri}name`), or an object whose
44
+ * keys are the names
45
+ * @property {Array|FunctionLibrary} [functions] - Function definitions or
46
+ * modules (arrays of definitions) added to the built-in ones, or a
47
+ * complete library
48
+ * @property {boolean} [backwardsCompatible] - XPath 1.0 compatibility mode
49
+ * @property {string} [baseUri] - Static base URI
50
+ * @property {string} [defaultCollation] - Only the codepoint collation
51
+ * @property {Array<object>|object|Map} [decimalFormats] - Decimal formats
52
+ * for fn:format-number: an array of definitions with a `name` (none for
53
+ * the default format), or definitions by name; properties in camelCase
54
+ * or as xsl:decimal-format attribute names (see eval/decimalFormats.js)
55
+ */
56
+
57
+ /**
58
+ * @typedef {object} DynamicOptions
59
+ * @property {object} [variables] - Values of the external variables by name
60
+ * @property {number} [implicitTimezone] - Minutes, default 0 (UTC)
61
+ * @property {Date|import("../xdm/datetime.js").DateTimeValue} [currentDateTime]
62
+ * @property {(uri: string) => Node} [documentLoader] - Loads fn:doc URIs
63
+ * (absolute, resolved against the base URI)
64
+ * @property {(uri: string) => (string|Uint8Array|ArrayBuffer|{content: string|Uint8Array|ArrayBuffer, encoding?: string, mediaType?: string})} [textLoader]
65
+ * - Loads the text resources of fn:unparsed-text, fn:unparsed-text-lines,
66
+ * fn:unparsed-text-available and fn:json-doc (absolute URIs, resolved
67
+ * against the base URI); bytes are decoded by BOM, `encoding` (external
68
+ * information), the XML declaration of an XML `mediaType`, the
69
+ * function's encoding argument, else UTF-8. Return null or throw when
70
+ * there is no resource (FOUT1170). Default: no resources at all;
71
+ * `readFileUri` (exported) reads `file:` URIs in Node.js, for trusted
72
+ * expressions only
73
+ * @property {(text: string, baseUri?: string) => Document} [xmlParser]
74
+ * - Parses the strings of fn:parse-xml and fn:parse-xml-fragment; throws
75
+ * when the text is not well-formed. Default: `globalThis.DOMParser`,
76
+ * FODC0006 without it
77
+ * @property {(uri: string|null) => Array|null} [collections] - The items
78
+ * of fn:collection by absolute URI (null: the default collection);
79
+ * fn:uri-collection gives their document URIs (xs:anyURI items are
80
+ * returned as they are). Null for an unknown collection (FODC0002).
81
+ * Default: no collections
82
+ * @property {(value: Array, label: string) => void} [trace] - fn:trace output
83
+ * @property {() => Document} [createDocument] - Creates the empty
84
+ * documents in which functions build new nodes (default: from the DOM of
85
+ * the context item, else of the global document)
86
+ * @property {import("./eval/documentOrder.js").DocumentOrder} [documentOrder]
87
+ * - Document order cache to share between evaluations over unchanged trees
88
+ */
89
+
90
+ /**
91
+ * @param {StaticOptions["functions"]} functions
92
+ * @returns {FunctionLibrary}
93
+ */
94
+ function libraryOf(functions) {
95
+ if (functions instanceof FunctionLibrary) return functions;
96
+ if (!functions || functions.length === 0) return defaultFunctionLibrary;
97
+ return defaultFunctionLibrary.extend(...functions);
98
+ }
99
+
100
+ /**
101
+ * @param {string[]|object|undefined} variables
102
+ * @returns {string[]} the variable names
103
+ */
104
+ const variableNames = (variables) =>
105
+ Array.isArray(variables) ? variables : Object.keys(variables ?? {});
106
+
107
+ /**
108
+ * Compiles an XPath 3.1 expression.
109
+ * @param {string} expression
110
+ * @param {StaticOptions} [options]
111
+ * @returns {{evaluate: (contextItem?: *, options?: DynamicOptions) => Array}}
112
+ * the compiled expression; `evaluate` takes the context item (undefined
113
+ * for none) and returns the result sequence
114
+ * @throws {XPathError} static errors: XPST0003 (syntax), XPST0008,
115
+ * XPST0017, XPST0051, XPST0080, XPST0081...; XPDY0130 when the
116
+ * expression is nested too deeply
117
+ */
118
+ export function compileXPath(expression, options = {}) {
119
+ const sc = createStaticContext(options, libraryOf(options.functions));
120
+ const keys = variableNames(options.variables).map((name) =>
121
+ variableKey(name, sc),
122
+ );
123
+ const evaluator = withinLimits(() =>
124
+ compileNode(parseXPath(expression), rootScope(sc, keys)),
125
+ );
126
+ return {
127
+ evaluate(contextItem, dynamicOptions = {}) {
128
+ const values = new Map();
129
+ for (const [name, value] of Object.entries(
130
+ dynamicOptions.variables ?? {},
131
+ )) {
132
+ values.set(variableKey(name, sc), toSequence(value));
133
+ }
134
+ let env = null;
135
+ for (const key of keys) env = { value: values.get(key), next: env };
136
+ const focus = toSequence(contextItem);
137
+ if (focus.length > 1) {
138
+ throw new XPathError("XPTY0004", "The context item must be one item");
139
+ }
140
+ const ctx = {
141
+ item: focus[0],
142
+ position: 1,
143
+ size: 1,
144
+ env,
145
+ dyn: createDynamicContext(sc, dynamicOptions, focus[0]),
146
+ };
147
+ ctx.dyn.descendantMemo = createDescendantMemo();
148
+ return withinLimits(() => evaluator(ctx));
149
+ },
150
+ };
151
+ }
152
+
153
+ /** Static options other than `variables`; any of them disables the cache. */
154
+ const STATIC_OPTIONS = [
155
+ "namespaces",
156
+ "defaultElementNamespace",
157
+ "defaultFunctionNamespace",
158
+ "functions",
159
+ "backwardsCompatible",
160
+ "baseUri",
161
+ "defaultCollation",
162
+ "decimalFormats",
163
+ ];
164
+
165
+ /** Most compiled expressions kept by evaluateXPath. */
166
+ export const COMPILED_CACHE_SIZE = 64;
167
+
168
+ /** Compiled expressions of evaluateXPath by key, least recently used first. */
169
+ const compiledCache = new Map();
170
+
171
+ /**
172
+ * The compiled expression of evaluateXPath, from a small LRU cache when
173
+ * the only static option is `variables` (the names are part of the key):
174
+ * other static options can be objects that change between calls, so their
175
+ * expressions are compiled every time.
176
+ * @param {string} expression
177
+ * @param {StaticOptions} options
178
+ * @returns {ReturnType<typeof compileXPath>}
179
+ */
180
+ function compiledFor(expression, options) {
181
+ const names = variableNames(options.variables);
182
+ const compile = () =>
183
+ compileXPath(expression, { ...options, variables: names });
184
+ if (STATIC_OPTIONS.some((name) => options[name] !== undefined)) {
185
+ return compile();
186
+ }
187
+ const key = JSON.stringify([expression, names]);
188
+ let compiled = compiledCache.get(key);
189
+ if (compiled) compiledCache.delete(key);
190
+ else {
191
+ compiled = compile();
192
+ if (compiledCache.size >= COMPILED_CACHE_SIZE) {
193
+ compiledCache.delete(compiledCache.keys().next().value);
194
+ }
195
+ }
196
+ compiledCache.set(key, compiled);
197
+ return compiled;
198
+ }
199
+
200
+ /**
201
+ * Compiles and evaluates an expression; the names of `options.variables`
202
+ * are the external variables. Without other static options, the compiled
203
+ * expression is kept in a small LRU cache (COMPILED_CACHE_SIZE
204
+ * expressions) and reused by the next calls with the same text and
205
+ * variable names; for repeated evaluations, compileXPath once is still
206
+ * the faster way.
207
+ * @param {string} expression
208
+ * @param {*} [contextItem] - Context item, undefined or null for none
209
+ * @param {StaticOptions & DynamicOptions} [options]
210
+ * @returns {Array} the result sequence
211
+ */
212
+ export function evaluateXPath(expression, contextItem, options = {}) {
213
+ return compiledFor(expression, options).evaluate(contextItem, options);
214
+ }
215
+
216
+ export { createFunctionLibrary, FunctionLibrary, XPathError };
217
+ export { readFileUri } from "./eval/resources.js";
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The XPath 3.1 abstract syntax tree: expression nodes. Node tests and
3
+ * sequence types are described in typeAst.js.
4
+ *
5
+ * Every node is a plain object `{ type, ...fields, start, end }`, where
6
+ * `start` and `end` are offsets (UTF-16 code units) into the expression.
7
+ * Parentheses leave no node: `(E)` is the node of E, `()` an EmptySequence.
8
+ * Names stay as written ({@link QName}); the evaluator resolves prefixes and
9
+ * default namespaces. Numeric literals keep their lexical form, so decimals
10
+ * lose no precision before the data model sees them.
11
+ *
12
+ * @module @tradik/xslt3/xpath/syntax/ast
13
+ */
14
+
15
+ /**
16
+ * A name as written: `local`, `prefix:local` or `Q{uri}local`.
17
+ *
18
+ * @typedef {object} QName
19
+ * @property {string|null} prefix - Prefix, null when absent
20
+ * @property {string} local - Local part
21
+ * @property {string|null} uri - Namespace of an EQName `Q{uri}local`, else null
22
+ */
23
+
24
+ /**
25
+ * Any expression node.
26
+ *
27
+ * @typedef {SequenceExpr|EmptySequence|ForExpr|LetExpr|QuantifiedExpr|IfExpr|
28
+ * LogicalExpr|ComparisonExpr|StringConcatExpr|RangeExpr|ArithmeticExpr|
29
+ * SetExpr|InstanceOfExpr|TreatExpr|CastableExpr|CastExpr|ArrowExpr|
30
+ * UnaryExpr|SimpleMapExpr|PathExpr|AxisStep|FilterExpr|DynamicFunctionCall|
31
+ * Lookup|UnaryLookup|NumericLiteral|StringLiteral|VarRef|ContextItemExpr|
32
+ * FunctionCall|NamedFunctionRef|InlineFunctionExpr|MapConstructor|
33
+ * SquareArrayConstructor|CurlyArrayConstructor} Expr
34
+ */
35
+
36
+ /** @typedef {{type: "SequenceExpr", items: Expr[], start: number, end: number}} SequenceExpr `E1, E2, ...` (two or more items) */
37
+ /** @typedef {{type: "EmptySequence", start: number, end: number}} EmptySequence `()` */
38
+ /** @typedef {{type: "Binding", name: QName, expr: Expr, start: number, end: number}} Binding `$name in E` or `$name := E` */
39
+ /** @typedef {{type: "ForExpr", bindings: Binding[], returnExpr: Expr, start: number, end: number}} ForExpr `for $a in A, $b in B return R` */
40
+ /** @typedef {{type: "LetExpr", bindings: Binding[], returnExpr: Expr, start: number, end: number}} LetExpr `let $a := A, $b := B return R` */
41
+ /** @typedef {{type: "QuantifiedExpr", quantifier: "some"|"every", bindings: Binding[], satisfies: Expr, start: number, end: number}} QuantifiedExpr */
42
+ /** @typedef {{type: "IfExpr", condition: Expr, thenExpr: Expr, elseExpr: Expr, start: number, end: number}} IfExpr */
43
+ /** @typedef {{type: "LogicalExpr", operator: "or"|"and", left: Expr, right: Expr, start: number, end: number}} LogicalExpr */
44
+
45
+ /**
46
+ * Comparison; `kind` is "general" (= != < <= > >=), "value" (eq ne lt le gt
47
+ * ge) or "node" (is << >>). Comparisons do not chain.
48
+ *
49
+ * @typedef {{type: "ComparisonExpr", kind: "general"|"value"|"node", operator: string, left: Expr, right: Expr, start: number, end: number}} ComparisonExpr
50
+ */
51
+
52
+ /** @typedef {{type: "StringConcatExpr", left: Expr, right: Expr, start: number, end: number}} StringConcatExpr `A || B` */
53
+ /** @typedef {{type: "RangeExpr", left: Expr, right: Expr, start: number, end: number}} RangeExpr `A to B` */
54
+ /** @typedef {{type: "ArithmeticExpr", operator: "+"|"-"|"*"|"div"|"idiv"|"mod", left: Expr, right: Expr, start: number, end: number}} ArithmeticExpr */
55
+ /** @typedef {{type: "SetExpr", operator: "union"|"intersect"|"except", left: Expr, right: Expr, start: number, end: number}} SetExpr `|` is stored as "union" */
56
+ /** @typedef {{type: "InstanceOfExpr", expr: Expr, sequenceType: import("./typeAst.js").SequenceType, start: number, end: number}} InstanceOfExpr */
57
+ /** @typedef {{type: "TreatExpr", expr: Expr, sequenceType: import("./typeAst.js").SequenceType, start: number, end: number}} TreatExpr */
58
+ /** @typedef {{type: "CastableExpr", expr: Expr, targetType: QName, emptyAllowed: boolean, start: number, end: number}} CastableExpr `E castable as T?` */
59
+ /** @typedef {{type: "CastExpr", expr: Expr, targetType: QName, emptyAllowed: boolean, start: number, end: number}} CastExpr `E cast as T?` */
60
+
61
+ /**
62
+ * `E => f(args)`, `E => $f(args)` or `E => (expr)(args)`: exactly one of
63
+ * `functionName` and `functionExpr` is set. Arguments do not include E.
64
+ *
65
+ * @typedef {{type: "ArrowExpr", expr: Expr, functionName: QName|null, functionExpr: Expr|null, arguments: Array<Expr|ArgumentPlaceholder>, start: number, end: number}} ArrowExpr
66
+ */
67
+
68
+ /** @typedef {{type: "UnaryExpr", operator: "+"|"-", operand: Expr, start: number, end: number}} UnaryExpr `--E` nests two nodes */
69
+ /** @typedef {{type: "SimpleMapExpr", left: Expr, right: Expr, start: number, end: number}} SimpleMapExpr `A ! B` */
70
+
71
+ /**
72
+ * A path with at least one `/` or `//`. `absolute` is true for a leading
73
+ * slash; a lone `/` has no steps. `//` is expanded to
74
+ * `/descendant-or-self::node()/`, so `//a` has two steps.
75
+ *
76
+ * @typedef {{type: "PathExpr", absolute: boolean, steps: Expr[], start: number, end: number}} PathExpr
77
+ */
78
+
79
+ /**
80
+ * An axis step, abbreviated or not: `@a` has axis "attribute", `..` is
81
+ * `parent::node()`, and a step without axis takes "child", or "attribute"
82
+ * for an attribute()/schema-attribute() test, or "namespace" for
83
+ * namespace-node() (XPath 3.1 section 3.3.5).
84
+ *
85
+ * @typedef {{type: "AxisStep", axis: string, nodeTest: import("./typeAst.js").NodeTest, predicates: Expr[], start: number, end: number}} AxisStep
86
+ */
87
+
88
+ /** @typedef {{type: "FilterExpr", base: Expr, predicate: Expr, start: number, end: number}} FilterExpr `E[P]` on a primary expression */
89
+ /** @typedef {{type: "DynamicFunctionCall", functionExpr: Expr, arguments: Array<Expr|ArgumentPlaceholder>, start: number, end: number}} DynamicFunctionCall `$f(1, ?)` */
90
+
91
+ /**
92
+ * Lookup `E?key`. `keyKind` "name" (`key` is the NCName), "integer" (`key`
93
+ * is the lexical integer), "expr" (`key` is the parenthesized Expr) or
94
+ * "wildcard" (`?*`, `key` null).
95
+ *
96
+ * @typedef {{type: "Lookup", base: Expr, keyKind: "name"|"integer"|"expr"|"wildcard", key: string|Expr|null, start: number, end: number}} Lookup
97
+ */
98
+
99
+ /** @typedef {{type: "UnaryLookup", keyKind: "name"|"integer"|"expr"|"wildcard", key: string|Expr|null, start: number, end: number}} UnaryLookup `?key` on the context item */
100
+ /** @typedef {{type: "NumericLiteral", kind: "integer"|"decimal"|"double", value: string, start: number, end: number}} NumericLiteral `value` is the lexical form */
101
+ /** @typedef {{type: "StringLiteral", value: string, start: number, end: number}} StringLiteral Doubled quotes already unescaped */
102
+ /** @typedef {{type: "VarRef", name: QName, start: number, end: number}} VarRef */
103
+ /** @typedef {{type: "ContextItemExpr", start: number, end: number}} ContextItemExpr `.` */
104
+ /** @typedef {{type: "FunctionCall", name: QName, arguments: Array<Expr|ArgumentPlaceholder>, start: number, end: number}} FunctionCall With a placeholder it is a partial application */
105
+ /** @typedef {{type: "ArgumentPlaceholder", start: number, end: number}} ArgumentPlaceholder `?` as an argument */
106
+ /** @typedef {{type: "NamedFunctionRef", name: QName, arity: number, start: number, end: number}} NamedFunctionRef `name#arity` */
107
+ /** @typedef {{type: "Param", name: QName, sequenceType: import("./typeAst.js").SequenceType|null, start: number, end: number}} Param */
108
+ /** @typedef {{type: "InlineFunctionExpr", params: Param[], returnType: import("./typeAst.js").SequenceType|null, body: Expr, start: number, end: number}} InlineFunctionExpr An empty body `{}` is an EmptySequence */
109
+ /** @typedef {{type: "MapEntry", key: Expr, value: Expr, start: number, end: number}} MapEntry */
110
+ /** @typedef {{type: "MapConstructor", entries: MapEntry[], start: number, end: number}} MapConstructor `map { k : v, ... }` */
111
+ /** @typedef {{type: "SquareArrayConstructor", members: Expr[], start: number, end: number}} SquareArrayConstructor `[a, b]`: one member per expression */
112
+ /** @typedef {{type: "CurlyArrayConstructor", expr: Expr, start: number, end: number}} CurlyArrayConstructor `array { E }`: one member per item of E */
113
+
114
+ /**
115
+ * Creates a node with the fields in a stable order: type, fields, offsets.
116
+ *
117
+ * @template {object} F
118
+ * @param {string} type - Node type
119
+ * @param {F} fields - Node-specific fields
120
+ * @param {number} start - Offset of the first character
121
+ * @param {number} end - Offset after the last character
122
+ * @returns {{type: string, start: number, end: number} & F} The node
123
+ */
124
+ export function makeNode(type, fields, start, end) {
125
+ // Object.assign: several times faster than a spread in a literal here
126
+ const node = Object.assign({ type }, fields);
127
+ node.start = start;
128
+ node.end = end;
129
+ return node;
130
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Character classes of the XPath 3.1 lexical structure (Appendix A.2).
3
+ *
4
+ * Names use the XML 1.0 (fifth edition) name characters, which are also
5
+ * those of XML 1.1, so the xml-version constraint (A.1.2) needs no switch.
6
+ *
7
+ * @module @tradik/xslt3/xpath/syntax/chars
8
+ */
9
+
10
+ const NAME_START =
11
+ "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D" +
12
+ "\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF" +
13
+ "\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\u{10000}-\\u{EFFFF}";
14
+ const NAME_CHAR = `${NAME_START}\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040`;
15
+
16
+ /** NCName (sticky). */
17
+ // The combining marks U+0300-U+036F are NameChars on their own, as XML says.
18
+ // eslint-disable-next-line no-misleading-character-class
19
+ export const NCNAME = new RegExp(`[${NAME_START}][${NAME_CHAR}]*`, "uy");
20
+ /** One NameStartChar without ":" (sticky). */
21
+ export const NAME_START_CHAR = new RegExp(`[${NAME_START}]`, "uy");
22
+ /** IntegerLiteral, DecimalLiteral or DoubleLiteral (sticky). */
23
+ export const NUMBER = /(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][+-]?[0-9]+)?/y;
24
+ /** XML whitespace (sticky). */
25
+ export const WHITESPACE = /[ \t\r\n]+/y;
26
+
27
+ /**
28
+ * Returns the match of a sticky regular expression at an offset.
29
+ *
30
+ * @param {RegExp} pattern - Sticky (`y`) pattern
31
+ * @param {string} text - Source
32
+ * @param {number} offset - Where the match must start
33
+ * @returns {string|null} The matched text, or null
34
+ */
35
+ export function matchAt(pattern, text, offset) {
36
+ pattern.lastIndex = offset;
37
+ const match = pattern.exec(text);
38
+ return match ? match[0] : null;
39
+ }
40
+
41
+ /**
42
+ * @param {string} text - Candidate name
43
+ * @returns {boolean} Whether the whole text is an NCName
44
+ */
45
+ export function isNCName(text) {
46
+ return matchAt(NCNAME, text, 0) === text;
47
+ }
48
+
49
+ /**
50
+ * Whitespace normalization of xs:anyURI and normalize-space(): runs of
51
+ * whitespace become one space, leading and trailing ones are removed.
52
+ *
53
+ * @param {string} text - Text
54
+ * @returns {string} Normalized text
55
+ */
56
+ export function normalizeSpace(text) {
57
+ return text.replace(/[ \t\r\n]+/g, " ").trim();
58
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Parser of the XPath 3.1 constructors: inline function expressions
3
+ * (productions [69] InlineFunctionExpr, [10] ParamList, [11] Param), maps
4
+ * ([69] MapConstructor to [72] MapValueExpr) and arrays ([73]
5
+ * ArrayConstructor to [75] CurlyArrayConstructor) of Appendix A.1.
6
+ *
7
+ * @module @tradik/xslt3/xpath/syntax/constructors
8
+ */
9
+
10
+ import { makeNode } from "./ast.js";
11
+ import { parseExpr, parseExprSingle } from "./expressions.js";
12
+ import { qName } from "./tokenStream.js";
13
+ import { parseSequenceType } from "./types.js";
14
+
15
+ /** @typedef {import("./tokenStream.js").TokenStream} TokenStream */
16
+ /** @typedef {import("./ast.js").Expr} Expr */
17
+
18
+ /**
19
+ * EnclosedExpr ::= "{" Expr? "}"; an empty one gives an EmptySequence.
20
+ *
21
+ * @param {TokenStream} p - Tokens, at "{"
22
+ * @returns {{expr: Expr, end: number}} The content and the offset after "}"
23
+ */
24
+ function parseEnclosedExpr(p) {
25
+ const open = p.expectSymbol("{");
26
+ const close = p.acceptSymbol("}");
27
+ if (close) {
28
+ return {
29
+ expr: makeNode("EmptySequence", {}, open.start, close.end),
30
+ end: close.end,
31
+ };
32
+ }
33
+ const expr = parseExpr(p);
34
+ return { expr, end: p.expectSymbol("}").end };
35
+ }
36
+
37
+ /**
38
+ * Reads `item ("," item)*` up to a closing symbol, which is consumed.
39
+ *
40
+ * @template T
41
+ * @param {TokenStream} p - Tokens, after the opening symbol
42
+ * @param {string} close - Closing symbol
43
+ * @param {(p: TokenStream) => T} parseItem - Parser of one item
44
+ * @returns {{items: T[], end: number}} The items and the offset after close
45
+ */
46
+ function parseList(p, close, parseItem) {
47
+ const items = [];
48
+ if (!p.isSymbol(close)) {
49
+ do items.push(parseItem(p));
50
+ while (p.acceptSymbol(","));
51
+ }
52
+ return { items, end: p.expectSymbol(close).end };
53
+ }
54
+
55
+ /**
56
+ * InlineFunctionExpr ::= "function" "(" ParamList? ")" ("as" SequenceType)?
57
+ * FunctionBody
58
+ *
59
+ * @param {TokenStream} p - Tokens, at "function"
60
+ * @returns {import("./ast.js").InlineFunctionExpr} The function
61
+ */
62
+ export function parseInlineFunctionExpr(p) {
63
+ const start = p.next().start;
64
+ p.expectSymbol("(");
65
+ const { items: params } = parseList(p, ")", parseParam);
66
+ const returnType = p.acceptKeyword("as") ? parseSequenceType(p) : null;
67
+ const { expr: body, end } = parseEnclosedExpr(p);
68
+ return makeNode(
69
+ "InlineFunctionExpr",
70
+ { params, returnType, body },
71
+ start,
72
+ end,
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Param ::= "$" EQName TypeDeclaration?
78
+ *
79
+ * @param {TokenStream} p - Tokens
80
+ * @returns {import("./ast.js").Param} The parameter
81
+ */
82
+ function parseParam(p) {
83
+ const dollar = p.expectSymbol("$");
84
+ const name = p.expectName("a parameter name");
85
+ const sequenceType = p.acceptKeyword("as") ? parseSequenceType(p) : null;
86
+ const end = sequenceType ? sequenceType.end : name.end;
87
+ return makeNode(
88
+ "Param",
89
+ { name: qName(name), sequenceType },
90
+ dollar.start,
91
+ end,
92
+ );
93
+ }
94
+
95
+ /**
96
+ * MapConstructor ::= "map" "{" (MapConstructorEntry ("," ...)*)? "}"
97
+ *
98
+ * @param {TokenStream} p - Tokens, at "map"
99
+ * @returns {import("./ast.js").MapConstructor} The constructor
100
+ */
101
+ export function parseMapConstructor(p) {
102
+ const start = p.next().start;
103
+ p.expectSymbol("{");
104
+ const { items: entries, end } = parseList(p, "}", parseMapEntry);
105
+ return makeNode("MapConstructor", { entries }, start, end);
106
+ }
107
+
108
+ /**
109
+ * MapConstructorEntry ::= ExprSingle ":" ExprSingle. Note that `map{a:b}`
110
+ * reads the QName `a:b`; a key that is a name needs a space: `map{a :b}`.
111
+ *
112
+ * @param {TokenStream} p - Tokens
113
+ * @returns {import("./ast.js").MapEntry} The entry
114
+ */
115
+ function parseMapEntry(p) {
116
+ const key = parseExprSingle(p);
117
+ p.expectSymbol(":");
118
+ const value = parseExprSingle(p);
119
+ return makeNode("MapEntry", { key, value }, key.start, value.end);
120
+ }
121
+
122
+ /**
123
+ * SquareArrayConstructor ::= "[" (ExprSingle ("," ExprSingle)*)? "]"
124
+ *
125
+ * @param {TokenStream} p - Tokens, at "["
126
+ * @returns {import("./ast.js").SquareArrayConstructor} The constructor
127
+ */
128
+ export function parseSquareArrayConstructor(p) {
129
+ const start = p.next().start;
130
+ const { items: members, end } = parseList(p, "]", parseExprSingle);
131
+ return makeNode("SquareArrayConstructor", { members }, start, end);
132
+ }
133
+
134
+ /**
135
+ * CurlyArrayConstructor ::= "array" EnclosedExpr
136
+ *
137
+ * @param {TokenStream} p - Tokens, at "array"
138
+ * @returns {import("./ast.js").CurlyArrayConstructor} The constructor
139
+ */
140
+ export function parseCurlyArrayConstructor(p) {
141
+ const start = p.next().start;
142
+ const { expr, end } = parseEnclosedExpr(p);
143
+ return makeNode("CurlyArrayConstructor", { expr }, start, end);
144
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Parser of the top of the XPath 3.1 grammar: Expr (the comma operator),
3
+ * ExprSingle and the expressions introduced by a keyword (for, let, some,
4
+ * every, if). Productions [6] to [15] of XPath 3.1 Appendix A.1.
5
+ *
6
+ * Each function takes the {@link TokenStream} and returns an AST node.
7
+ *
8
+ * @module @tradik/xslt3/xpath/syntax/expressions
9
+ */
10
+
11
+ import { makeNode } from "./ast.js";
12
+ import { parseOrExpr } from "./operators.js";
13
+ import { qName } from "./tokenStream.js";
14
+
15
+ /** @typedef {import("./tokenStream.js").TokenStream} TokenStream */
16
+ /** @typedef {import("./ast.js").Expr} Expr */
17
+
18
+ /**
19
+ * Expr ::= ExprSingle ("," ExprSingle)*
20
+ *
21
+ * @param {TokenStream} p - Tokens
22
+ * @returns {Expr} A SequenceExpr for two or more items, else the item
23
+ */
24
+ export function parseExpr(p) {
25
+ const first = parseExprSingle(p);
26
+ if (!p.isSymbol(",")) return first;
27
+ const items = [first];
28
+ while (p.acceptSymbol(",")) items.push(parseExprSingle(p));
29
+ return makeNode("SequenceExpr", { items }, first.start, items.at(-1).end);
30
+ }
31
+
32
+ /**
33
+ * ExprSingle ::= ForExpr | LetExpr | QuantifiedExpr | IfExpr | OrExpr.
34
+ * A keyword starts its expression only when followed by "$" (or "(" for
35
+ * `if`); otherwise it is an ordinary name, e.g. the element `for`.
36
+ *
37
+ * @param {TokenStream} p - Tokens
38
+ * @returns {Expr} The expression
39
+ */
40
+ export function parseExprSingle(p) {
41
+ if (p.isSymbol("$", 1)) {
42
+ if (p.isKeyword("for")) {
43
+ return parseBindingExpr(p, "ForExpr", "in", "return");
44
+ }
45
+ if (p.isKeyword("let")) {
46
+ return parseBindingExpr(p, "LetExpr", ":=", "return");
47
+ }
48
+ if (p.isKeyword("some") || p.isKeyword("every")) {
49
+ return parseBindingExpr(p, "QuantifiedExpr", "in", "satisfies");
50
+ }
51
+ }
52
+ if (p.isKeyword("if") && p.isSymbol("(", 1)) return parseIfExpr(p);
53
+ return parseOrExpr(p);
54
+ }
55
+
56
+ /**
57
+ * ForExpr, LetExpr and QuantifiedExpr, which share their shape:
58
+ * keyword binding ("," binding)* final-keyword ExprSingle.
59
+ *
60
+ * @param {TokenStream} p - Tokens
61
+ * @param {"ForExpr"|"LetExpr"|"QuantifiedExpr"} type - Node type
62
+ * @param {"in"|":="} separator - Between variable and value
63
+ * @param {"return"|"satisfies"} finalKeyword - Before the result expression
64
+ * @returns {Expr} The node
65
+ */
66
+ function parseBindingExpr(p, type, separator, finalKeyword) {
67
+ const keyword = p.next();
68
+ const bindings = [];
69
+ do {
70
+ const dollar = p.expectSymbol("$");
71
+ const name = qName(p.expectName("a variable name"));
72
+ if (separator === "in") p.expectKeyword("in");
73
+ else p.expectSymbol(":=");
74
+ const expr = parseExprSingle(p);
75
+ bindings.push(makeNode("Binding", { name, expr }, dollar.start, expr.end));
76
+ } while (p.acceptSymbol(","));
77
+ p.expectKeyword(finalKeyword);
78
+ const result = parseExprSingle(p);
79
+ const fields =
80
+ type === "QuantifiedExpr"
81
+ ? { quantifier: keyword.local, bindings, satisfies: result }
82
+ : { bindings, returnExpr: result };
83
+ return makeNode(type, fields, keyword.start, result.end);
84
+ }
85
+
86
+ /**
87
+ * IfExpr ::= "if" "(" Expr ")" "then" ExprSingle "else" ExprSingle
88
+ *
89
+ * @param {TokenStream} p - Tokens
90
+ * @returns {import("./ast.js").IfExpr} The node
91
+ */
92
+ function parseIfExpr(p) {
93
+ const start = p.next().start;
94
+ p.expectSymbol("(");
95
+ const condition = parseExpr(p);
96
+ p.expectSymbol(")");
97
+ p.expectKeyword("then");
98
+ const thenExpr = parseExprSingle(p);
99
+ p.expectKeyword("else");
100
+ const elseExpr = parseExprSingle(p);
101
+ return makeNode(
102
+ "IfExpr",
103
+ { condition, thenExpr, elseExpr },
104
+ start,
105
+ elseExpr.end,
106
+ );
107
+ }