Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ce2d704
[feature] Add XQuery 4.0 syntax to ANTLR 2 grammar
joewiz Apr 4, 2026
2916f59
[feature] Add XQuery 4.0 expression classes
joewiz Apr 4, 2026
4202cef
[feature] Add XQuery 4.0 type infrastructure and expression modificat…
joewiz Apr 4, 2026
e18c598
[bugfix] Align XQuery error codes with W3C specification
joewiz Apr 4, 2026
a0cde7e
[test] Add XQSuite tests for XQuery 4.0 parser features
joewiz Apr 4, 2026
4c342d1
[feature] Add version gating and feature flag for XQuery 4.0 syntax
joewiz Apr 5, 2026
47a4cdf
[feature] Align fn: function parameter names with XQuery 4.0 F&O spec
joewiz Apr 25, 2026
4e3f10d
[feature] Add XQuery 4.0 bare map syntax and content expressions
joewiz Apr 26, 2026
fb3b5b6
[ci] Remove Codacy config that breaks PMD analysis; remove unused code
joewiz Apr 26, 2026
99f6316
[optimize] Speed up XQueryLexer keyword-table lookup
joewiz Apr 27, 2026
41fd5c2
[ci] Restore .codacy/codacy.yaml (removal deferred to separate PR)
joewiz Apr 27, 2026
143f428
[refactor] ParserBenchmark: move SAMPLES to top, use Java text blocks
joewiz Apr 27, 2026
118fdf9
[feature] Annotations on FunctionTest + extend reserved namespaces
joewiz Apr 28, 2026
ee462fe
[refactor] Suppress NPath warnings on XQ4 expression dispatchers
joewiz Apr 28, 2026
9424f11
[bugfix] Reject reserved function names in FunctionDecl
joewiz Apr 28, 2026
c2e0ec3
[refactor] AnnotationsTest: convert to text blocks per review
joewiz Apr 28, 2026
8d031dc
[bugfix] prod-FunctionDecl: XQ4 keyword args, arity overlap, version …
joewiz Apr 28, 2026
e7d51af
[refactor] Address review: switch expression in versionDecl, import FQDN
joewiz Apr 29, 2026
3dbf368
[refactor] Remove proactive PMD.NPathComplexity suppressions
joewiz Apr 29, 2026
0f93f1d
[refactor] Convert switches to switch expressions per review
joewiz Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
979 changes: 907 additions & 72 deletions exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g

Large diffs are not rendered by default.

2,164 changes: 1,617 additions & 547 deletions exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions exist-core/src/main/java/org/exist/xquery/CastExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,15 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
}
}

// Should be handled by the parser
if (requiredType == Type.ANY_ATOMIC_TYPE || (requiredType == Type.NOTATION && expression.returnsType() != Type.NOTATION)) {
// XPST0080: cannot cast to abstract or special types
if (requiredType == Type.ANY_ATOMIC_TYPE || requiredType == Type.ANY_SIMPLE_TYPE
|| requiredType == Type.ANY_TYPE || requiredType == Type.UNTYPED
|| (requiredType == Type.NOTATION && expression.returnsType() != Type.NOTATION)) {
throw new XPathException(this, ErrorCodes.XPST0080, "cannot cast to " + Type.getTypeName(requiredType));
}

if (requiredType == Type.ANY_SIMPLE_TYPE || expression.returnsType() == Type.ANY_SIMPLE_TYPE || requiredType == Type.UNTYPED || expression.returnsType() == Type.UNTYPED) {
throw new XPathException(this, ErrorCodes.XPST0051, "cannot cast to " + Type.getTypeName(requiredType));
if (expression.returnsType() == Type.ANY_SIMPLE_TYPE || expression.returnsType() == Type.UNTYPED) {
throw new XPathException(this, ErrorCodes.XPST0051, "cannot cast from " + Type.getTypeName(expression.returnsType()));
}

final Sequence result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,13 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
{context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence());}
}

