替代版本的语法无法正常工作

Alternate version of grammar not working as I'd prefer

此代码按我的意愿解析 $string

#! /usr/bin/env raku

my $string = q:to/END/;
aaa bbb   # this has trailing spaces which I want to keep

       kjkjsdf
kjkdsf
END

grammar Markdown {
    token TOP {  ^ ([ <blank> | <text> ])+ $ }
    token blank { [ \h* <.newline> ]  }
    token text { <indent> <content> }
    token indent { \h* }
    token newline { \n }
    token content { \N*? <trailing>* <.newline> } 
    token trailing { \h+ }
}

my $match = Markdown.parse($string);
$match.say;

输出

「aaa bbb

       kjkjsdf
kjkdsf
」
 0 => 「aaa bbb
」
  text => 「aaa bbb
」
   indent => 「」
   content => 「aaa bbb
」
    trailing => 「   」
 0 => 「
」
  blank => 「
」
 0 => 「       kjkjsdf
」
  text => 「       kjkjsdf
」
   indent => 「       」
   content => 「kjkjsdf
」
 0 => 「kjkdsf
」
  text => 「kjkdsf
」
   indent => 「」
   content => 「kjkdsf
」

现在,我遇到的唯一问题是我希望 <trailing> 级别与 <indent><content> 捕获处于同一层次结构级别.

所以我尝试了这个语法:

grammar Markdown {
    token TOP {  ^ ([ <blank> | <text> ])+ $ }
    token blank { [ \h* <.newline> ]  }
    token text { <indent> <content> <trailing>* <.newline> }
    token indent { \h* }
    token newline { \n }
    token content { \N*?  } 
    token trailing { \h+ }
}

但是,它破坏了解析。所以我尝试了这个:

    token TOP {  ^ ([ <blank> | <text> ])+ $ }
    token blank { [ \h* <.newline> ]  }
    token text { <indent> <content>*? <trailing>* <.newline> }
    token indent { \h* }
    token newline { \n }
    token content { \N  } 
    token trailing { \h+ }

并得到:

 0 => 「aaa bbb
」
  text => 「aaa bbb
」
   indent => 「」
   content => 「a」
   content => 「a」
   content => 「a」
   content => 「 」
   content => 「b」
   content => 「b」
   content => 「b」
   trailing => 「   」
 0 => 「
」
  blank => 「
」
 0 => 「       kjkjsdf
」
  text => 「       kjkjsdf
」
   indent => 「       」
   content => 「k」
   content => 「j」
   content => 「k」
   content => 「j」
   content => 「s」
   content => 「d」
   content => 「f」
 0 => 「kjkdsf
」
  text => 「kjkdsf
」
   indent => 「」
   content => 「k」
   content => 「j」
   content => 「k」
   content => 「d」
   content => 「s」
   content => 「f」

这与我想要的非常接近,但它具有将 <content> 分解成单个字母的不良效果,这并不理想。我可以在事后通过按摩 $match 对象很容易地解决这个问题,但我想尝试提高我的语法技能。

我能够通过否定的先行断言实现我想要的:

    token TOP {  ^ ([ <blank> | <text> ])+ $ }
    token blank { [ \h* <.newline> ]  }
    token text { <indent>? <content> <trailing>? <.newline> }
    token indent { \h+ }
    token newline { \n }
    token content {  <.non_trailing>  } 
    token non_trailing { ( . <!before \w \h* \n>)+ \S* }

    token trailing { \h+ }

<.non_trailing> 禁止单个字符出现在匹配对象中,. <!before \w \h* \n>)+ \S* 位将匹配任何不后跟白色 space 和新行的字符,\S* 位获取负前瞻留下的字符。

输出

「aaa bbb

       kjkjsdf
kjkdsf
」
 0 => 「aaa bbb
」
  text => 「aaa bbb
」
   content => 「aaa bbb」
   trailing => 「   」
 0 => 「
」
  blank => 「
」
 0 => 「       kjkjsdf
」
  text => 「       kjkjsdf
」
   indent => 「       」
   content => 「kjkjsdf」
 0 => 「kjkdsf
」
  text => 「kjkdsf
」
   content => 「kjkdsf」

快速而肮脏

my $string = q:to/END/;
aaa bbb  

       kjkjsdf
kjkdsf
END

grammar Markdown {
    token TOP {  ^ ([ <blank> | <text> ])+ $ }
    token blank { [ \h* <.newline> ]  }
    token text { <indent>? $<content>=\N*? <trailing>? <.newline> }
    token indent { \h+ }
    token newline { \n }
    token trailing { \h+ }
}

my $match = Markdown.parse($string);
$match.say;

前瞻断言

my $string = q:to/END/;
aaa bbb  

       kjkjsdf
kjkdsf
END

grammar Markdown {
    token TOP {  ^ ([ <blank> | <text> ])+ $ }
    token blank { [ \h* <.newline> ]  }
    token text { <indent>? <content> <trailing>? <.newline> }
    token indent { \h+ }
    token newline { \n }
    token content { [<!before <trailing>> \N]+  }
    token trailing { \h+ $$ }
}

my $match = Markdown.parse($string);
$match.say;

一点重构

my $string = q:to/END/;
aaa bbb  

       kjkjsdf
kjkdsf
END

grammar Markdown {
    token TOP { ( <blank> | <text> )+ %% \n }
    token blank { ^^ \h* $$  }
    token text { <indent>? <content> <trailing>? }
    token indent { ^^ \h+ }
    token content { [<!before <trailing>> \N]+  }
    token trailing { \h+ $$ }
}

my $match = Markdown.parse($string);
$match.say;