Search code examples
dartpetitparser

How to match the start and end of a block


I want to define a special code block, which may starts by any combination of characters of {[<#, and the end will be }]>#.

Some example:

{
    block-content
}

{##
    block-content
##}

#[[<{### 
    block-content 
###}>]]#

Is it possible with petitparser-dart?


Solution

  • Yes, back-references are possible, but it is not that straight-forward.

    First we need a function that can reverse our delimiter. I came up with the following:

    String reverseDelimiter(String token) {
      return token.split('').reversed.map((char) {
        if (char == '[') return ']';
        if (char == '{') return '}';
        if (char == '<') return '>';
        return char;
      }).join();
    }
    

    Then we have to declare the stopDelimiter parser. It is undefined at this point, but will be replaced with the real parser as soon as we know it.

    var stopDelimiter = undefined();
    

    In the action of the startDelimiter we replace the stopDelimiter with a dynamically created parser as follows:

    var startDelimiter = pattern('#<{[').plus().flatten().map((String start) {
      stopDelimiter.set(string(reverseDelimiter(start)).flatten());
      return start;
    });
    

    The rest is trivial, but depends on your exact requirements:

    var blockContents = any().starLazy(stopDelimiter).flatten();
    var parser = startDelimiter & blockContents & stopDelimiter;
    

    The code above defines the blockContents parser so that it reads through anything until the matching stopDelimiter is encountered. The provided examples pass:

    print(parser.parse('{ block-content }')); 
      // Success[1:18]: [{,  block-content , }]
    
    print(parser.parse('{## block-content ##}')); 
      // Success[1:22]: [{##,  block-content , ##}]
    
    print(parser.parse('#[[<{### block-content ###}>]]#'));
      // Success[1:32]: [#[[<{###,  block-content , ###}>]]#]
    

    The above code doesn't work if you want to nest the parser. If necessary, that problem can be avoided by remembering the previous stopDelimiter and restoring it.