feat(parser): parse Midas type constraints

This commit is contained in:
2026-05-18 12:18:41 +02:00
parent e10d71a66b
commit 74ac9c5381
4 changed files with 88 additions and 13 deletions

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from typing import Generic, Optional, TypeVar from typing import Any, Generic, Optional, TypeVar
from lexer.token import Token from lexer.token import Token
@@ -79,6 +79,12 @@ class Expr(ABC):
def accept(self, visitor: Visitor[T]) -> T: ... def accept(self, visitor: Visitor[T]) -> T: ...
class Visitor(ABC, Generic[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 @abstractmethod
def visit_type_expr(self, expr: TypeExpr) -> T: ... def visit_type_expr(self, expr: TypeExpr) -> T: ...
@@ -89,6 +95,22 @@ class Expr(ABC):
def visit_type_body_expr(self, expr: TypeBodyExpr) -> T: ... 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) @dataclass(frozen=True)
class TypeExpr(Expr): class TypeExpr(Expr):
name: Token name: Token
@@ -100,6 +122,10 @@ class TypeExpr(Expr):
@dataclass(frozen=True) @dataclass(frozen=True)
class ConstraintExpr(Expr): class ConstraintExpr(Expr):
left: Expr
op: Token
right: Expr
def accept(self, visitor: Expr.Visitor[T]) -> T: def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_constraint_expr(self) return visitor.visit_constraint_expr(self)

View File

@@ -102,7 +102,7 @@ class AnnotationAstPrinter(AstPrinter, a.Expr.Visitor[None], a.Stmt.Visitor[None
if i == len(expr.constraints) - 1: if i == len(expr.constraints) - 1:
self._mark_last() self._mark_last()
constraint.accept(self) constraint.accept(self)
def visit_constraint_expr(self, expr: a.ConstraintExpr) -> None: def visit_constraint_expr(self, expr: a.ConstraintExpr) -> None:
self._write_line("ConstraintExpr") self._write_line("ConstraintExpr")
with self._child_level(): with self._child_level():
@@ -110,9 +110,9 @@ class AnnotationAstPrinter(AstPrinter, a.Expr.Visitor[None], a.Stmt.Visitor[None
with self._child_level(): with self._child_level():
self._mark_last() self._mark_last()
expr.left.accept(self) expr.left.accept(self)
self._write_line(f"operator: {expr.op.lexeme}") self._write_line(f"operator: {expr.op.lexeme}")
self._write_line("right", last=True) self._write_line("right", last=True)
with self._child_level(): with self._child_level():
self._mark_last() 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}"' name_text: str = "None" if expr.name is None else f'"{expr.name.lexeme}"'
self._write_line(f"name: {name_text}") self._write_line(f"name: {name_text}")
self._write_optional_child("type", expr.type, last=True) self._write_optional_child("type", expr.type, last=True)
def visit_wildcard_expr(self, expr: a.WildcardExpr) -> None: def visit_wildcard_expr(self, expr: a.WildcardExpr) -> None:
self._write_line("WildcardExpr") self._write_line("WildcardExpr")
def visit_literal_expr(self, expr: a.LiteralExpr) -> None: def visit_literal_expr(self, expr: a.LiteralExpr) -> None:
self._write_line("LiteralExpr") self._write_line("LiteralExpr")
with self._child_level(): 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]): 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: for constraint in expr.constraints:
parts.append("(" + constraint.accept(self) + ")") parts.append("(" + constraint.accept(self) + ")")
return " + ".join(parts) return " + ".join(parts)
def visit_constraint_expr(self, expr: a.ConstraintExpr) -> str: def visit_constraint_expr(self, expr: a.ConstraintExpr) -> str:
parts: list[str] = [ parts: list[str] = [
expr.left.accept(self), expr.left.accept(self),
expr.op.lexeme, expr.op.lexeme,
expr.right.accept(self) expr.right.accept(self),
] ]
return " ".join(parts) 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): def visit_constraint_expr(self, expr: m.ConstraintExpr):
self._write_line("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): def visit_type_body_expr(self, expr: m.TypeBodyExpr):
self._write_line("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: if i == len(expr.properties) - 1:
self._mark_last() self._mark_last()
property.accept(self) 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)

View File

@@ -68,8 +68,6 @@ class AnnotationParser(Parser):
constraints: list[ConstraintExpr] = [] constraints: list[ConstraintExpr] = []
while not self.is_at_end() and self.match(TokenType.PLUS): 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") self.consume(TokenType.LEFT_PAREN, "Expected '(' before type constraint")
constraints.append(self.constraint_expr()) constraints.append(self.constraint_expr())
self.consume(TokenType.RIGHT_PAREN, "Expected ')' after type constraint") self.consume(TokenType.RIGHT_PAREN, "Expected ')' after type constraint")

View File

@@ -3,12 +3,15 @@ from typing import Optional
from core.ast.midas import ( from core.ast.midas import (
ConstraintExpr, ConstraintExpr,
ConstraintStmt, ConstraintStmt,
Expr,
LiteralExpr,
OpStmt, OpStmt,
PropertyStmt, PropertyStmt,
Stmt, Stmt,
TypeBodyExpr, TypeBodyExpr,
TypeExpr, TypeExpr,
TypeStmt, TypeStmt,
WildcardExpr,
) )
from lexer.token import Token, TokenType from lexer.token import Token, TokenType
from parser.base import Parser from parser.base import Parser
@@ -96,7 +99,9 @@ class MidasParser(Parser):
constraints: list[ConstraintExpr] = [] constraints: list[ConstraintExpr] = []
while not self.is_at_end() and self.match(TokenType.PLUS): 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()) constraints.append(self.constraint_expr())
self.consume(TokenType.RIGHT_PAREN, "Expected ')' after type constraint")
return TypeExpr(name=name, constraints=constraints) return TypeExpr(name=name, constraints=constraints)
@@ -106,8 +111,34 @@ class MidasParser(Parser):
Returns: Returns:
ConstraintExpr: the parsed type constraint expression 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: def type_body_expr(self) -> TypeBodyExpr:
"""Parse a type definition body """Parse a type definition body