You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Raku中如何从字符串动态创建Code对象实现文法动态构建

核心问题:在Raku中如何从字符串动态创建Code对象?

问题背景

在Raku v2022.06版本中,需要动态创建包含如下两条产生式的文法G:

  • S → λ(空串)
  • S → aSb

初始硬编码实现

最初的硬编码版本可以正确实现文法解析逻辑,代码如下:

sub MAIN ( )
  {
  my @variableRightHandSidesMap is Array[Pair] ;
  @variableRightHandSidesMap.push: Pair.new("S", "") ;
  @variableRightHandSidesMap.push: Pair.new("S", "'a' <S> 'b'") ;

  constant Parser := Metamodel::GrammarHOW.new_type( name => 'Parser' ) ;

  my $myTopCode = my token TOP { <S> } ;
  my $mySCode = my token S { '' | 'a' <S> 'b' } ;

  Parser.^add_method( 'TOP', $myTopCode ) ;
  Parser.^add_method( 'S', $mySCode ) ;

  Parser.^compose ;
  say Parser.HOW.^name ;
  say Parser.^methods( :local ) ;
  say ?(Parser.parse: 'aabb') ;
  say ?(Parser.parse: 'aaaaabbbbb') ;
  say ?(Parser.parse: 'aabbb') ;
  say ?(Parser.parse: 'abab') ;
  } # end sub MAIN

运行输出符合预期:

Perl6::Metamodel::GrammarHOW
(token TOP { <S> } token S { '' | 'a' <S> 'b' })
True
True
False
False

该版本中$myTopCode和$mySCode均为硬编码实现,需要解决的问题是:基于@variableRightHandSidesMap中存储的字符串键值对,以编程方式动态创建这两个Code对象。


基础实现方案

Raku中从字符串动态生成代码(包括token/rule/regex这类文法组成单元),最直接的方式是使用EVAL,需要提前声明use MONKEY-SEE-NO-EVAL;开启EVAL执行权限。
基于该思路实现的可运行草稿版本如下,运行结果和硬编码版本完全一致:

use MONKEY-SEE-NO-EVAL ;

sub MAIN ( )
  {
  my @variableRightHandSidesMap is Array[Pair] ;
  @variableRightHandSidesMap.push: Pair.new("S", "") ;
  @variableRightHandSidesMap.push: Pair.new("S", "'a' <S> 'b'") ;

  constant Parser := Metamodel::GrammarHOW.new_type( name => 'Parser' ) ;

  my $startVariable = @variableRightHandSidesMap[0].key ; # 'S'
  my $myTopCode = EVAL ( 'my token TOP { <' ~ $startVariable ~ '> }' ) ;
  Parser.^add_method( 'TOP', $myTopCode ) ;

  my Str $sCumulativeRightHandSide = '' ;
  loop ( my $i = 0 ; $i < @variableRightHandSidesMap.elems ; $i++ )
    {
    if ( $i > 0 )
      {
      $sCumulativeRightHandSide ~= ( ' | ' ) ;
      }
    if ( @variableRightHandSidesMap[$i].value.chars <= 0 )
      {
      $sCumulativeRightHandSide ~= ( '\'\'' ) ;
      }
    else
      {
      $sCumulativeRightHandSide ~= ( @variableRightHandSidesMap[$i].value ) ;
      } # end else
    } # end loop

  my $mySCode = EVAL ( 'my token ' ~ 'S' ~ ' { ' ~ $sCumulativeRightHandSide ~ ' }' ) ;
  Parser.^add_method( 'S', $mySCode ) ;

  Parser.^compose ;
  say Parser.HOW.^name ;
  say Parser.^methods( :local ) ;
  say ?(Parser.parse: 'aabb') ;
  say ?(Parser.parse: 'aaaaabbbbb') ;
  say ?(Parser.parse: 'aabbb') ;
  say ?(Parser.parse: 'abab') ;
  } # end sub MAIN

优化方案

可以利用Raku内置的列表处理函数简化冗余的手动循环逻辑,优化后代码更简洁,且天然支持多非终结符的文法扩展:

use MONKEY-SEE-NO-EVAL;

sub MAIN() {
    # 文法产生式配置,可直接扩展添加更多非终结符规则
    my @productions = 
        "S" => "",
        "S" => "'a' <S> 'b'";
    
    constant Parser := Metamodel::GrammarHOW.new_type(name => 'Parser');

    # 生成TOP入口规则,默认匹配第一个产生式的左部非终结符
    my $start-symbol = @productions[0].key;
    Parser.^add_method('TOP', EVAL "my token TOP { <$start-symbol> }");

    # 按非终结符分组,批量生成所有token规则
    for @productions.classify(*.key).kv -> $sym, @rhs {
        my $rule-body = @rhs.map({ .value || "''" }).join(' | ');
        Parser.^add_method($sym, EVAL "my token $sym \{ $rule-body \}");
    }

    Parser.^compose;

    # 测试验证
    say Parser.HOW.^name;
    say Parser.^methods(:local);
    say ?Parser.parse('aabb');        # True
    say ?Parser.parse('aaaaabbbbb'); # True
    say ?Parser.parse('aabbb');       # False
    say ?Parser.parse('abab');        # False
}

核心优化点:

  • 用classify按非终结符自动分组产生式,不需要手动维护循环索引,扩展多非终结符文法时不需要修改核心逻辑
  • 用map+join直接拼接规则体,空产生式自动替换为'',逻辑更紧凑
  • 用双引号字符串插值替代反复的~连接操作,代码可读性更高
  • EVAL生成的Code对象可以直接作为参数传入add_method,不需要额外赋值到临时变量

注意:EVAL会直接执行传入的字符串内容,如果规则字符串来自不可信的外部输入,必须提前做合法性校验,避免代码注入风险。


内容的提问来源于stack exchange,提问作者user3134725

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 04:21:42