From 8bc091851702d676a2360dcbdd771805f9d57bf6 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Mon, 18 May 2026 11:27:52 +0200 Subject: [PATCH] feat(parser): parse annotation type constraints --- core/ast/annotations.py | 30 ++++++++++++++++++++++++++++-- core/ast/printer.py | 35 ++++++++++++++++++++++++++++++++--- parser/annotations.py | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/core/ast/annotations.py b/core/ast/annotations.py index c5dce93..a885e29 100644 --- a/core/ast/annotations.py +++ b/core/ast/annotations.py @@ -2,7 +2,7 @@ from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Generic, Optional, TypeVar +from typing import Any, Generic, Optional, TypeVar from lexer.token import Token @@ -34,9 +34,15 @@ class Expr(ABC): def accept(self, visitor: Visitor[T]) -> T: ... class Visitor(ABC, Generic[T]): + @abstractmethod + def visit_wildcard_expr(self, expr: WildcardExpr) -> T: ... + + @abstractmethod + def visit_literal_expr(self, expr: LiteralExpr) -> T: ... + @abstractmethod def visit_type_expr(self, expr: TypeExpr) -> T: ... - + @abstractmethod def visit_constraint_expr(self, expr: ConstraintExpr) -> T: ... @@ -47,6 +53,22 @@ class Expr(ABC): def visit_schema_element_expr(self, expr: SchemaElementExpr) -> T: ... +@dataclass(frozen=True) +class WildcardExpr(Expr): + token: Token + + def accept(self, visitor: Expr.Visitor[T]) -> T: + return visitor.visit_wildcard_expr(self) + + +@dataclass(frozen=True) +class LiteralExpr(Expr): + value: Any + + def accept(self, visitor: Expr.Visitor[T]) -> T: + return visitor.visit_literal_expr(self) + + @dataclass(frozen=True) class TypeExpr(Expr): name: Token @@ -58,6 +80,10 @@ class TypeExpr(Expr): @dataclass(frozen=True) class ConstraintExpr(Expr): + left: Expr + op: Token + right: Expr + def accept(self, visitor: Expr.Visitor[T]) -> T: return visitor.visit_constraint_expr(self) diff --git a/core/ast/printer.py b/core/ast/printer.py index 2b338c6..4fdc7fb 100644 --- a/core/ast/printer.py +++ b/core/ast/printer.py @@ -105,7 +105,18 @@ class AnnotationAstPrinter(AstPrinter, a.Expr.Visitor[None], a.Stmt.Visitor[None def visit_constraint_expr(self, expr: a.ConstraintExpr) -> None: self._write_line("ConstraintExpr") - # TODO + with self._child_level(): + self._write_line("left") + with self._child_level(): + self._mark_last() + expr.left.accept(self) + + self._write_line(f"operator: {expr.op.lexeme}") + + self._write_line("right", last=True) + with self._child_level(): + self._mark_last() + expr.right.accept(self) def visit_schema_expr(self, expr: a.SchemaExpr): self._write_line("SchemaExpr") @@ -122,6 +133,14 @@ class AnnotationAstPrinter(AstPrinter, a.Expr.Visitor[None], a.Stmt.Visitor[None name_text: str = "None" if expr.name is None else f'"{expr.name.lexeme}"' self._write_line(f"name: {name_text}") self._write_optional_child("type", expr.type, last=True) + + def visit_wildcard_expr(self, expr: a.WildcardExpr) -> None: + self._write_line("WildcardExpr") + + def visit_literal_expr(self, expr: a.LiteralExpr) -> None: + self._write_line("LiteralExpr") + with self._child_level(): + self._write_line(f'value: {expr.value}', last=True) class AnnotationPrinter(a.Expr.Visitor[str], a.Stmt.Visitor[str]): @@ -141,8 +160,12 @@ class AnnotationPrinter(a.Expr.Visitor[str], a.Stmt.Visitor[str]): return " + ".join(parts) def visit_constraint_expr(self, expr: a.ConstraintExpr) -> str: - # TODO - return "" + parts: list[str] = [ + expr.left.accept(self), + expr.op.lexeme, + expr.right.accept(self) + ] + return " ".join(parts) def visit_schema_expr(self, expr: a.SchemaExpr) -> str: res: str = expr.left.lexeme @@ -161,6 +184,12 @@ class AnnotationPrinter(a.Expr.Visitor[str], a.Stmt.Visitor[str]): parts.append(expr.type.accept(self)) return ": ".join(parts) + def visit_wildcard_expr(self, expr: a.WildcardExpr) -> str: + return "_" + + def visit_literal_expr(self, expr: a.LiteralExpr) -> str: + return str(expr.value) + class MidasAstPrinter(AstPrinter, m.Expr.Visitor[None], m.Stmt.Visitor[None]): def visit_type_stmt(self, stmt: m.TypeStmt): diff --git a/parser/annotations.py b/parser/annotations.py index e90cf43..5b75762 100644 --- a/parser/annotations.py +++ b/parser/annotations.py @@ -4,10 +4,12 @@ from core.ast.annotations import ( AnnotationStmt, ConstraintExpr, Expr, + LiteralExpr, SchemaElementExpr, SchemaExpr, Stmt, TypeExpr, + WildcardExpr, ) from lexer.token import Token, TokenType from parser.base import Parser @@ -80,8 +82,34 @@ class AnnotationParser(Parser): Returns: ConstraintExpr: the parsed type constraint expression """ - # TODO - return ConstraintExpr() + + left: Expr = self.constraint_value() + op: Token = self.constraint_operator() + right: Expr = self.constraint_value() + return ConstraintExpr(left=left, op=op, right=right) + + def constraint_value(self) -> Expr: + if self.match(TokenType.UNDERSCORE): + return WildcardExpr(self.previous()) + return self.literal() + + def literal(self) -> LiteralExpr: + if self.match(TokenType.FALSE): + return LiteralExpr(False) + if self.match(TokenType.TRUE): + return LiteralExpr(True) + if self.match(TokenType.NONE): + return LiteralExpr(None) + + if self.match(TokenType.NUMBER): + return LiteralExpr(self.previous().value) + + raise self.error(self.peek(), "Expected literal") + + def constraint_operator(self) -> Token: + if self.match(TokenType.LESS, TokenType.LESS_EQUAL, TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.EQUAL_EQUAL, TokenType.BANG_EQUAL): + return self.previous() + raise self.error(self.peek(), "Expected constraint operator") def schema(self) -> SchemaExpr: """Parse a schema definition