Skip to content

Commit

Permalink
implement atomic-type-annotation (#2387)
Browse files Browse the repository at this point in the history
  • Loading branch information
GuntherRademacher authored Feb 26, 2025
1 parent 13f9dfa commit f240c6a
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 2 deletions.
3 changes: 3 additions & 0 deletions basex-core/src/main/java/org/basex/query/func/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ public enum Function implements AFunction {
ATOMIC_EQUAL(FnAtomicEqual::new, "atomic-equal(value1,value2)",
params(ANY_ATOMIC_TYPE_O, ANY_ATOMIC_TYPE_O), BOOLEAN_O),
/** XQuery function. */
ATOMIC_TYPE_ANNOTATION(FnAtomicTypeAnnotation::new, "atomic-type-annotation(value)",
params(ANY_ATOMIC_TYPE_O), MAP_O),
/** XQuery function. */
AVAILABLE_ENVIRONMENT_VARIABLES(FnAvailableEnvironmentVariables::new,
"available-environment-variables()",
params(), STRING_ZM),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ protected void simplifyArgs(final CompileContext cc) throws QueryException {
final int p = Math.min(a, definition.types.length - 1);
final Type type = definition.types[p].type;
if(type.instanceOf(AtomType.ANY_ATOMIC_TYPE)) {
final Simplify mode = type.instanceOf(AtomType.NUMERIC) ? Simplify.NUMBER : Simplify.STRING;
final Simplify mode = type.instanceOf(AtomType.NUMERIC) ? Simplify.NUMBER :
type.instanceOf(AtomType.STRING) ? Simplify.STRING : Simplify.DATA;
arg(a, arg -> arg.simplifyFor(mode, cc));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package org.basex.query.func.fn;

import static org.basex.query.value.type.AtomType.*;
import static org.basex.query.value.type.SeqType.*;

import org.basex.query.*;
import org.basex.query.expr.*;
import org.basex.query.func.*;
import org.basex.query.util.list.*;
import org.basex.query.value.*;
import org.basex.query.value.item.*;
import org.basex.query.value.map.*;
import org.basex.query.value.seq.*;
import org.basex.query.value.type.*;
import org.basex.query.var.*;
import org.basex.util.*;
import org.basex.util.hash.*;

/**
* Function implementation.
*
* @author BaseX Team, BSD License
* @author Gunther Rademacher
*/
public final class FnAtomicTypeAnnotation extends StandardFunc {

@Override
public Item item(final QueryContext qc, final InputInfo ii) throws QueryException {
final Item value = toAtomItem(arg(0), qc);
return annotate(value.type.atomic(), qc, ii);
}

/**
* Creates a type annotation for the specified atomic type.
* @param type the type to be annotated
* @param qc query context
* @param ii input info
* @return the type annotation
* @throws QueryException query exception
*/
protected static Item annotate(final AtomType type, final QueryContext qc, final InputInfo ii)
throws QueryException {
if(type == null) return Empty.VALUE;

final AtomType baseType;
final Variety variety;
AtomType primType = null;
FuncItem constructor = null;
switch(type) {
case ANY_TYPE:
baseType = null;
variety = Variety.mixed;
break;
case ANY_SIMPLE_TYPE:
baseType = ANY_TYPE;
variety = null;
break;
case ANY_ATOMIC_TYPE:
baseType = ANY_SIMPLE_TYPE;
variety = Variety.atomic;
break;
default:
final AtomType parent = type.parent;
baseType = parent == NUMERIC ? ANY_ATOMIC_TYPE : parent;
variety = Variety.atomic;
for(primType = type; !primType.parent.oneOf(ANY_ATOMIC_TYPE, NUMERIC, null);)
primType = primType.parent;
if(!type.oneOf(QNAME, NOTATION))
constructor = (FuncItem) Functions.item(type.qname(), 1, true, ii, qc, true);
}

final MapBuilder mb = new MapBuilder();
mb.put("name", type.qname());
mb.put("is-simple", Bln.get(type != ANY_TYPE));
mb.put("base-type", TypeAnnotation.funcItem(baseType, ii));
if(primType != null) mb.put("primitive-type", TypeAnnotation.funcItem(primType, ii));
if(variety != null) mb.put("variety", variety.name());
mb.put("matches", Matches.funcItem(type, qc, ii));
if(constructor != null) mb.put("constructor", constructor);
return mb.map();
}

/** The variety of a type. */
private enum Variety {
/** Mixed. */ mixed,
/** List. */ list,
/** Atomic. */ atomic
};

/**
* Function creating the type annotation for a given atomic type.
*/
private static final class TypeAnnotation extends Arr {
/** Function type. */
private static final FuncType FUNC_TYPE = FuncType.get(MAP_O);
/** The type to be annotated. */
private final AtomType type;

/**
* Constructor.
* @param info input info
* @param type the type to be annotated
*/
private TypeAnnotation(final InputInfo info, final AtomType type) {
super(info, MAP_O);
this.type = type;
}

/**
* Create a function item for a new instance.
* @param type the type to be matched
* @param info input info
* @return the function item
*/
public static Value funcItem(final AtomType type, final InputInfo info) {
return new FuncItem(info, new TypeAnnotation(info, type), new Var[] { }, AnnList.EMPTY,
FUNC_TYPE, 0, null);
}

@Override
public Item item(final QueryContext qc, final InputInfo ii) throws QueryException {
return annotate(type, qc, ii);
}

@Override
public Expr copy(final CompileContext cc, final IntObjMap<Var> vm) {
return new TypeAnnotation(info, type);
}

@Override
public void toString(final QueryString qs) {
qs.token("type-annotation").params(exprs);
}
}

/**
* Function checking if an item matches a given type.
*/
private static final class Matches extends Arr {
/** Function type. */
private static final FuncType FUNC_TYPE = FuncType.get(BOOLEAN_O, ANY_ATOMIC_TYPE_O);
/** The type to be matched. */
final AtomType type;

/**
* Constructor.
* @param info input info
* @param type the type to be matched
* @param args the arguments
*/
private Matches(final InputInfo info, final AtomType type, final Expr... args) {
super(info, BOOLEAN_O, args);
this.type = type;
}

/**
* Create a function item for a new instance.
* @param type the type to be matched
* @param qc query context
* @param info input info
* @return the function item
*/
public static Value funcItem(final AtomType type, final QueryContext qc, final InputInfo info) {
final Var var = new VarScope().addNew(new QNm("value"), ANY_ATOMIC_TYPE_O, qc, info);
final Var[] params = { var };
return new FuncItem(info, new Matches(info, type, new VarRef(info, var)), params,
AnnList.EMPTY, FUNC_TYPE, params.length, null);
}

@Override
public Item item(final QueryContext qc, final InputInfo ii) throws QueryException {
final Item value = toAtomItem(arg(0), qc);
return Bln.get(value.instanceOf(type));
}

@Override
public Expr copy(final CompileContext cc, final IntObjMap<Var> vm) {
return new Matches(info, type, copyAll(cc, vm, args()));
}

@Override
public void toString(final QueryString qs) {
qs.token("matches").params(exprs);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@ public QNm read(final DataInput in, final QueryContext qc) throws IOException {
/** Name. */
private final byte[] name;
/** Parent type. */
private final AtomType parent;
public final AtomType parent;
/** URI. */
private final byte[] uri;

Expand Down
58 changes: 58 additions & 0 deletions basex-core/src/test/java/org/basex/query/func/FnModuleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,64 @@ public final class FnModuleTest extends SandboxTest {
error(func.args(" put#2", " [<_/>, '']"), FUNCUP_X);
}

/** Test method. */
@Test public void atomicTypeAnnotation() {
final Function func = ATOMIC_TYPE_ANNOTATION;

query("atomic-type-annotation(23) ? name", "integer");
query("let $x := 23, $y := 93.7 return " + func.args(" $x") + "? matches($y)", false);
query("atomic-type-annotation(xs:numeric('23.2')) ? name", "double");

final String q1 = func.args(" <a>42</a>");
query(q1, "{\"name\":untypedAtomic,\"is-simple\":true(),\"base-type\":(anonymous-function)#0,"
+ "\"primitive-type\":(anonymous-function)#0,\"variety\":\"atomic\",\"matches\":"
+ "(anonymous-function)#1,\"constructor\":xs:untypedAtomic#1}");
query(q1 + "?name eq xs:QName('xs:untypedAtomic')", true);
query(q1 + "?is-simple", true);
query(q1 + "?variety", "atomic");
query(q1 + "?base-type()?name eq xs:QName('xs:anyAtomicType')", true);
query(q1 + "?base-type()?is-simple", true);
query(q1 + "?base-type()?variety", "atomic");
query(q1 + "?base-type()?base-type()?name eq xs:QName('xs:anySimpleType')", true);
query(q1 + "?base-type()?base-type()?is-simple", true);
query(q1 + "?base-type()?base-type()=> map:contains('variety')", false);
query(q1 + "?base-type()?base-type()?base-type()?name eq xs:QName('xs:anyType')", true);
query(q1 + "?base-type()?base-type()?base-type()?is-simple", false);
query(q1 + "?base-type()?base-type()?base-type()?variety", "mixed");
query(q1 + "?base-type()?base-type()?base-type()?base-type()", "");
query(q1 + "?primitive-type()?name eq xs:QName('xs:untypedAtomic')", true);
query(q1 + "=> map:contains('members')", false);
query(q1 + "=> map:contains('simple-content-type')", false);
query(q1 + "?matches(<a>abc</a>)", true);
query(q1 + "?constructor(<a>abc</a>)", "abc");

final String q2 = "let $q2 := " + func.args(" xs:unsignedByte(255)") + "\n return $q2";
query(q2, "{\"name\":unsignedByte,\"is-simple\":true(),\"base-type\":(anonymous-function)#0,"
+ "\"primitive-type\":(anonymous-function)#0,\"variety\":\"atomic\",\"matches\":"
+ "(anonymous-function)#1,\"constructor\":xs:unsignedByte#1}");
query(q2 + "?name eq xs:QName('xs:unsignedByte')", true);
query(q2 + "?is-simple", true);
query(q2 + "?base-type()?name eq xs:QName('xs:unsignedShort')", true);
query(q2 + "?base-type()?is-simple", true);
query(q2 + "?base-type()?matches($q2?base-type()?constructor(255))", true);
query(q2 + "?primitive-type()?name eq xs:QName('xs:decimal')", true);
query(q2 + "?primitive-type()?base-type()?name eq xs:QName('xs:anyAtomicType')", true);
query(q2 + "?primitive-type() => deep-equal($q2?base-type()?primitive-type())", true);
query(q2 + "?variety", "atomic");
query(q2 + "=> map:contains('members')", false);
query(q2 + "=> map:contains('simple-content-type')", false);
query(q2 + "?matches($q2?constructor(255))", true);
query(q2 + "?matches($q2?base-type()?constructor(255))", false);
query(q2 + "?constructor(255) => ($q2?matches)()", true);

error(q2 + "?constructor(256)", FUNCCAST_X_X_X);

error(func.args(" ()"), EMPTYFOUND);
error(func.args(" []"), EMPTYFOUND);
error(func.args(" {}"), FIATOMIZE_X);
error(func.args(" [1, 2]"), SEQFOUND_X);
}

/** Test method. */
@Test public void avg() {
final Function func = AVG;
Expand Down

0 comments on commit f240c6a

Please sign in to comment.