""" raymarch toy usage: march ... ::= "." | ::= | "(" ")" example: march cube.pos(0, 0, 0).color(red) cam(1.0).pos(1, 1, 1).lookat(0, 0, 0) """ from dataclasses import dataclass import regex as re from dataclasses import dataclass @dataclass class Command: id: str args: list[str] class CommandChainParser: CHAIN_PATTERN = r"^(([a-zA-Z0-9]+(?:\(([^()]*|(?1)+)(\s*\,\s*(?1))*\))?)(\.(?1))*)" def __init__(self, _command_chain: str) -> None: self.command_chain = _command_chain def __iter__(self): return self def __next__(self): if query := re.match(CommandChainParser.CHAIN_PATTERN, self.command_chain): cmd_chain = query[0] self.command_chain = self.command_chain[len(cmd_chain) + 1 :] return cmd_chain raise StopIteration class CommandParser: CMD_PATTERN = r"^[a-zA-Z]+(?:\(([0-9.\-+]+|(([a-zA-Z0-9]+(?:\(([^()]*|(?1)+)(\s*\,\s*(?1))*\))?)(\.(?1))*))(\s*\,\s*(?1))*\))?" ID_PATTERN = r"^[a-zA-Z]+(?=\(|\.|$)" ARG_PATTERN = CommandChainParser.CHAIN_PATTERN TRIM_PATTERN = r"^\s*\,\s*" def __init__(self, _command: str) -> None: self.command = _command def __iter__(self): return self def __next__(self): if query := re.match(CommandParser.CMD_PATTERN, self.command): cmd = query[0] if cmd_id_qry := re.match(CommandParser.ID_PATTERN, cmd): id = cmd_id_qry[0] cmd_args = cmd[len(id) + 1 : -1] # .replace(" ", "").split(",") args: list[str] = [] self.command = self.command[len(cmd) + 1 :] while cmd_arg_qry := re.match(CommandParser.ARG_PATTERN, cmd_args): arg = cmd_arg_qry[0] args.append(arg) cmd_args = cmd_args[len(arg) :] if trim_qry := re.match(CommandParser.TRIM_PATTERN, cmd_args): cmd_args = cmd_args[len(trim_qry[0]) :] # while "" in cmd_args: # cmd_args.remove("") return Command(id, args) raise StopIteration