From 74ac9c5381f3a7bb69d4f07c24e66fa99084729a Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Mon, 18 May 2026 12:18:41 +0200 Subject: [PATCH] feat(parser): parse Midas type constraints --- core/ast/midas.py | 28 +++++++++++++++++++++++++++- core/ast/printer.py | 36 ++++++++++++++++++++++++++++-------- parser/annotations.py | 2 -- parser/midas.py | 35 +++++++++++++++++++++++++++++++++-- 4 files changed, 88 insertions(+), 13 deletions(-) diff --git a/core/ast/midas.py b/core/ast/midas.py index 6d1b035..4f2b03f 100644 --- a/core/ast/midas.py +++ b/core/ast/midas.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 @@ -79,6 +79,12 @@ 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: ... @@ -89,6 +95,22 @@ class Expr(ABC): def visit_type_body_expr(self, expr: TypeBodyExpr) -> 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 @@ -100,6 +122,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 4fdc7fb..1fd48de 100644 --- a/core/ast/printer.py +++ b/core/ast/printer.py @@ -102,7 +102,7 @@ class AnnotationAstPrinter(AstPrinter, a.Expr.Visitor[None], a.Stmt.Visitor[None if i == len(expr.constraints) - 1: self._mark_last() constraint.accept(self) - + def visit_constraint_expr(self, expr: a.ConstraintExpr) -> None: self._write_line("ConstraintExpr") with self._child_level(): @@ -110,9 +110,9 @@ class AnnotationAstPrinter(AstPrinter, a.Expr.Visitor[None], a.Stmt.Visitor[None 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() @@ -133,14 +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) + self._write_line(f"value: {expr.value}", last=True) class AnnotationPrinter(a.Expr.Visitor[str], a.Stmt.Visitor[str]): @@ -158,12 +158,12 @@ class AnnotationPrinter(a.Expr.Visitor[str], a.Stmt.Visitor[str]): for constraint in expr.constraints: parts.append("(" + constraint.accept(self) + ")") return " + ".join(parts) - + def visit_constraint_expr(self, expr: a.ConstraintExpr) -> str: parts: list[str] = [ expr.left.accept(self), expr.op.lexeme, - expr.right.accept(self) + expr.right.accept(self), ] return " ".join(parts) @@ -257,6 +257,18 @@ class MidasAstPrinter(AstPrinter, m.Expr.Visitor[None], m.Stmt.Visitor[None]): def visit_constraint_expr(self, expr: m.ConstraintExpr): self._write_line("ConstraintExpr") + 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_type_body_expr(self, expr: m.TypeBodyExpr): self._write_line("TypeBodyExpr") @@ -268,3 +280,11 @@ class MidasAstPrinter(AstPrinter, m.Expr.Visitor[None], m.Stmt.Visitor[None]): if i == len(expr.properties) - 1: self._mark_last() property.accept(self) + + def visit_wildcard_expr(self, expr: m.WildcardExpr) -> None: + self._write_line("WildcardExpr") + + def visit_literal_expr(self, expr: m.LiteralExpr) -> None: + self._write_line("LiteralExpr") + with self._child_level(): + self._write_line(f"value: {expr.value}", last=True) diff --git a/parser/annotations.py b/parser/annotations.py index 5b75762..0bf99d6 100644 --- a/parser/annotations.py +++ b/parser/annotations.py @@ -68,8 +68,6 @@ class AnnotationParser(Parser): constraints: list[ConstraintExpr] = [] while not self.is_at_end() and self.match(TokenType.PLUS): - print(self.peek()) - print(self.tokens) self.consume(TokenType.LEFT_PAREN, "Expected '(' before type constraint") constraints.append(self.constraint_expr()) self.consume(TokenType.RIGHT_PAREN, "Expected ')' after type constraint") diff --git a/parser/midas.py b/parser/midas.py index bf7c1d7..631e51c 100644 --- a/parser/midas.py +++ b/parser/midas.py @@ -3,12 +3,15 @@ from typing import Optional from core.ast.midas import ( ConstraintExpr, ConstraintStmt, + Expr, + LiteralExpr, OpStmt, PropertyStmt, Stmt, TypeBodyExpr, TypeExpr, TypeStmt, + WildcardExpr, ) from lexer.token import Token, TokenType from parser.base import Parser @@ -96,7 +99,9 @@ class MidasParser(Parser): constraints: list[ConstraintExpr] = [] while not self.is_at_end() and self.match(TokenType.PLUS): + self.consume(TokenType.LEFT_PAREN, "Expected '(' before type constraint") constraints.append(self.constraint_expr()) + self.consume(TokenType.RIGHT_PAREN, "Expected ')' after type constraint") return TypeExpr(name=name, constraints=constraints) @@ -106,8 +111,34 @@ class MidasParser(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 type_body_expr(self) -> TypeBodyExpr: """Parse a type definition body