How to match the start and end of a block

2019-08-06 13:45发布

问题:

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?

回答1:

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.