@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.
- package/LICENSE.md +28 -0
- package/README.md +98 -2
- package/package.json +43 -4
- package/src/errors.js +20 -0
- package/src/functions/aggregates.js +144 -0
- package/src/functions/arraySort.js +33 -0
- package/src/functions/arrays.js +181 -0
- package/src/functions/attributeTypes.js +58 -0
- package/src/functions/baseUris.js +82 -0
- package/src/functions/collations.js +127 -0
- package/src/functions/collections.js +84 -0
- package/src/functions/context.js +83 -0
- package/src/functions/core.js +167 -0
- package/src/functions/datetime.js +203 -0
- package/src/functions/deepEqual.js +132 -0
- package/src/functions/diagnostics.js +88 -0
- package/src/functions/focus.js +34 -0
- package/src/functions/format/dateComponents.js +176 -0
- package/src/functions/format/datePicture.js +115 -0
- package/src/functions/format/decimalFormat.js +65 -0
- package/src/functions/format/digitPattern.js +145 -0
- package/src/functions/format/formatDateTime.js +204 -0
- package/src/functions/format/formatInteger.js +109 -0
- package/src/functions/format/formatNumber.js +165 -0
- package/src/functions/format/fractionFormat.js +80 -0
- package/src/functions/format/germanWords.js +89 -0
- package/src/functions/format/numberLanguages.js +41 -0
- package/src/functions/format/numberPicture.js +202 -0
- package/src/functions/format/numberWords.js +155 -0
- package/src/functions/format/place.js +74 -0
- package/src/functions/format/timezoneFormat.js +89 -0
- package/src/functions/higherOrder.js +106 -0
- package/src/functions/ids.js +127 -0
- package/src/functions/json/index.js +17 -0
- package/src/functions/json/jsonChars.js +110 -0
- package/src/functions/json/jsonParser.js +203 -0
- package/src/functions/json/jsonStrings.js +116 -0
- package/src/functions/json/jsonToXml.js +116 -0
- package/src/functions/json/parseJson.js +119 -0
- package/src/functions/json/xmlRepresentation.js +103 -0
- package/src/functions/json/xmlToJson.js +147 -0
- package/src/functions/library.js +47 -0
- package/src/functions/mapArrayEqual.js +41 -0
- package/src/functions/maps.js +152 -0
- package/src/functions/math.js +73 -0
- package/src/functions/nodePaths.js +181 -0
- package/src/functions/nodes.js +199 -0
- package/src/functions/numeric.js +148 -0
- package/src/functions/options.js +58 -0
- package/src/functions/parseIetfDate.js +104 -0
- package/src/functions/parseXml.js +77 -0
- package/src/functions/qnames.js +68 -0
- package/src/functions/random.js +128 -0
- package/src/functions/regex/analyzeString.js +144 -0
- package/src/functions/regex/charClass.js +112 -0
- package/src/functions/regex/compile.js +81 -0
- package/src/functions/regex/escapes.js +109 -0
- package/src/functions/regex/parser.js +169 -0
- package/src/functions/regex/reader.js +37 -0
- package/src/functions/regex/regexFunctions.js +140 -0
- package/src/functions/regex/translate.js +102 -0
- package/src/functions/regex/unicodeBlocks.js +194 -0
- package/src/functions/registry.js +140 -0
- package/src/functions/sequences.js +173 -0
- package/src/functions/serializeFunction.js +53 -0
- package/src/functions/signatures.js +105 -0
- package/src/functions/sort.js +102 -0
- package/src/functions/stringMatch.js +45 -0
- package/src/functions/strings.js +194 -0
- package/src/functions/stringsCore.js +66 -0
- package/src/functions/support.js +106 -0
- package/src/functions/textDecoding.js +117 -0
- package/src/functions/ucaCollation.js +127 -0
- package/src/functions/unparsedText.js +71 -0
- package/src/functions/uri.js +166 -0
- package/src/functions/xqueryModule.js +24 -0
- package/src/index.js +38 -0
- package/src/items/array.js +84 -0
- package/src/items/function.js +45 -0
- package/src/items/map.js +120 -0
- package/src/items/mapKey.js +82 -0
- package/src/serialize/index.js +129 -0
- package/src/serialize/markup/content.js +74 -0
- package/src/serialize/markup/dialect.js +178 -0
- package/src/serialize/markup/htmlData.js +70 -0
- package/src/serialize/markup/namespaces.js +145 -0
- package/src/serialize/markup/prolog.js +91 -0
- package/src/serialize/markup/writer.js +183 -0
- package/src/serialize/methods/adaptive.js +131 -0
- package/src/serialize/methods/json.js +158 -0
- package/src/serialize/methods/text.js +43 -0
- package/src/serialize/output/buffer.js +48 -0
- package/src/serialize/output/encoding.js +146 -0
- package/src/serialize/output/expander.js +204 -0
- package/src/serialize/params/fromElement.js +141 -0
- package/src/serialize/params/fromMap.js +128 -0
- package/src/serialize/params/names.js +69 -0
- package/src/serialize/params/settings.js +153 -0
- package/src/serialize/params/values.js +163 -0
- package/src/serialize/sequence.js +111 -0
- package/src/xdm/arithmetic.js +155 -0
- package/src/xdm/atomic.js +142 -0
- package/src/xdm/binary.js +92 -0
- package/src/xdm/calendar.js +71 -0
- package/src/xdm/cast.js +176 -0
- package/src/xdm/compare.js +192 -0
- package/src/xdm/datetime.js +206 -0
- package/src/xdm/decimal.js +200 -0
- package/src/xdm/duration.js +128 -0
- package/src/xdm/generalCompare.js +152 -0
- package/src/xdm/index.js +47 -0
- package/src/xdm/lexical.js +126 -0
- package/src/xdm/nodes.js +170 -0
- package/src/xdm/numeric.js +122 -0
- package/src/xdm/qname.js +65 -0
- package/src/xdm/strings.js +52 -0
- package/src/xdm/temporalArithmetic.js +180 -0
- package/src/xdm/timeline.js +68 -0
- package/src/xdm/types.js +178 -0
- package/src/xpath/eval/atomics.js +45 -0
- package/src/xpath/eval/axes.js +212 -0
- package/src/xpath/eval/calls.js +168 -0
- package/src/xpath/eval/coercion.js +146 -0
- package/src/xpath/eval/compiler.js +71 -0
- package/src/xpath/eval/constructors.js +129 -0
- package/src/xpath/eval/decimalFormats.js +93 -0
- package/src/xpath/eval/descendantMemo.js +77 -0
- package/src/xpath/eval/documentOrder.js +142 -0
- package/src/xpath/eval/domNodes.js +174 -0
- package/src/xpath/eval/dynamicCall.js +27 -0
- package/src/xpath/eval/dynamicContext.js +161 -0
- package/src/xpath/eval/flow.js +115 -0
- package/src/xpath/eval/functionItems.js +194 -0
- package/src/xpath/eval/groupedChildren.js +88 -0
- package/src/xpath/eval/inline.js +71 -0
- package/src/xpath/eval/namespaceNodes.js +90 -0
- package/src/xpath/eval/nodeTests.js +164 -0
- package/src/xpath/eval/operators.js +190 -0
- package/src/xpath/eval/paths.js +163 -0
- package/src/xpath/eval/positional.js +81 -0
- package/src/xpath/eval/predicates.js +56 -0
- package/src/xpath/eval/primary.js +99 -0
- package/src/xpath/eval/resources.js +149 -0
- package/src/xpath/eval/scope.js +108 -0
- package/src/xpath/eval/sequenceType.js +193 -0
- package/src/xpath/eval/staticContext.js +142 -0
- package/src/xpath/eval/steps.js +98 -0
- package/src/xpath/eval/subtype.js +154 -0
- package/src/xpath/eval/typeExprs.js +147 -0
- package/src/xpath/eval/typeNames.js +101 -0
- package/src/xpath/eval/uris.js +21 -0
- package/src/xpath/eval/values.js +64 -0
- package/src/xpath/index.js +217 -0
- package/src/xpath/syntax/ast.js +130 -0
- package/src/xpath/syntax/chars.js +58 -0
- package/src/xpath/syntax/constructors.js +144 -0
- package/src/xpath/syntax/expressions.js +107 -0
- package/src/xpath/syntax/index.js +44 -0
- package/src/xpath/syntax/kindTests.js +134 -0
- package/src/xpath/syntax/lexer.js +220 -0
- package/src/xpath/syntax/operators.js +111 -0
- package/src/xpath/syntax/paths.js +102 -0
- package/src/xpath/syntax/postfix.js +91 -0
- package/src/xpath/syntax/primaries.js +149 -0
- package/src/xpath/syntax/steps.js +146 -0
- package/src/xpath/syntax/syntaxError.js +31 -0
- package/src/xpath/syntax/tokenStream.js +163 -0
- package/src/xpath/syntax/typeAst.js +76 -0
- package/src/xpath/syntax/typeOperators.js +127 -0
- package/src/xpath/syntax/types.js +158 -0
- package/src/xslt/api.js +153 -0
- package/src/xslt/compiler/acceptance.js +178 -0
- package/src/xslt/compiler/accumulatorDecl.js +146 -0
- package/src/xslt/compiler/attributeValues.js +174 -0
- package/src/xslt/compiler/attributes.js +211 -0
- package/src/xslt/compiler/avt.js +103 -0
- package/src/xslt/compiler/body.js +154 -0
- package/src/xslt/compiler/children.js +105 -0
- package/src/xslt/compiler/compatibility.js +130 -0
- package/src/xslt/compiler/components.js +179 -0
- package/src/xslt/compiler/contextItem.js +95 -0
- package/src/xslt/compiler/decimalFormats.js +132 -0
- package/src/xslt/compiler/declarationKinds.js +82 -0
- package/src/xslt/compiler/elementInfo.js +246 -0
- package/src/xslt/compiler/expressions.js +175 -0
- package/src/xslt/compiler/globalContextItem.js +92 -0
- package/src/xslt/compiler/globals.js +209 -0
- package/src/xslt/compiler/linker.js +134 -0
- package/src/xslt/compiler/miscDecl.js +171 -0
- package/src/xslt/compiler/modeDecl.js +88 -0
- package/src/xslt/compiler/moduleLoading.js +111 -0
- package/src/xslt/compiler/modules.js +158 -0
- package/src/xslt/compiler/outputDecl.js +209 -0
- package/src/xslt/compiler/overrideLinks.js +141 -0
- package/src/xslt/compiler/packageChecks.js +84 -0
- package/src/xslt/compiler/packageRegistry.js +95 -0
- package/src/xslt/compiler/packageTable.js +181 -0
- package/src/xslt/compiler/packageVersions.js +143 -0
- package/src/xslt/compiler/packages.js +138 -0
- package/src/xslt/compiler/registry.js +234 -0
- package/src/xslt/compiler/staticStage.js +197 -0
- package/src/xslt/compiler/staticVariables.js +93 -0
- package/src/xslt/compiler/stylesheet.js +205 -0
- package/src/xslt/compiler/tailPosition.js +74 -0
- package/src/xslt/compiler/templatesDecl.js +180 -0
- package/src/xslt/compiler/useAccumulators.js +76 -0
- package/src/xslt/compiler/useWhen.js +94 -0
- package/src/xslt/compiler/visibility.js +160 -0
- package/src/xslt/index.js +11 -0
- package/src/xslt/instructions/analyzeString.js +137 -0
- package/src/xslt/instructions/assert.js +72 -0
- package/src/xslt/instructions/attributeSets.js +115 -0
- package/src/xslt/instructions/conditional.js +99 -0
- package/src/xslt/instructions/control.js +123 -0
- package/src/xslt/instructions/copy.js +151 -0
- package/src/xslt/instructions/elements.js +161 -0
- package/src/xslt/instructions/evaluate.js +189 -0
- package/src/xslt/instructions/fork.js +35 -0
- package/src/xslt/instructions/grouping.js +106 -0
- package/src/xslt/instructions/index.js +94 -0
- package/src/xslt/instructions/iterate.js +182 -0
- package/src/xslt/instructions/literal.js +142 -0
- package/src/xslt/instructions/maps.js +72 -0
- package/src/xslt/instructions/merge.js +190 -0
- package/src/xslt/instructions/number.js +205 -0
- package/src/xslt/instructions/output.js +102 -0
- package/src/xslt/instructions/params.js +110 -0
- package/src/xslt/instructions/resultDocument.js +143 -0
- package/src/xslt/instructions/sort.js +114 -0
- package/src/xslt/instructions/sourceDocument.js +73 -0
- package/src/xslt/instructions/templates.js +153 -0
- package/src/xslt/instructions/text.js +151 -0
- package/src/xslt/instructions/try.js +186 -0
- package/src/xslt/instructions/variables.js +119 -0
- package/src/xslt/names.js +204 -0
- package/src/xslt/patterns/axisOrigins.js +121 -0
- package/src/xslt/patterns/compile.js +151 -0
- package/src/xslt/patterns/general.js +132 -0
- package/src/xslt/patterns/grammar.js +190 -0
- package/src/xslt/patterns/modes.js +153 -0
- package/src/xslt/patterns/paths.js +130 -0
- package/src/xslt/patterns/patternChecks.js +29 -0
- package/src/xslt/patterns/steps.js +162 -0
- package/src/xslt/processor.js +178 -0
- package/src/xslt/runtime/accumulatorFunctions.js +48 -0
- package/src/xslt/runtime/accumulators.js +174 -0
- package/src/xslt/runtime/apply.js +153 -0
- package/src/xslt/runtime/atomicKeys.js +36 -0
- package/src/xslt/runtime/availability.js +162 -0
- package/src/xslt/runtime/baseUri.js +64 -0
- package/src/xslt/runtime/compilerHook.js +27 -0
- package/src/xslt/runtime/conditional.js +110 -0
- package/src/xslt/runtime/context.js +106 -0
- package/src/xslt/runtime/copy.js +123 -0
- package/src/xslt/runtime/copyFunctions.js +71 -0
- package/src/xslt/runtime/copyOrigins.js +60 -0
- package/src/xslt/runtime/defaultMethod.js +42 -0
- package/src/xslt/runtime/documentContent.js +93 -0
- package/src/xslt/runtime/dynamicXPath.js +90 -0
- package/src/xslt/runtime/functionCache.js +65 -0
- package/src/xslt/runtime/functionCall.js +57 -0
- package/src/xslt/runtime/functions.js +191 -0
- package/src/xslt/runtime/groups.js +108 -0
- package/src/xslt/runtime/invocation.js +156 -0
- package/src/xslt/runtime/iteration.js +107 -0
- package/src/xslt/runtime/keys.js +141 -0
- package/src/xslt/runtime/lazy.js +97 -0
- package/src/xslt/runtime/locations.js +86 -0
- package/src/xslt/runtime/machine.js +137 -0
- package/src/xslt/runtime/merging.js +167 -0
- package/src/xslt/runtime/namespaceScope.js +141 -0
- package/src/xslt/runtime/numberFormat.js +89 -0
- package/src/xslt/runtime/numbering.js +168 -0
- package/src/xslt/runtime/params.js +59 -0
- package/src/xslt/runtime/properties.js +103 -0
- package/src/xslt/runtime/rawResults.js +82 -0
- package/src/xslt/runtime/sequenceReceiver.js +102 -0
- package/src/xslt/runtime/snapshot.js +66 -0
- package/src/xslt/runtime/sortKeys.js +119 -0
- package/src/xslt/runtime/streamAvailable.js +60 -0
- package/src/xslt/runtime/strip.js +102 -0
- package/src/xslt/runtime/transformFunction.js +169 -0
- package/src/xslt/runtime/transformation.js +168 -0
- package/src/xslt/runtime/transformationContext.js +85 -0
- package/src/xslt/runtime/treeReceiver.js +194 -0
- 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
|
+
}
|