mirror of
https://github.com/blackboxprogramming/lucidia.git
synced 2026-03-17 07:57:19 -05:00
71 lines
1.5 KiB
Python
71 lines
1.5 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import List, Union
|
|
|
|
# A tiny placeholder AST for expressions like: "a AND NOT b"
|
|
@dataclass
|
|
class Atom:
|
|
name: str
|
|
|
|
@dataclass
|
|
class Not:
|
|
expr: "Expr"
|
|
|
|
@dataclass
|
|
class BinOp:
|
|
op: str
|
|
left: "Expr"
|
|
right: "Expr"
|
|
|
|
Expr = Union[Atom, Not, BinOp]
|
|
|
|
|
|
def tokenize(s: str) -> List[str]:
|
|
return s.replace("(", " ( ").replace(")", " ) ").split()
|
|
|
|
|
|
def parse(tokens: List[str]) -> Expr:
|
|
"""
|
|
Very small, permissive parser:
|
|
grammar ~> expr := term (("AND"|"OR") term)*
|
|
term := "NOT" term | atom | "(" expr ")"
|
|
atom := /[A-Za-z_][A-Za-z0-9_]*/
|
|
"""
|
|
pos = 0
|
|
|
|
def peek() -> str | None:
|
|
return tokens[pos] if pos < len(tokens) else None
|
|
|
|
def eat() -> str:
|
|
nonlocal pos
|
|
tok = tokens[pos]
|
|
pos += 1
|
|
return tok
|
|
|
|
def parse_term() -> Expr:
|
|
t = peek()
|
|
if t is None:
|
|
raise ValueError("unexpected end")
|
|
if t == "NOT":
|
|
eat()
|
|
return Not(parse_term())
|
|
if t == "(":
|
|
eat()
|
|
node = parse_expr()
|
|
if eat() != ")":
|
|
raise ValueError("expected ')'")
|
|
return node
|
|
# atom
|
|
return Atom(eat())
|
|
|
|
def parse_expr() -> Expr:
|
|
left = parse_term()
|
|
while peek() in ("AND", "OR"):
|
|
op = eat()
|
|
right = parse_term()
|
|
left = BinOp(op, left, right)
|
|
return left
|
|
|
|
return parse_expr()
|