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();