@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,44 @@
1
+ /**
2
+ * XPath 3.1 syntax: the parser from expression text to an AST.
3
+ *
4
+ * ```js
5
+ * import { parseXPath } from "./xpath/syntax/index.js";
6
+ * parseXPath("for $x in 1 to 3 return $x * 2");
7
+ * // { type: "ForExpr", bindings: [...], returnExpr: {...}, start: 0, end: 30 }
8
+ * ```
9
+ *
10
+ * The node types are documented in ast.js and typeAst.js.
11
+ *
12
+ * @module @tradik/xslt3/xpath/syntax
13
+ */
14
+
15
+ import { parseExpr } from "./expressions.js";
16
+ import { TokenStream } from "./tokenStream.js";
17
+
18
+ /** XPath versions the parser accepts as `xpathVersion`. */
19
+ const VERSIONS = new Set(["3.1"]);
20
+
21
+ /**
22
+ * Parses an XPath 3.1 expression.
23
+ *
24
+ * @param {string} expression - The expression text
25
+ * @param {object} [options] - Options
26
+ * @param {"3.1"} [options.xpathVersion] - Language version, "3.1" (the only
27
+ * one so far). TODO: "2.0" / "3.0" could reject the newer constructs
28
+ * (maps, arrays, `=>`, `!`, `||`, let, inline functions) here.
29
+ * @returns {import("./ast.js").Expr} The root of the AST
30
+ * @throws {import("../../errors.js").XPathError} XPST0003 on a syntax error
31
+ * (the message gives the offset and an excerpt), XPTY0004 for a
32
+ * processing-instruction() test whose string is not an NCName
33
+ * @throws {RangeError} For an unsupported `xpathVersion`
34
+ */
35
+ export function parseXPath(expression, options = {}) {
36
+ const { xpathVersion = "3.1" } = options;
37
+ if (!VERSIONS.has(xpathVersion)) {
38
+ throw new RangeError(`Unsupported XPath version: ${xpathVersion}`);
39
+ }
40
+ const p = new TokenStream(String(expression), { xpathVersion });
41
+ const expr = parseExpr(p);
42
+ if (p.peek().type !== "eof") p.fail("Unexpected token");
43
+ return expr;
44
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Parser of the XPath 3.1 kind tests, used both as node tests of axis steps
3
+ * and as item types: productions [83] KindTest to [100] TypeName of
4
+ * Appendix A.1.
5
+ *
6
+ * @module @tradik/xslt3/xpath/syntax/kindTests
7
+ */
8
+
9
+ import { makeNode } from "./ast.js";
10
+ import { isNCName, normalizeSpace } from "./chars.js";
11
+ import { isPlainName, qName } from "./tokenStream.js";
12
+
13
+ /** @typedef {import("./tokenStream.js").TokenStream} TokenStream */
14
+
15
+ /** Kind tests without arguments, by keyword. */
16
+ const EMPTY_TESTS = {
17
+ node: "AnyKindTest",
18
+ text: "TextTest",
19
+ comment: "CommentTest",
20
+ "namespace-node": "NamespaceNodeTest",
21
+ };
22
+
23
+ /** Parsers of the kind tests with arguments, after the "(". */
24
+ const TESTS_WITH_ARGUMENTS = {
25
+ "document-node": parseDocumentTestBody,
26
+ element: (p) => parseNamedTestBody(p, "ElementTest"),
27
+ attribute: (p) => parseNamedTestBody(p, "AttributeTest"),
28
+ "schema-element": (p) => ({
29
+ type: "SchemaElementTest",
30
+ name: qName(p.expectName()),
31
+ }),
32
+ "schema-attribute": (p) => ({
33
+ type: "SchemaAttributeTest",
34
+ name: qName(p.expectName()),
35
+ }),
36
+ "processing-instruction": parsePITestBody,
37
+ };
38
+
39
+ /**
40
+ * @param {import("./lexer.js").Token} token - Name token followed by "("
41
+ * @returns {boolean} Whether the name is the keyword of a kind test
42
+ */
43
+ export function isKindTestName(token) {
44
+ return (
45
+ isPlainName(token) &&
46
+ (Object.hasOwn(EMPTY_TESTS, token.local) ||
47
+ Object.hasOwn(TESTS_WITH_ARGUMENTS, token.local))
48
+ );
49
+ }
50
+
51
+ /**
52
+ * KindTest, e.g. `element(*, xs:untyped)` or `document-node(element(a))`.
53
+ * The caller has checked {@link isKindTestName}.
54
+ *
55
+ * @param {TokenStream} p - Tokens, at the keyword
56
+ * @returns {import("./typeAst.js").KindTest} The test
57
+ */
58
+ export function parseKindTest(p) {
59
+ const keyword = p.next();
60
+ p.expectSymbol("(");
61
+ const emptyType = EMPTY_TESTS[keyword.local];
62
+ const { type, ...fields } = emptyType
63
+ ? { type: emptyType }
64
+ : TESTS_WITH_ARGUMENTS[keyword.local](p);
65
+ const end = p.expectSymbol(")").end;
66
+ return makeNode(type, fields, keyword.start, end);
67
+ }
68
+
69
+ /**
70
+ * Inside `document-node(...)`: an optional element or schema-element test.
71
+ *
72
+ * @param {TokenStream} p - Tokens
73
+ * @returns {{type: string, elementTest: object|null}} Fields of the test
74
+ */
75
+ function parseDocumentTestBody(p) {
76
+ let elementTest = null;
77
+ if (p.isKeyword("element") || p.isKeyword("schema-element")) {
78
+ elementTest = parseKindTest(p);
79
+ } else if (!p.isSymbol(")")) {
80
+ p.fail("Expected element(...) or schema-element(...)");
81
+ }
82
+ return { type: "DocumentTest", elementTest };
83
+ }
84
+
85
+ /**
86
+ * Inside `element(...)` or `attribute(...)`: (name | "*") ("," TypeName)?,
87
+ * plus a "?" (nillable) after the type name of an element test.
88
+ *
89
+ * @param {TokenStream} p - Tokens
90
+ * @param {"ElementTest"|"AttributeTest"} type - Test type
91
+ * @returns {object} Fields of the test
92
+ */
93
+ function parseNamedTestBody(p, type) {
94
+ let name = null;
95
+ let typeName = null;
96
+ let nillable = false;
97
+ if (!p.isSymbol(")")) {
98
+ if (!p.acceptSymbol("*")) name = qName(p.expectName('a name or "*"'));
99
+ if (p.acceptSymbol(",")) {
100
+ typeName = qName(p.expectName("a type name"));
101
+ if (type === "ElementTest") nillable = Boolean(p.acceptSymbol("?"));
102
+ }
103
+ }
104
+ return type === "ElementTest"
105
+ ? { type, name, typeName, nillable }
106
+ : { type, name, typeName };
107
+ }
108
+
109
+ /**
110
+ * Inside `processing-instruction(...)`: an NCName or a string literal, whose
111
+ * whitespace-normalized value must be an NCName (else XPTY0004).
112
+ *
113
+ * @param {TokenStream} p - Tokens
114
+ * @returns {{type: "PITest", target: string|null}} Fields of the test
115
+ */
116
+ function parsePITestBody(p) {
117
+ const token = p.peek();
118
+ if (p.isSymbol(")")) return { type: "PITest", target: null };
119
+ if (token.type === "string") {
120
+ p.next();
121
+ const target = normalizeSpace(token.value);
122
+ if (!isNCName(target)) {
123
+ p.fail(
124
+ "Processing-instruction target is not an NCName",
125
+ token,
126
+ "XPTY0004",
127
+ );
128
+ }
129
+ return { type: "PITest", target };
130
+ }
131
+ if (!isPlainName(token)) p.fail("Expected an NCName or a string literal");
132
+ p.next();
133
+ return { type: "PITest", target: token.local };
134
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * XPath 3.1 lexer (XPath 3.1 Appendix A.2, lexical structure).
3
+ *
4
+ * Keywords are not reserved in XPath, so the lexer does not know them: `div`,
5
+ * `for` or `union` come out as name tokens and the parser decides from the
6
+ * context whether a name is an operator, a keyword or a name test. Names
7
+ * follow maximal munch, so `a-b` is one name while `a - b` and `a -b` are a
8
+ * subtraction (A.2.2). Comments `(: ... :)`, which nest, count as whitespace.
9
+ *
10
+ * @module @tradik/xslt3/xpath/syntax/lexer
11
+ */
12
+
13
+ import {
14
+ NAME_START_CHAR,
15
+ NCNAME,
16
+ NUMBER,
17
+ WHITESPACE,
18
+ matchAt,
19
+ normalizeSpace,
20
+ } from "./chars.js";
21
+ import { syntaxError } from "./syntaxError.js";
22
+
23
+ /**
24
+ * A token. `type` is "name" (NCName, QName or EQName: `prefix`, `local`,
25
+ * `uri`), "wildcard" (`prefix:*`, `*:local`, `Q{uri}*`; a bare `*` is the
26
+ * symbol "*"), "integer", "decimal", "double", "string" (`value` holds the
27
+ * lexical form of a number, the unescaped content of a string), "symbol"
28
+ * (`value` is the operator or punctuation) or "eof".
29
+ *
30
+ * @typedef {object} Token
31
+ * @property {"name"|"wildcard"|"integer"|"decimal"|"double"|"string"|"symbol"|"eof"} type
32
+ * @property {string} [value] - Symbol, literal content or numeric lexical form
33
+ * @property {string|null} [prefix] - Prefix of a name or wildcard
34
+ * @property {string|null} [local] - Local part of a name or wildcard
35
+ * @property {string|null} [uri] - Namespace of an EQName / `Q{uri}*`
36
+ * @property {number} start - Offset of the first character
37
+ * @property {number} end - Offset after the last character
38
+ */
39
+
40
+ /** Symbols of two characters, tried before the single-character ones. */
41
+ const LONG_SYMBOLS = new Set("!= <= >= << >> => || // :: := ..".split(" "));
42
+ const SHORT_SYMBOLS = new Set("!#$()*+,-./:<=>?@[]{}|");
43
+
44
+ /** Tokenizer over one expression; use {@link tokenize}. */
45
+ class Lexer {
46
+ /** @param {string} source - The expression */
47
+ constructor(source) {
48
+ this.source = source;
49
+ this.pos = 0;
50
+ }
51
+
52
+ /** @returns {never} Throws XPST0003 at the current offset. */
53
+ fail(message, offset = this.pos) {
54
+ throw syntaxError(this.source, offset, message);
55
+ }
56
+
57
+ /** Skips whitespace and (nested) comments. */
58
+ skipIgnorable() {
59
+ for (;;) {
60
+ const space = matchAt(WHITESPACE, this.source, this.pos);
61
+ if (space) this.pos += space.length;
62
+ else if (this.source.startsWith("(:", this.pos)) this.skipComment();
63
+ else return;
64
+ }
65
+ }
66
+
67
+ /** Skips one comment, which may contain nested comments. */
68
+ skipComment() {
69
+ const start = this.pos;
70
+ let depth = 0;
71
+ do {
72
+ if (this.pos >= this.source.length) this.fail("Unclosed comment", start);
73
+ if (this.source.startsWith("(:", this.pos)) {
74
+ depth++;
75
+ this.pos += 2;
76
+ } else if (this.source.startsWith(":)", this.pos)) {
77
+ depth--;
78
+ this.pos += 2;
79
+ } else this.pos++;
80
+ } while (depth > 0);
81
+ }
82
+
83
+ /** @returns {Token} The next token (eof at the end). */
84
+ next() {
85
+ this.skipIgnorable();
86
+ const { source, pos } = this;
87
+ const char = source[pos];
88
+ if (pos >= source.length) return { type: "eof", start: pos, end: pos };
89
+ if (char === '"' || char === "'") return this.string(char);
90
+ if (matchAt(NUMBER, source, pos)) return this.number();
91
+ if (source.startsWith("Q{", pos)) return this.bracedName();
92
+ if (matchAt(NCNAME, source, pos)) return this.name();
93
+ if (char === "*" && source[pos + 1] === ":") {
94
+ const local = matchAt(NCNAME, source, pos + 2);
95
+ if (local) {
96
+ return this.named(
97
+ "wildcard",
98
+ null,
99
+ local,
100
+ null,
101
+ pos + 2 + local.length,
102
+ );
103
+ }
104
+ }
105
+ const pair = source.slice(pos, pos + 2);
106
+ if (LONG_SYMBOLS.has(pair)) {
107
+ return this.token("symbol", { value: pair }, pos + 2);
108
+ }
109
+ if (SHORT_SYMBOLS.has(char)) {
110
+ return this.token("symbol", { value: char }, pos + 1);
111
+ }
112
+ return this.fail(`Unexpected character ${JSON.stringify(char)}`);
113
+ }
114
+
115
+ /** @returns {Token} A token from the current offset to `end`. */
116
+ token(type, fields, end) {
117
+ const token = Object.assign({ type }, fields);
118
+ token.start = this.pos;
119
+ token.end = end;
120
+ this.pos = end;
121
+ return token;
122
+ }
123
+
124
+ /** @returns {Token} A string literal; `""` / `''` stand for one quote. */
125
+ string(quote) {
126
+ let value = "";
127
+ let at = this.pos + 1;
128
+ for (;;) {
129
+ const close = this.source.indexOf(quote, at);
130
+ if (close < 0) this.fail("Unterminated string literal");
131
+ value += this.source.slice(at, close);
132
+ if (this.source[close + 1] !== quote) {
133
+ return this.token("string", { value }, close + 1);
134
+ }
135
+ value += quote;
136
+ at = close + 2;
137
+ }
138
+ }
139
+
140
+ /** @returns {Token} A numeric literal, keeping its lexical form. */
141
+ number() {
142
+ const text = matchAt(NUMBER, this.source, this.pos);
143
+ const end = this.pos + text.length;
144
+ // A.2.2: a numeric literal must not run into a name ("10div") or into
145
+ // another numeric literal ("1.2.3").
146
+ if (
147
+ matchAt(NAME_START_CHAR, this.source, end) ||
148
+ matchAt(NUMBER, this.source, end)
149
+ ) {
150
+ this.fail("Numeric literal followed by a name or number", end);
151
+ }
152
+ let type = "integer";
153
+ if (/[eE]/.test(text)) type = "double";
154
+ else if (text.includes(".")) type = "decimal";
155
+ return this.token(type, { value: text }, end);
156
+ }
157
+
158
+ /**
159
+ * @param {"name"|"wildcard"} type - Token type
160
+ * @param {string|null} prefix - Prefix
161
+ * @param {string|null} local - Local name
162
+ * @param {string|null} uri - Braced URI
163
+ * @param {number} end - Offset after the token
164
+ * @returns {Token} A name or wildcard token
165
+ */
166
+ named(type, prefix, local, uri, end) {
167
+ return this.token(type, { prefix, local, uri }, end);
168
+ }
169
+
170
+ /** @returns {Token} `Q{uri}local` or `Q{uri}*`. */
171
+ bracedName() {
172
+ const close = this.source.indexOf("}", this.pos + 2);
173
+ const open = this.source.indexOf("{", this.pos + 2);
174
+ if (close < 0 || (open >= 0 && open < close)) {
175
+ this.fail("Malformed braced URI literal");
176
+ }
177
+ // The namespace is whitespace-normalized like an xs:anyURI.
178
+ const uri = normalizeSpace(this.source.slice(this.pos + 2, close));
179
+ if (this.source[close + 1] === "*") {
180
+ return this.named("wildcard", null, null, uri, close + 2);
181
+ }
182
+ const local = matchAt(NCNAME, this.source, close + 1);
183
+ if (!local) this.fail("Expected a local name", close + 1);
184
+ return this.named("name", null, local, uri, close + 1 + local.length);
185
+ }
186
+
187
+ /** @returns {Token} An NCName, a QName or a `prefix:*` wildcard. */
188
+ name() {
189
+ const first = matchAt(NCNAME, this.source, this.pos);
190
+ const end = this.pos + first.length;
191
+ if (this.source[end] === ":") {
192
+ const local = matchAt(NCNAME, this.source, end + 1);
193
+ if (local) {
194
+ return this.named("name", first, local, null, end + 1 + local.length);
195
+ }
196
+ if (this.source[end + 1] === "*") {
197
+ return this.named("wildcard", first, null, null, end + 2);
198
+ }
199
+ }
200
+ return this.named("name", null, first, null, end);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Splits an expression into tokens, comments and whitespace removed.
206
+ *
207
+ * @param {string} source - XPath expression
208
+ * @returns {Token[]} The tokens, the last one of type "eof"
209
+ * @throws {import("../../errors.js").XPathError} XPST0003 on a lexical error
210
+ */
211
+ export function tokenize(source) {
212
+ const lexer = new Lexer(source);
213
+ const tokens = [];
214
+ let token;
215
+ do {
216
+ token = lexer.next();
217
+ tokens.push(token);
218
+ } while (token.type !== "eof");
219
+ return tokens;
220
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Parser of the XPath 3.1 binary operator expressions, from `or` (lowest
3
+ * precedence) down to `intersect`/`except`: productions [8] OrExpr to [24]
4
+ * IntersectExceptExpr of Appendix A.1. The levels below (instance of ... !)
5
+ * are in typeOperators.js.
6
+ *
7
+ * Operator keywords are recognised only here, in operator position, so
8
+ * `div div div` divides the child element `div` by itself.
9
+ *
10
+ * @module @tradik/xslt3/xpath/syntax/operators
11
+ */
12
+
13
+ import { makeNode } from "./ast.js";
14
+ import { parseInstanceofExpr } from "./typeOperators.js";
15
+
16
+ /** @typedef {import("./tokenStream.js").TokenStream} TokenStream */
17
+ /** @typedef {import("./ast.js").Expr} Expr */
18
+
19
+ /** Kind of each comparison operator (ComparisonExpr.kind). */
20
+ const COMPARISON_KINDS = {
21
+ ...Object.fromEntries(
22
+ "= != < <= > >=".split(" ").map((op) => [op, "general"]),
23
+ ),
24
+ ...Object.fromEntries(
25
+ "eq ne lt le gt ge".split(" ").map((op) => [op, "value"]),
26
+ ),
27
+ ...Object.fromEntries("is << >>".split(" ").map((op) => [op, "node"])),
28
+ };
29
+
30
+ /** Operators stored under another name. */
31
+ const ALIASES = { "|": "union" };
32
+
33
+ /**
34
+ * Builds the parser of one binary precedence level. Each operator may be
35
+ * a symbol or a keyword; both are tried, as names and symbols never clash.
36
+ *
37
+ * @param {string} type - Node type
38
+ * @param {string} operators - Space-separated operators of the level
39
+ * @param {(p: TokenStream) => Expr} operand - Parser of the next level
40
+ * @param {object} [options] - Level options
41
+ * @param {boolean} [options.chain] - Left-associative; false allows at most
42
+ * one operator (comparisons and `to` are non-associative)
43
+ * @param {boolean} [options.withOperator] - Store the operator in the node
44
+ * @returns {(p: TokenStream) => Expr} Parser of the level
45
+ */
46
+ export function binaryLevel(type, operators, operand, options = {}) {
47
+ const { chain = true, withOperator = true } = options;
48
+ const spellings = operators.split(" ");
49
+ const match = (p) =>
50
+ spellings.find((op) => p.acceptSymbol(op) || p.acceptKeyword(op));
51
+ return (p) => {
52
+ let left = operand(p);
53
+ let spelling;
54
+ while ((spelling = match(p))) {
55
+ const right = operand(p);
56
+ const operator = ALIASES[spelling] ?? spelling;
57
+ const kind = COMPARISON_KINDS[operator];
58
+ const fields = withOperator ? { operator, left, right } : { left, right };
59
+ const node = kind ? { kind, ...fields } : fields;
60
+ left = makeNode(type, node, left.start, right.end);
61
+ if (!chain) break;
62
+ }
63
+ return left;
64
+ };
65
+ }
66
+
67
+ const parseIntersectExceptExpr = binaryLevel(
68
+ "SetExpr",
69
+ "intersect except",
70
+ parseInstanceofExpr,
71
+ );
72
+ const parseUnionExpr = binaryLevel(
73
+ "SetExpr",
74
+ "union |",
75
+ parseIntersectExceptExpr,
76
+ );
77
+ const parseMultiplicativeExpr = binaryLevel(
78
+ "ArithmeticExpr",
79
+ "* div idiv mod",
80
+ parseUnionExpr,
81
+ );
82
+ const parseAdditiveExpr = binaryLevel(
83
+ "ArithmeticExpr",
84
+ "+ -",
85
+ parseMultiplicativeExpr,
86
+ );
87
+ const parseRangeExpr = binaryLevel("RangeExpr", "to", parseAdditiveExpr, {
88
+ chain: false,
89
+ withOperator: false,
90
+ });
91
+ const parseStringConcatExpr = binaryLevel(
92
+ "StringConcatExpr",
93
+ "||",
94
+ parseRangeExpr,
95
+ { withOperator: false },
96
+ );
97
+ const parseComparisonExpr = binaryLevel(
98
+ "ComparisonExpr",
99
+ Object.keys(COMPARISON_KINDS).join(" "),
100
+ parseStringConcatExpr,
101
+ { chain: false },
102
+ );
103
+ const parseAndExpr = binaryLevel("LogicalExpr", "and", parseComparisonExpr);
104
+
105
+ /**
106
+ * OrExpr ::= AndExpr ("or" AndExpr)*, the operator expression of lowest
107
+ * precedence and the entry point of this module.
108
+ *
109
+ * @type {(p: TokenStream) => Expr}
110
+ */
111
+ export const parseOrExpr = binaryLevel("LogicalExpr", "or", parseAndExpr);
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Parser of XPath 3.1 path expressions: productions [36] PathExpr to [52]
3
+ * RelativePathExpr of Appendix A.1; the steps are parsed in steps.js.
4
+ *
5
+ * Leading-lone-slash constraint (A.1.2): a `/` followed by a token that can
6
+ * start a step is the start of a path, so `/ * 5` is `/*` followed by an
7
+ * unexpected `5`; `(/) * 5` multiplies the root.
8
+ *
9
+ * @module @tradik/xslt3/xpath/syntax/paths
10
+ */
11
+
12
+ import { makeNode } from "./ast.js";
13
+ import { parseStepExpr } from "./steps.js";
14
+
15
+ /** @typedef {import("./tokenStream.js").TokenStream} TokenStream */
16
+ /** @typedef {import("./ast.js").Expr} Expr */
17
+
18
+ /** Symbols that can start a step (leading-lone-slash constraint). */
19
+ const STEP_START_SYMBOLS = new Set(["*", "@", ".", "..", "$", "(", "?", "["]);
20
+ const LITERAL_TOKENS = new Set([
21
+ "name",
22
+ "wildcard",
23
+ "integer",
24
+ "decimal",
25
+ "double",
26
+ "string",
27
+ ]);
28
+
29
+ /**
30
+ * PathExpr ::= ("/" RelativePathExpr?) | ("//" RelativePathExpr) |
31
+ * RelativePathExpr
32
+ *
33
+ * @param {TokenStream} p - Tokens
34
+ * @returns {Expr} A PathExpr, or the step itself when there is no slash
35
+ */
36
+ export function parsePathExpr(p) {
37
+ const slash = p.acceptSymbol("/");
38
+ if (slash) {
39
+ if (!startsStep(p)) {
40
+ return makeNode(
41
+ "PathExpr",
42
+ { absolute: true, steps: [] },
43
+ slash.start,
44
+ slash.end,
45
+ );
46
+ }
47
+ return continuePath(p, slash.start, true, [parseStepExpr(p)]);
48
+ }
49
+ const doubleSlash = p.acceptSymbol("//");
50
+ if (doubleSlash) {
51
+ const steps = [descendantOrSelfStep(doubleSlash), parseStepExpr(p)];
52
+ return continuePath(p, doubleSlash.start, true, steps);
53
+ }
54
+ const first = parseStepExpr(p);
55
+ if (!p.isSymbol("/") && !p.isSymbol("//")) return first;
56
+ return continuePath(p, first.start, false, [first]);
57
+ }
58
+
59
+ /**
60
+ * Reads the `("/" | "//") StepExpr` pairs that follow the first step.
61
+ *
62
+ * @param {TokenStream} p - Tokens
63
+ * @param {number} start - Offset of the path
64
+ * @param {boolean} absolute - Whether the path starts with a slash
65
+ * @param {Expr[]} steps - Steps read so far
66
+ * @returns {import("./ast.js").PathExpr} The path
67
+ */
68
+ function continuePath(p, start, absolute, steps) {
69
+ let separator;
70
+ while ((separator = p.acceptSymbol("/") ?? p.acceptSymbol("//"))) {
71
+ if (separator.value === "//") steps.push(descendantOrSelfStep(separator));
72
+ steps.push(parseStepExpr(p));
73
+ }
74
+ return makeNode("PathExpr", { absolute, steps }, start, steps.at(-1).end);
75
+ }
76
+
77
+ /**
78
+ * @param {TokenStream} p - Tokens
79
+ * @returns {boolean} Whether the current token can start a StepExpr
80
+ */
81
+ function startsStep(p) {
82
+ const token = p.peek();
83
+ return token.type === "symbol"
84
+ ? STEP_START_SYMBOLS.has(token.value)
85
+ : LITERAL_TOKENS.has(token.type);
86
+ }
87
+
88
+ /**
89
+ * The step `descendant-or-self::node()` that `//` stands for.
90
+ *
91
+ * @param {import("./lexer.js").Token} token - The `//` token
92
+ * @returns {import("./ast.js").AxisStep} The step
93
+ */
94
+ function descendantOrSelfStep(token) {
95
+ const nodeTest = makeNode("AnyKindTest", {}, token.start, token.end);
96
+ return makeNode(
97
+ "AxisStep",
98
+ { axis: "descendant-or-self", nodeTest, predicates: [] },
99
+ token.start,
100
+ token.end,
101
+ );
102
+ }