diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilder.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilder.java index a4894804b..bc809d71a 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilder.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilder.java @@ -1334,6 +1334,8 @@ public TermAst termFromBuiltInCall(fr.inria.corese.core.next.impl.parser.antlr.S return this.createConstraint(ASTConstants.FUNCTION_CALL.BOUND, List.of(this.var(ctx.var_().getText()))); } else if (ctx.regexExpression() != null) { return termFromRegex(ctx.regexExpression()); + } else if (ctx.STRLEN() != null) { + return new StrLenAst(args.getFirst()); } else { throw new QueryEvaluationException("Unexpected function for a BuiltInCall for token " + ctx.getText()); } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/semantic/support/VariableScopeAnalyzer.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/semantic/support/VariableScopeAnalyzer.java index c2e353b37..fe3e8e230 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/semantic/support/VariableScopeAnalyzer.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/semantic/support/VariableScopeAnalyzer.java @@ -197,6 +197,8 @@ case NotInAst(TermAst left, List candidates) -> { } } + case StrLenAst(TermAst argument) -> collectReferencedVariables(argument, referencedVariables); + case ConstraintAst ignored -> { // Other constraint shapes are ignored until they are supported here. } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/constraint/StrLenAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/constraint/StrLenAst.java new file mode 100644 index 000000000..36a52b00d --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/constraint/StrLenAst.java @@ -0,0 +1,10 @@ +package fr.inria.corese.core.next.query.impl.sparql.ast.constraint; + +import fr.inria.corese.core.next.query.impl.sparql.ast.ConstraintAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.TermAst; + +/** + * Function {@code STRLEN(string)} in SPARQL 1.1 + * Returns the length of a string. + */ +public record StrLenAst(TermAst argument) implements ConstraintAst {} \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserStrLenTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserStrLenTest.java new file mode 100644 index 000000000..073c6c7a7 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserStrLenTest.java @@ -0,0 +1,58 @@ +package fr.inria.corese.core.next.query.impl.parser; + +import fr.inria.corese.core.next.query.impl.sparql.ast.BindAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.FilterAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.LiteralAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.VarAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.EqualsAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.StrLenAst; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@DisplayName("SPARQL 1.1 - Parser and AST : STRLEN") +class SparqlParserStrLenTest extends AbstractSparqlParserFeatureTest { + + @Test + @DisplayName("BIND(STRLEN(?s) AS ?len)") + void shouldParseStrLen() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + SELECT * WHERE { + ?s ?p ?o . + BIND(STRLEN(?s) AS ?len) + } + """); + + assertNotNull(ast); + BindAst bind = assertInstanceOf(BindAst.class, ast.whereClause().patterns().getLast()); + StrLenAst strLen = assertInstanceOf(StrLenAst.class, bind.expression()); + assertEquals("s", assertInstanceOf(VarAst.class, strLen.argument()).name()); + assertEquals("len", bind.variable().name()); + } + + @Test + @DisplayName("FILTER(STRLEN(?s) = 2)") + void shouldParseStrLenInFilter() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + SELECT * WHERE { + ?s ?p ?o . + FILTER(STRLEN(?s) = 2) + } + """); + + assertNotNull(ast); + FilterAst filter = assertInstanceOf(FilterAst.class, ast.whereClause().patterns().getLast()); + EqualsAst equals = assertInstanceOf(EqualsAst.class, filter.operator()); + StrLenAst strLen = assertInstanceOf(StrLenAst.class, equals.getLeftArgument()); + assertEquals("s", assertInstanceOf(VarAst.class, strLen.argument()).name()); + assertEquals("2", assertInstanceOf(LiteralAst.class, equals.getRightArgument()).lexical()); + } +} \ No newline at end of file