import logging
import os
import pickle
import sys
import traceback
import ply
from ply import yacc
from ..configuration import Configuration
configuration = Configuration()
[docs]class Parser:
"""
Abstract base class for Abjad parsers.
Rules objects for lexing and parsing must be defined by overriding the
abstract properties ``lexer_rules_object`` and ``parser_rules_object``.
For most parsers these properties should simply return ``self``.
"""
### CLASS VARIABLES ###
__slots__ = ("_debug", "_lexer", "_logger", "_parser")
_is_abstract = True
### INITIALIZER ###
def __init__(self, debug=False):
self._debug = bool(debug)
self._lexer = None
self._parser = None
if self.debug:
logging.basicConfig(
level=logging.DEBUG,
filename=self.logger_path,
filemode="w",
format="%(filename)10s:%(lineno)8d:%(message)s",
)
self._logger = logging.getLogger()
else:
self._logger = yacc.NullLogger()
self._lexer = ply.lex.lex(
debug=self.debug,
debuglog=self.logger,
object=self.lexer_rules_object,
)
if self.pickle_path and not os.path.exists(self.pickle_path):
try:
directory, _ = os.path.split(self.pickle_path)
if not os.path.exists(directory):
os.makedirs(directory)
string = pickle.dumps(None)
with open(self.pickle_path, "wb") as file_pointer:
file_pointer.write(string)
except (IOError, OSError):
traceback.print_exc()
self._parser = ply.yacc.yacc(
debug=self.debug,
debuglog=self.logger,
module=self.parser_rules_object,
outputdir=self.output_path,
picklefile=self.pickle_path,
)
### SPECIAL METHODS ###
[docs] def __call__(self, string):
"""
Parse ``string`` and return result.
"""
if hasattr(self, "_setup"):
self._setup()
if self.debug:
result = self.parser.parse_debug(
string, lexer=self.lexer, debug=self.logger
)
else:
result = self.parser.parse(string, lexer=self.lexer)
if hasattr(self, "_cleanup"):
result = self._cleanup(result)
return result
### PUBLIC METHODS ###
[docs] def tokenize(self, string):
"""
Tokenize ``string`` and print results.
"""
self.lexer.input(string)
for token in self.lexer:
print(token)
### PUBLIC PROPERTIES ###
@property
def debug(self):
"""
Is true when parser runs in debugging mode.
"""
return self._debug
@property
def lexer(self):
"""
Gets parser's PLY Lexer instance.
"""
return self._lexer
@property
def logger(self):
"""
Gets parser's logger.
"""
return self._logger
@property
def logger_path(self):
"""
Gets parser's logfile output path.
"""
if self.output_path is None:
return None
string = "-".join(str(x) for x in sys.version_info)
file_name = f"parselog_{type(self).__name__}_{string}.txt"
return os.path.join(self.output_path, file_name)
@property
def output_path(self):
"""
Gets output path for files associated with the parser.
"""
output_path = configuration.configuration_directory / "parsers"
if not output_path.is_dir():
try:
os.makedirs(output_path)
except (IOError, OSError):
return None
return output_path
@property
def parser(self):
"""
Gets parser's PLY LRParser instance.
"""
return self._parser
@property
def pickle_path(self):
"""
Gets output path for the parser's pickled parsing tables.
"""
if self.output_path is None:
return None
string = "-".join(str(x) for x in sys.version_info)
file_name = f"parse_tables_{type(self).__name__}_{string}.pkl"
return os.path.join(self.output_path, file_name)