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
相关产品推荐
相关产品推荐

