#!/usr/bin/env python3 import re import sys import argparse import os import stat from dataclasses import dataclass from typing import List, Union TOKEN_SPECIFICATION = [ ('IMPORT', r'import'), ('EVAL', r'eval'), ('OPTION', r'option'), ('DMENU', r'dmenu'), ('LPAREN', r'\('), ('RPAREN', r'\)'), ('STRING', r'"(?:\\.|[^"\\])*"|\'[^\']*\'|`[^`]*`'), ('SKIP', r'[\n \t]+'), ('MISMATCH', r'.'), ] TOKEN_REGEX = '|'.join(f'(?P<{name}>{pattern})' for name, pattern in TOKEN_SPECIFICATION) @dataclass class Token: type: str value: str pos: int def __repr__(self): return f'Token({self.type}, {self.value}, pos={self.pos})' def tokenize(code): tokens = [] for mo in re.finditer(TOKEN_REGEX, code): kind = mo.lastgroup value = mo.group() pos = mo.start() if kind == 'SKIP': continue elif kind == 'MISMATCH': raise RuntimeError(f'Unexpected token {value!r} at position {pos}') else: tokens.append(Token(kind, value, pos)) return tokens def restore_string(string_tkn_val: str): if string_tkn_val[0] == '"': return string_tkn_val[1:-1].replace("\\\"", "\"").replace("\\\\", "\\") else: return string_tkn_val[1:-1] class ActionDmenu: ... @dataclass class OptionInDmenu: label: str action: Union[ActionDmenu, str] def __repr__(self): if isinstance(self.action, ActionDmenu): return f"option {self.label} {self.action}" else: return f"option {self.label} eval {self.action}" @dataclass class ActionDmenu: prompt: str options: List[OptionInDmenu] def __repr__(self): return f"dmenu {self.prompt} ( {' ; '.join(map(str, self.options))} )" class Parser: def __init__(self, tokens): self.tokens = tokens self.pos = 0 def current(self): if self.pos < len(self.tokens): return self.tokens[self.pos] return None def eat(self, token_type) -> Token: token = self.current() if token is None: raise RuntimeError("Unexpected end of input") if token.type == token_type: self.pos += 1 return token raise RuntimeError(f"Expected token {token_type} at position {token.pos} but got {token.type}") # Grammar production: