Source code for dpmcore.services.syntax

"""Syntax validation service — no database required."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Optional

from antlr4 import CommonTokenStream, InputStream

from dpmcore.dpm_xl.ast.constructor import ASTVisitor
from dpmcore.dpm_xl.grammar.generated.dpm_xlLexer import dpm_xlLexer
from dpmcore.dpm_xl.grammar.generated.dpm_xlParser import dpm_xlParser
from dpmcore.dpm_xl.grammar.generated.listeners import DPMErrorListener
from dpmcore.errors import SyntaxError_


[docs] @dataclass(frozen=True) class SyntaxResult: """Outcome of a syntax validation.""" is_valid: bool error_message: Optional[str] expression: str
[docs] class SyntaxService: """Validate and parse DPM-XL expression syntax. This service is stateless and does **not** require a database connection. """
[docs] def __init__(self) -> None: """Build a stateless syntax validator.""" # DPMErrorListener lives in grammar/ (untyped generated code). self._error_listener = DPMErrorListener() # type: ignore[no-untyped-call] self._visitor = ASTVisitor()
# ------------------------------------------------------------------ # # Public API # ------------------------------------------------------------------ #
[docs] def validate(self, expression: str) -> SyntaxResult: """Validate the syntax of *expression*. Returns a :class:`SyntaxResult` — never raises on invalid syntax. """ try: self._parse(expression) return SyntaxResult( is_valid=True, error_message=None, expression=expression, ) except (SyntaxError_, SyntaxError) as exc: return SyntaxResult( is_valid=False, error_message=str(exc), expression=expression, ) except Exception as exc: return SyntaxResult( is_valid=False, error_message=f"Unexpected error: {exc}", expression=expression, )
[docs] def parse(self, expression: str) -> Any: """Parse *expression* and return the AST root node. Raises on syntax errors. """ parse_tree = self._parse(expression) return self._visitor.visit(parse_tree)
[docs] def is_valid(self, expression: str) -> bool: """Quick boolean check.""" return self.validate(expression).is_valid
# ------------------------------------------------------------------ # # Internal helpers # ------------------------------------------------------------------ # def _parse(self, expression: str) -> Any: """Run ANTLR lexer + parser, return the parse tree.""" input_stream = InputStream(expression) lexer = dpm_xlLexer(input_stream) lexer._listeners = [self._error_listener] token_stream = CommonTokenStream(lexer) parser = dpm_xlParser(token_stream) parser._listeners = [self._error_listener] # parser.start lives in grammar/ (untyped generated code). tree = parser.start() # type: ignore[no-untyped-call] if parser._syntaxErrors > 0: raise SyntaxError_("Syntax errors detected") return tree