if (requiredType == Type.ANY_ATOMIC_TYPE || (requiredType == Type.NOTATION && expression.returnsType() != Type.NOTATION))
if (requiredType == Type.ANY_ATOMIC_TYPE || requiredType == Type.ANY_SIMPLE_TYPE
|| requiredType == Type.ANY_TYPE || requiredType == Type.UNTYPED
|| (requiredType == Type.NOTATION && expression.returnsType() != Type.NOTATION))
{throw new XPathException(this, ErrorCodes.XPST0080, "cannot convert to " + Type.getTypeName(requiredType));}

if (requiredType == Type.ANY_SIMPLE_TYPE || expression.returnsType() == Type.ANY_SIMPLE_TYPE || requiredType == Type.UNTYPED || expression.returnsType() == Type.UNTYPED)
{throw new XPathException(this, ErrorCodes.XPST0051, "cannot convert to " + Type.getTypeName(requiredType));}
if (expression.returnsType() == Type.ANY_SIMPLE_TYPE || expression.returnsType() == Type.UNTYPED)
{throw new XPathException(this, ErrorCodes.XPST0051, "cannot convert from " + Type.getTypeName(expression.returnsType()));}

Sequence result;
//See : http://article.gmane.org/gmane.text.xml.xquery.general/1413
Expand Down
137 changes: 137 additions & 0 deletions exist-core/src/main/java/org/exist/xquery/ChoiceCastExpression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* info@exist-db.org
* http://www.exist-db.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.xquery;

import org.exist.dom.persistent.DocumentSet;
import org.exist.xquery.util.ExpressionDumper;
import org.exist.xquery.value.*;

/**
* Implements cast as (T1 | T2 | ...) from XQuery 4.0.
* Tries each target type in order and returns the first successful cast.
*/
public class ChoiceCastExpression extends AbstractExpression {

private final int[] targetTypes;
private final Cardinality cardinality;
private Expression expression;

public ChoiceCastExpression(final XQueryContext context, final Expression expr,
final int[] targetTypes, final Cardinality cardinality) {
super(context);
this.targetTypes = targetTypes;
this.cardinality = cardinality;
this.expression = expr;
}

@Override
public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
contextInfo.setParent(this);
expression.analyze(contextInfo);
}

@Override
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
final Sequence seq = Atomize.atomize(expression.eval(contextSequence, contextItem));
if (seq.isEmpty()) {
if (cardinality.atLeastOne()) {
throw new XPathException(this, ErrorCodes.XPTY0004,
"Type error: empty sequence is not allowed here");
}
return Sequence.EMPTY_SEQUENCE;
}
if (seq.hasMany()) {
throw new XPathException(this, ErrorCodes.XPTY0004,
"cardinality error: sequence with more than one item is not allowed here");
}

final Item item = seq.itemAt(0);
XPathException lastError = null;

for (final int targetType : targetTypes) {
try {
return item.convertTo(targetType);
} catch (final XPathException e) {
lastError = e;
}
}

throw new XPathException(this, ErrorCodes.FORG0001,
"Cannot cast " + Type.getTypeName(item.getType()) +
" to any of the choice types", lastError);
}

@Override
public int returnsType() {
return Type.ANY_ATOMIC_TYPE;
}

@Override
public Cardinality getCardinality() {
return Cardinality.ZERO_OR_ONE;
}

@Override
public void dump(final ExpressionDumper dumper) {
expression.dump(dumper);
dumper.display(" cast as (");
for (int i = 0; i < targetTypes.length; i++) {
if (i > 0) {
dumper.display(" | ");
}
dumper.display(Type.getTypeName(targetTypes[i]));
}
dumper.display(")");
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(expression.toString()).append(" cast as (");
for (int i = 0; i < targetTypes.length; i++) {
if (i > 0) {
sb.append(" | ");
}
sb.append(Type.getTypeName(targetTypes[i]));
}
sb.append(")");
return sb.toString();
}

@Override
public int getDependencies() {
return expression.getDependencies() | Dependency.CONTEXT_ITEM;
}

@Override
public void setContextDocSet(final DocumentSet contextSet) {
super.setContextDocSet(contextSet);
expression.setContextDocSet(contextSet);
}

