Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ build-backend = "hatchling.build"

[dependency-groups]
dev = [
"devtools>=0.12.2",
"pytest>=8.3.4",
"rdflib>=7.1.3",
]
8 changes: 6 additions & 2 deletions src/larql/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path

import lark
from larql.utils.validators import UnicodeValidator
from larql.utils.validators import InsertDeleteQuadValidator, UnicodeValidator


_package_path = Path(files("larql")) # type: ignore
Expand All @@ -19,7 +19,11 @@ def __init__(self, query: str) -> None:
self.query = query
self.tree: lark.Tree = sparql_parser.parse(self.query)

self._run_validators(UnicodeValidator())
self._run_validators(
# UnicodeValidator might be useless; UTF-8 should already be checked in the grammar now
UnicodeValidator(),
InsertDeleteQuadValidator(),
)

def _run_validators(self, *validators: lark.Visitor):
"""Run validators against the Tree component."""
Expand Down
42 changes: 40 additions & 2 deletions src/larql/utils/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import lark


class BindScopeValidator(lark.Visitor):
"""Validator for checking that bound variables are not previously used."""


class UnicodeValidator(lark.Visitor):
"""Validator for checking that string terminals are UTF-8 encodable."""

Expand All @@ -18,5 +22,39 @@ def string(self, node):
raise lark.ParseError(message) from e


class GroupByValidator(lark.Visitor):
pass
class InsertDeleteQuadValidator(lark.Visitor):
"""Validator for checking semantic quad constraints in INSERT/DELETE environments."""

def _validate_children(
self, node: lark.Tree, disallowed: set[str], error_message
) -> None:
for child in node.children:
if isinstance(child, lark.Token):
continue
else:
if child.data in disallowed:
message = error_message.format(child=child)
raise lark.ParseError(message)

self._validate_children(child, disallowed, error_message)

def insert_data(self, node: lark.Tree) -> None:
message = "Variable nodes are not allowed in INSERT DATA. Found node '{child}'."
self._validate_children(node, {"var"}, message)

def delete_data(self, node: lark.Tree) -> None:
message = "Variables and blank nodes are not allowed in DELETE DATA contexts. Found node: '{child}'."
self._validate_children(node, {"var", "blank_node"}, message)

def delete_clause(self, node: lark.Tree) -> None:
message = (
"Blank nodes are not allowed in DELETE clauses.\nFound node: '{child}'."
)
self._validate_children(node, {"blank_node"}, message)

def delete_where(self, node: lark.Tree) -> None:
message = (
"Blank nodes are not allowed in DELETE WHERE contexts.\n"
"Found node: '{child}'."
)
self._validate_children(node, {"blank_node"}, message)
7 changes: 2 additions & 5 deletions tests/test_negative_parse_tests.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from pathlib import Path

from lark import LarkError
from larql import SPARQLParser, sparql_parser
import pytest

from lark import LarkError
from larql import SPARQLParser
from tests.discovery import SPARQL11SyntaxTestDiscovery


Expand All @@ -15,8 +15,5 @@ def test_positive_query_parsing(test_query_path):
_query_file_path: Path = Path(test_query_path)
query: str = _query_file_path.read_text()

with pytest.raises(LarkError):
sparql_parser.parse(query)

with pytest.raises(LarkError):
SPARQLParser(query)
3 changes: 1 addition & 2 deletions tests/test_positive_parse_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

from pathlib import Path

from larql import SPARQLParser, sparql_parser
import pytest

from larql import SPARQLParser
from tests.discovery import SPARQL11SyntaxTestDiscovery


Expand All @@ -16,5 +16,4 @@ def test_positive_query_parsing(test_query_path):
_query_file_path: Path = Path(test_query_path)
query = _query_file_path.read_text()

assert sparql_parser.parse(query)
assert SPARQLParser(query)
55 changes: 55 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading