Kano001's picture
Upload 3077 files
6a86ad5 verified
raw
history blame
4.46 kB
import os
import logging
import re
from sympy.external import import_module
from sympy.parsing.latex.lark.transformer import TransformToSymPyExpr
_lark = import_module("lark")
class LarkLaTeXParser:
r"""Class for converting input `\mathrm{\LaTeX}` strings into SymPy Expressions.
It holds all the necessary internal data for doing so, and exposes hooks for
customizing its behavior.
Parameters
==========
print_debug_output : bool, optional
If set to ``True``, prints debug output to the logger. Defaults to ``False``.
transform : bool, optional
If set to ``True``, the class runs the Transformer class on the parse tree
generated by running ``Lark.parse`` on the input string. Defaults to ``True``.
Setting it to ``False`` can help with debugging the `\mathrm{\LaTeX}` grammar.
grammar_file : str, optional
The path to the grammar file that the parser should use. If set to ``None``,
it uses the default grammar, which is in ``grammar/latex.lark``, relative to
the ``sympy/parsing/latex/lark/`` directory.
transformer : str, optional
The name of the Transformer class to use. If set to ``None``, it uses the
default transformer class, which is :py:func:`TransformToSymPyExpr`.
"""
def __init__(self, print_debug_output=False, transform=True, grammar_file=None, transformer=None):
grammar_dir_path = os.path.join(os.path.dirname(__file__), "grammar/")
if grammar_file is None:
with open(os.path.join(grammar_dir_path, "latex.lark"), encoding="utf-8") as f:
latex_grammar = f.read()
else:
with open(grammar_file, encoding="utf-8") as f:
latex_grammar = f.read()
self.parser = _lark.Lark(
latex_grammar,
source_path=grammar_dir_path,
parser="earley",
start="latex_string",
lexer="auto",
ambiguity="explicit",
propagate_positions=False,
maybe_placeholders=False,
keep_all_tokens=True)
self.print_debug_output = print_debug_output
self.transform_expr = transform
if transformer is None:
self.transformer = TransformToSymPyExpr()
else:
self.transformer = transformer()
def doparse(self, s: str):
if self.print_debug_output:
_lark.logger.setLevel(logging.DEBUG)
parse_tree = self.parser.parse(s)
if not self.transform_expr:
# exit early and return the parse tree
_lark.logger.debug("expression = %s", s)
_lark.logger.debug(parse_tree)
_lark.logger.debug(parse_tree.pretty())
return parse_tree
if self.print_debug_output:
# print this stuff before attempting to run the transformer
_lark.logger.debug("expression = %s", s)
# print the `parse_tree` variable
_lark.logger.debug(parse_tree.pretty())
sympy_expression = self.transformer.transform(parse_tree)
if self.print_debug_output:
_lark.logger.debug("SymPy expression = %s", sympy_expression)
return sympy_expression
if _lark is not None:
_lark_latex_parser = LarkLaTeXParser()
def parse_latex_lark(s: str):
"""
Experimental LaTeX parser using Lark.
This function is still under development and its API may change with the
next releases of SymPy.
"""
if _lark is None:
raise ImportError("Lark is probably not installed")
return _lark_latex_parser.doparse(s)
def _pretty_print_lark_trees(tree, indent=0, show_expr=True):
if isinstance(tree, _lark.Token):
return tree.value
data = str(tree.data)
is_expr = data.startswith("expression")
if is_expr:
data = re.sub(r"^expression", "E", data)
is_ambig = (data == "_ambig")
if is_ambig:
new_indent = indent + 2
else:
new_indent = indent
output = ""
show_node = not is_expr or show_expr
if show_node:
output += str(data) + "("
if is_ambig:
output += "\n" + "\n".join([" " * new_indent + _pretty_print_lark_trees(i, new_indent, show_expr) for i in tree.children])
else:
output += ",".join([_pretty_print_lark_trees(i, new_indent, show_expr) for i in tree.children])
if show_node:
output += ")"
return output