@Override
public void resetState(final boolean postOptimization) {
super.resetState(postOptimization);
expression.resetState(postOptimization);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* info@exist-db.org
* http://www.exist-db.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.xquery;

import org.exist.dom.persistent.DocumentSet;
import org.exist.xquery.util.ExpressionDumper;
import org.exist.xquery.value.*;

/**
* Implements castable as (T1 | T2 | ...) from XQuery 4.0.
* Returns true if the value can be cast to any of the target types.
*/
public class ChoiceCastableExpression extends AbstractExpression {

private final int[] targetTypes;
private final Cardinality requiredCardinality;
private final Expression expression;

public ChoiceCastableExpression(final XQueryContext context, final Expression expr,
final int[] targetTypes, final Cardinality requiredCardinality) {
super(context);
this.expression = expr;
this.targetTypes = targetTypes;
this.requiredCardinality = requiredCardinality;
}

@Override
public int returnsType() {
return Type.BOOLEAN;
}

@Override
public Cardinality getCardinality() {
return Cardinality.EXACTLY_ONE;
}

@Override
public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
contextInfo.setParent(this);
expression.analyze(contextInfo);
}

@Override
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
final Sequence seq = Atomize.atomize(expression.eval(contextSequence, contextItem));
if (seq.isEmpty()) {
return BooleanValue.valueOf(
requiredCardinality.isSuperCardinalityOrEqualOf(Cardinality.EMPTY_SEQUENCE));
}
if (!requiredCardinality.isSuperCardinalityOrEqualOf(seq.getCardinality())) {
return BooleanValue.FALSE;
}

final Item item = seq.itemAt(0);
for (final int targetType : targetTypes) {
try {
item.convertTo(targetType);
return BooleanValue.TRUE;
} catch (final XPathException e) {
// try next type
}
}
return BooleanValue.FALSE;
}

@Override
public void dump(final ExpressionDumper dumper) {
expression.dump(dumper);
dumper.display(" castable as (");
for (int i = 0; i < targetTypes.length; i++) {
if (i > 0) {
dumper.display(" | ");
}
dumper.display(Type.getTypeName(targetTypes[i]));
}
dumper.display(")");
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(expression.toString()).append(" castable as (");
for (int i = 0; i < targetTypes.length; i++) {
if (i > 0) {
sb.append(" | ");
}
sb.append(Type.getTypeName(targetTypes[i]));
}
sb.append(")");
return sb.toString();
}

@Override
public int getDependencies() {
return Dependency.CONTEXT_SET + Dependency.CONTEXT_ITEM;
}

@Override
public void setContextDocSet(final DocumentSet contextSet) {
super.setContextDocSet(contextSet);
expression.setContextDocSet(contextSet);
}

@Override
public void resetState(final boolean postOptimization) {
super.resetState(postOptimization);
expression.resetState(postOptimization);
}
}
12 changes: 11 additions & 1 deletion exist-core/src/main/java/org/exist/xquery/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ public interface Constants {
"following-sibling",
"namespace",
"self",
"attribute-descendant"
"attribute-descendant",
"following-or-self",
"preceding-or-self",
"following-sibling-or-self",
"preceding-sibling-or-self"
};

/**
Expand All @@ -73,6 +77,12 @@ public interface Constants {
//combines /descendant-or-self::node()/attribute:*
int DESCENDANT_ATTRIBUTE_AXIS = 13;

/** XQuery 4.0 axes */
int FOLLOWING_OR_SELF_AXIS = 14;
int PRECEDING_OR_SELF_AXIS = 15;
int FOLLOWING_SIBLING_OR_SELF_AXIS = 16;
int PRECEDING_SIBLING_OR_SELF_AXIS = 17;

/**
* Node types
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,14 @@ else if (seq.hasMany())
error.addArgs(ExpressionDumper.dump(expression),
requiredCardinality.getHumanDescription(),
seq.getItemCount());
throw new XPathException(this, error.toString());
final String errCode = error.getErrorCode();
final ErrorCodes.ErrorCode xpathErrCode;
if ("XPDY0050".equals(errCode)) {
xpathErrCode = ErrorCodes.XPDY0050;
} else {
xpathErrCode = ErrorCodes.XPTY0004;
}
throw new XPathException(this, xpathErrCode, error.toString());
}
if (context.getProfiler().isEnabled())
{context.getProfiler().end(this, "", seq);}
Expand Down
Loading
Loading