diff --git a/src/main/antlr/SparqlLexer.g4 b/src/main/antlr/SparqlLexer.g4 index 75b967818..fdb951589 100644 --- a/src/main/antlr/SparqlLexer.g4 +++ b/src/main/antlr/SparqlLexer.g4 @@ -236,7 +236,7 @@ fragment PN_CHARS PN_PREFIX: PN_CHARS_BASE ((PN_CHARS | '.')* PN_CHARS)?; -PN_LOCAL: (PN_CHARS_U | ':' | DIGIT | PLX) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX))?; +fragment PN_LOCAL: (PN_CHARS_U | ':' | DIGIT | PLX) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX))?; fragment PLX: PERCENT | PN_LOCAL_ESC; diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserBgpTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserBgpTest.java index 9dd6be671..88a2752dd 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserBgpTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserBgpTest.java @@ -141,6 +141,78 @@ void shouldParseRdfLiteralWithDatatype() { assertEquals("xsd:integer", lit.datatype()); } + @Test + void shouldParsePrefixedNameWithDigitOnlyLocalPart() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + PREFIX ex: + SELECT * WHERE { + ?s ex:1 ?o . + } + """); + + BgpAst bgp = (BgpAst) ast.whereClause().patterns().getFirst(); + TriplePatternAst triple = bgp.triples().getFirst(); + + assertInstanceOf(IriAst.class, triple.predicate()); + assertEquals("ex:1", ((IriAst) triple.predicate()).raw()); + } + + @Test + void shouldParsePrefixedNameWithHyphenInLocalPart() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + PREFIX ex: + SELECT * WHERE { + ?s ex:abc-def ?o . + } + """); + + BgpAst bgp = (BgpAst) ast.whereClause().patterns().getFirst(); + TriplePatternAst triple = bgp.triples().getFirst(); + + assertInstanceOf(IriAst.class, triple.predicate()); + assertEquals("ex:abc-def", ((IriAst) triple.predicate()).raw()); + } + + @Test + void shouldParsePrefixedNameWithEscapedReservedCharacterInLocalPart() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + PREFIX ns: + SELECT * WHERE { + ?s ns:id\\=123 ?o . + } + """); + + BgpAst bgp = (BgpAst) ast.whereClause().patterns().getFirst(); + TriplePatternAst triple = bgp.triples().getFirst(); + + assertInstanceOf(IriAst.class, triple.predicate()); + assertEquals("ns:id\\=123", ((IriAst) triple.predicate()).raw()); + } + + @Test + void shouldParsePrefixedNameWithDefaultPrefix() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + PREFIX : + SELECT * WHERE { + ?s :foo ?o . + } + """); + + BgpAst bgp = (BgpAst) ast.whereClause().patterns().getFirst(); + TriplePatternAst triple = bgp.triples().getFirst(); + + assertInstanceOf(IriAst.class, triple.predicate()); + assertEquals(":foo", ((IriAst) triple.predicate()).raw()); + } + @Test void failFastTrueShouldThrowQuerySyntaxExceptionOnInvalidQuery() { SparqlParser parser = newParser(true, true); diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserBindTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserBindTest.java index e80bd4935..174b72dfd 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserBindTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserBindTest.java @@ -2,6 +2,8 @@ import fr.inria.corese.core.next.query.impl.sparql.ast.*; import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.FunctionCallAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.MultiplyAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.SubtractAst; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -89,6 +91,41 @@ void shouldParseBindWithArithmetic() { assertNotNull(bindAst.expression()); } + @Test + @DisplayName("BIND(?p*(1-?discount) AS ?price) — arithmetic expression without whitespace around minus") + void shouldParseBindWithSubtractWithoutWhitespace() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + SELECT ?price WHERE { + ?x ?p ?discount . + BIND(?p*(1-?discount) AS ?price) + } + """); + + assertNotNull(ast); + GroupGraphPatternAst where = ast.whereClause(); + assertEquals(2, where.patterns().size()); + + PatternAst last = where.patterns().getLast(); + assertInstanceOf(BindAst.class, last); + + BindAst bindAst = (BindAst) last; + assertEquals("price", bindAst.variable().name()); + assertInstanceOf(MultiplyAst.class, bindAst.expression()); + + MultiplyAst multiplyAst = (MultiplyAst) bindAst.expression(); + assertInstanceOf(VarAst.class, multiplyAst.getLeftArgument()); + assertEquals("p", ((VarAst) multiplyAst.getLeftArgument()).name()); + assertInstanceOf(SubtractAst.class, multiplyAst.getRightArgument()); + + SubtractAst subtractAst = (SubtractAst) multiplyAst.getRightArgument(); + assertInstanceOf(LiteralAst.class, subtractAst.getLeftArgument()); + assertEquals("1", ((LiteralAst) subtractAst.getLeftArgument()).lexical()); + assertInstanceOf(VarAst.class, subtractAst.getRightArgument()); + assertEquals("discount", ((VarAst) subtractAst.getRightArgument()).name()); + } + @Test @DisplayName("BIND(\"hello\" AS ?label) — string literal expression") void shouldParseBindWithStringLiteral() { @@ -208,4 +245,4 @@ void shouldAcceptBindVariableInSelectProjection() { assertEquals(1, select.projection().variables().size()); assertEquals("x", select.projection().variables().getFirst().name()); } -} \ No newline at end of file +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserFilterTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserFilterTest.java index 9d2b1a96f..4e04e478e 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserFilterTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserFilterTest.java @@ -1011,6 +1011,37 @@ void shouldParseAddSubChainEqualsFilter() { } + @Test + void shouldParseSubtractWithoutWhitespaceInsideParenthesizedMultiplyExpression() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + SELECT * WHERE { + ?x ?p ?discount . + ?x ?pricePredicate ?price . + FILTER(?p*(1-?discount) = ?price) + } + """); + + FilterAst filterAst = (FilterAst) ast.whereClause().patterns().getLast(); + EqualsAst equalsAst = (EqualsAst) filterAst.operator(); + + assertInstanceOf(MultiplyAst.class, equalsAst.getLeftArgument()); + assertInstanceOf(VarAst.class, equalsAst.getRightArgument()); + assertEquals("price", ((VarAst) equalsAst.getRightArgument()).name()); + + MultiplyAst multiplyAst = (MultiplyAst) equalsAst.getLeftArgument(); + assertInstanceOf(VarAst.class, multiplyAst.getLeftArgument()); + assertEquals("p", ((VarAst) multiplyAst.getLeftArgument()).name()); + assertInstanceOf(SubtractAst.class, multiplyAst.getRightArgument()); + + SubtractAst subtractAst = (SubtractAst) multiplyAst.getRightArgument(); + assertInstanceOf(LiteralAst.class, subtractAst.getLeftArgument()); + assertEquals("1", ((LiteralAst) subtractAst.getLeftArgument()).lexical()); + assertInstanceOf(VarAst.class, subtractAst.getRightArgument()); + assertEquals("discount", ((VarAst) subtractAst.getRightArgument()).name()); + } + @Test void shouldParseUnaryPlusFilter() { SparqlParser parser = newParserDefault();