Compare commits

...
2 Commits
Author SHA1 Message Date
HEL 74ac9c5381 feat(parser): parse Midas type constraints 2026-05-18 12:18:41 +02:00
HEL e10d71a66b feat(parser): split annotation and Midas keywords 2026-05-18 11:33:33 +02:00
7 changed files with 103 additions and 17 deletions

No files matched your search

+27 -1
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)
+28 -8
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)
+5 -1
View File
@@ -1,4 +1,5 @@
from lexer.base import Lexer from lexer.base import Lexer
from lexer.keyword import ANNOTATION_KEYWORDS
from lexer.token import TokenType from lexer.token import TokenType
@@ -86,7 +87,10 @@ class AnnotationLexer(Lexer):
""" """
while self.peek().isalnum() or self.peek() == "_": while self.peek().isalnum() or self.peek() == "_":
self.advance() self.advance()
self.add_token(TokenType.IDENTIFIER)
lexeme: str = self.source[self.start : self.idx]
token_type: TokenType = ANNOTATION_KEYWORDS.get(lexeme, TokenType.IDENTIFIER)
self.add_token(token_type)
def scan_comment(self): def scan_comment(self):
"""Scan the rest of a comment and add it as a token """Scan the rest of a comment and add it as a token
+8 -1
View File
@@ -1,9 +1,16 @@
from lexer.token import TokenType from lexer.token import TokenType
KEYWORDS: dict[str, TokenType] = { ANNOTATION_KEYWORDS: dict[str, TokenType] = {
"True": TokenType.TRUE,
"False": TokenType.FALSE,
"None": TokenType.NONE,
}
MIDAS_KEYWORDS: dict[str, TokenType] = {
"type": TokenType.TYPE, "type": TokenType.TYPE,
"op": TokenType.OP, "op": TokenType.OP,
"constraint": TokenType.CONSTRAINT, "constraint": TokenType.CONSTRAINT,
"true": TokenType.TRUE, "true": TokenType.TRUE,
"false": TokenType.FALSE, "false": TokenType.FALSE,
"none": TokenType.NONE,
} }
+2 -2
View File
@@ -1,5 +1,5 @@
from lexer.base import Lexer from lexer.base import Lexer
from lexer.keyword import KEYWORDS from lexer.keyword import MIDAS_KEYWORDS
from lexer.token import TokenType from lexer.token import TokenType
@@ -102,7 +102,7 @@ class MidasLexer(Lexer):
self.advance() self.advance()
lexeme: str = self.source[self.start : self.idx] lexeme: str = self.source[self.start : self.idx]
token_type: TokenType = KEYWORDS.get(lexeme, TokenType.IDENTIFIER) token_type: TokenType = MIDAS_KEYWORDS.get(lexeme, TokenType.IDENTIFIER)
self.add_token(token_type) self.add_token(token_type)
def scan_comment(self): def scan_comment(self):
-2
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")
+33 -2
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