Search code examples
lark-parser

Why is my example to parse bold text with Lark not working?


I defined the following grammar:

from lark import Lark

grammar = r'''
    start: ( bold_text | text)
    bold_text.2: "**" text "**"
    text.1: /.+/
'''

parser = Lark(grammar, start = "start", debug=True, ambiguity="explicit")

text = r"""**test**"""

parsed = parser.parse(text)
print(parsed.pretty())

For "**test**", this parses "text" and not "bold_text" as expected. Lark does not even show an ambiguity. What am I doing wrong? Any help would be greatly appreciated. Thank you.


Solution

  • Lark is using greedy regular expressions as the infrastructure of its lexer, as is common for parsers.

    The odd behavior you're seeing happens because when bold_text matches the text rule, its regexp also catches the two stars at the end, because they are matched by .

    A way to fix your grammar would be to do this instead:

    start: bold_text | text
    bold_text.2: "**" text "**"
    text.1: /[^*]+/
    

    That does add some difficulties, since there are situations when you might want to allow * as part of the text. But there are tricks to work around it.

    You can even use this solution with LALR, which will run much faster.

    An "easier" solution would be to enable the complete-Earley feature of Lark, which isn't affected by the greediness of regexps (but it also runs much slower):

    grammar = r'''
        start: ( bold_text | text)
        bold_text.2: "**" text "**"
        text.1: /.+/
    '''
    
    parser = Lark(grammar, start = "start", debug=True, ambiguity="explicit", lexer="dynamic_complete")
    

    And the result is -

    _ambig
      start
        bold_text
          text      test
      start
        text        **test**
    

    But using complete-Earley for very large texts will prove to performance-intensive, especially if you use catch-all regexps like .+.

    Having said all that, Lark is better suited for parsing structured languages like Python than for free-form text like markup. You might want to consider writing a specialized parser (aka "hand-written parser") for this task.