Source code for neologism.yacc

import subprocess
import xml.etree.ElementTree as xml_parser
from os import environ
from tempfile import NamedTemporaryFile
from typing import Optional

from .rule import Rule


[docs] class YaccDecodeError(Exception): pass
def __run_bison(command: list, custom_path: Optional[str] = None) -> subprocess.CompletedProcess: env = environ.copy() if custom_path is not None: env["PATH"] = custom_path return subprocess.run( command, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, env=env, check=False, ) def __yacc2xml(yacc_file_path: str, custom_path: Optional[str] = None): xml_file = NamedTemporaryFile() try: result = __run_bison( ["bison", "--xml=" + xml_file.name, "--output=/dev/null", yacc_file_path], custom_path, ) except FileNotFoundError: raise ChildProcessError("bison executable not found. PATH: {}".format(environ.get("PATH", ""))) if result.returncode != 0: message = "Failed to parse yacc file: {}".format(yacc_file_path) stderr = result.stderr.decode("utf-8", errors="replace").strip() if stderr: message = "{}\n{}".format(message, stderr) raise YaccDecodeError(message) return xml_file def __unescape(keyword: str) -> str: if len(keyword) == 3 and keyword[0] == "'" and keyword[-1] == "'": return keyword.strip("'") return keyword def __xml2rules(xml_file): rules = set() root = xml_parser.parse(xml_file.name).getroot() for rule in root.iter("rule"): lhs = __unescape(rule.find("lhs").text) rhs = [__unescape(symbol.text) for symbol in rule.find("rhs") if symbol.tag != "empty"] rules.add(Rule(lhs, rhs)) return rules def parse(file_path: str, custom_path: Optional[str] = None): return __xml2rules(__yacc2xml(file_path, custom_path))