【问题标题】:How to remove trailing comments via regexp?如何通过正则表达式删除尾随注释?
【发布时间】:2013-06-25 21:57:55
【问题描述】:

对于不熟悉 MATLAB 的读者:不确定他们属于哪个家族,但 MATLAB 正则表达式已在here 进行了详细描述。 MATLAB 的注释字符是 %(百分比),它的字符串分隔符是 '(撇号)。字符串中的字符串定界符写为双撇号 ('this is how you write "it''s" in a string.')。更复杂的是,矩阵转置运算符也是撇号(A'(Hermitian)或A.'(常规))。

现在,出于黑暗的原因(我将详细说明:),我正在尝试用 MATLAB 自己的语言解释 MATLAB 代码。

目前我正在尝试删除字符串单元格数组中的所有尾随 cmets,每个字符串都包含一行 MATLAB 代码。乍一看,这似乎很简单:

>> str = 'simpleCommand(); % simple trailing comment';
>> regexprep(str, '%.*$', '')
ans =
    simpleCommand(); 

当然,可能会出现这样的情况:

>> str = ' fprintf(''%d%*c%3.0f\n'', value, args{:}); % Let''s do this! ';
>> regexprep(str, '%.*$', '') 
ans = 
    fprintf('        %//   <-- WRONG!

显然,我们需要从匹配中排除所有位于字符串中的注释字符,同时还要考虑到语句后面的单个撇号(或点撇号)是一个运算符 ,而不是字符串分隔符。

基于字符串打开/关闭字符的数量 before 注释字符必须是 even 的假设(我知道这是不完整的,因为矩阵转置运算符),我想出了以下动态正则表达式来处理这种情况:

>> str = {
       'myFun( {''test'' ''%''}); % let''s '                 
       'sprintf(str, ''%*8.0f%*s%c%3d\n''); % it''s '        
       'sprintf(str, ''%*8.0f%*s%c%3d\n''); % let''s '       
       'sprintf(str, ''%*8.0f%*s%c%3d\n'');  '
       'A = A.'';%tight trailing comment'
   };
>> 
>> C = regexprep(str, '(^.*)(?@mod(sum(\1==''''''''),2)==0;)(%.*$)', '$1')

然而,

C = 
    'myFun( {'test' '%'}); '              %// sucess
    'sprintf(str, '%*8.0f%*s%c%3d\n'); '  %// sucess
    'sprintf(str, '%*8.0f%*s%c%3d\n'); '  %// sucess
    'sprintf(str, '%*8.0f%*s%c'           %// FAIL
    'A = A.';'                            %// success (although I'm not sure why)

所以我几乎在那里,但还没有:)

不幸的是,我已经用尽了我可以花在思考这个问题上的时间,需要继续做其他事情,所以也许其他有更多时间的人足够友好地思考这些问题:

  1. 字符串中的注释字符是我需要注意的唯一异常吗?
  2. 正确和/或更有效的方法是什么?

【问题讨论】:

  • 我不知道 matlab 但如何使用非贪婪量词:%.*?$
  • @M42:嗯,这似乎解决了这个问题,至少对于我的一小部分测试......你可以发布作为答案吗?
  • @M42: 等一下,我看的不够仔细——它没有解决问题……事实上,它不再做任何事情了:)
  • 好吧,如果指令以分号结尾,可能会这样:用;替换;\s*%.*?$
  • @M42:不幸的是,并非所有行都以分号结尾(例如,if (condition) % trailing comment 是一种常见的模式)

标签: regex string matlab parsing


【解决方案1】:

如何确保评论前的所有撇号成对出现,如下所示:

>> str = {
       'myFun( {''test'' ''%''}); % let''s '                 
       'sprintf(str, ''%*8.0f%*s%c%3d\n''); % it''s '        
       'sprintf(str, ''%*8.0f%*s%c%3d\n''); % let''s '       
       'sprintf(str, ''%*8.0f%*s%c%3d\n'');  '
   };

>> C = regexprep(str, '^(([^'']*''[^'']*'')*[^'']*)%.*$', '$1')

C = 
    myFun( {'test' '%'}); 
    sprintf(str, '%*8.0f%*s%c%3d\n'); 
    sprintf(str, '%*8.0f%*s%c%3d\n'); 
    sprintf(str, '%*8.0f%*s%c%3d\n'); 

【讨论】:

  • 看起来更好......但不幸的是在str = 'A = A.'';%trailing comment'之类的东西上失败了
【解决方案2】:

我更喜欢滥用checkcode(旧mlint的替代品)来进行解析。这是一个建议

function strNC = removeComments(str)
if iscell(str)
    strNC = cellfun(@removeComments, str, 'UniformOutput', false);
elseif regexp(str, '%', 'once')
    err = getCheckCodeId(str);
    strNC = regexprep(str, '%[^%]*$', '');
    errNC = getCheckCodeId(strNC);
    if strcmp(err, errNC),
        strNC = removeComments(strNC);
    else
        strNC = str;
    end
else
    strNC = str;
end
end

function errid = getCheckCodeId(line)
fName = 'someTempFileName.m';
fh = fopen(fName, 'w');
fprintf(fh, '%s\n', line);
fclose(fh);
if exist('checkcode')
    structRep = checkcode(fName, '-id');
else
    structRep = mlint(fName, '-id');
end
delete(fName);
if isempty(structRep)
    errid = '';
else
    errid = structRep.id;
end
end

对于每一行,它会通过将最后一行% 修剪到行尾来检查我们是否引入了错误。

对于您的示例,它返回:

>> removeComments(str)

ans = 

    'myFun( {'test' '%'}); '
    'sprintf(str, '%*8.0f%*s%c%3d\n'); '
    'sprintf(str, '%*8.0f%*s%c%3d\n'); '
    'sprintf(str, '%*8.0f%*s%c%3d\n');  '
    'A = A.';'

它不会删除抑制指令%#ok,所以你得到:

>> removeComments('a=1; %#ok')

ans =

a=1; %#ok

这可能是件好事。

【讨论】:

  • +1:你,我的好先生,很快就会成为我的新英雄!你充满了肮脏的技巧:p无论如何,虽然我同意这可能是更强大和“更好”的方式,但感觉非常冗长......另外,它会产生版本依赖性:我在 R2010a ,所以我将不得不使用mlint 而不是codecheck(理想情况下,我必须检查)。所以我会把这个问题留一段时间,看看一个正则表达式单行是否真的不可能。
  • 谢谢你的好话!确实不如单行词regexprep 简洁。我也对正则表达式的解决方案感到好奇,但我看到许多特殊情况需要处理,主要是因为´ 可用于字符串和共轭转置。
  • 是的,撇号很讨厌,不是吗?
【解决方案3】:

这通过检查一个字符之前允许的字符来匹配共轭转置大小写

  1. 号码2'
  2. 信件A'
  3. A.'
  4. 左括号、大括号和方括号A(1)'A{1}'[1 2 3]'

目前我能想到的只有这些。

C = regexprep(str, '^(([^'']*''[^'']*''|[^'']*[\.a-zA-Z0-9\)\}\]]''[^'']*)*[^'']*)%.*$', '$1')

在你的例子中我们返回

>> C = regexprep(str, '^(([^'']*''[^'']*''|[^'']*[\.a-zA-Z0-9\)\}\]]''[^'']*)*[^'']*)%.*$', '$1')

C = 

    'myFun( {'test' '%'}); '
    'sprintf(str, '%*8.0f%*s%c%3d\n'); '
    'sprintf(str, '%*8.0f%*s%c%3d\n'); '
    'sprintf(str, '%*8.0f%*s%c%3d\n');  '
    'A = A.';'

【讨论】:

  • +1(再次):开始看起来不错...不过需要进一步测试。
  • @M42 你的评论是多余的,因为他使用的是[^'']*
  • @M42 并非如此。您会看到[^''],但它不是实际的字符串。 Matlab 会将其转换为[^']。如果你想在 MATLAB 字符串中有一个撇号,你必须重复它。如果你运行disp('It''s a string'),你会得到It's a string
  • @MohsenNosratinia:好的,对不起,我的错。我不懂 MATLAB 语法。
  • 看看我的回答 :) 你能找到一个失败的构造吗?
【解决方案4】:

看看我发现了什么! :)

The comment stripping toolbox,作者 Peter J. Acklam。

对于 m 代码,它包含以下正则表达式:

mainregex = [ ...
     ' (                   ' ... % Grouping parenthesis (content goes to $1).
     '   ( ^ | \n )        ' ... % Beginning of string or beginning of line.
     '   (                 ' ... % Non-capturing grouping parenthesis.
     '                     ' ...
     '' ... % Match anything that is neither a comment nor a string...
     '       (             ' ... % Non-capturing grouping parenthesis.
     '           [\]\)}\w.]' ... % Either a character followed by
     '           ''+       ' ... %    one or more transpose operators
     '         |           ' ... % or else
     '           [^''%]    ' ... %   any character except single quote (which
     '                     ' ... %   starts a string) or a percent sign (which
     '                     ' ... %   starts a comment).
     '       )+            ' ... % Match one or more times.
     '                     ' ...
     '' ...  % ...or...
     '     |               ' ...
     '                     ' ...
     '' ...  % ...match a string.
     '       ''            ' ... % Opening single quote that starts the string.
     '         [^''\n]*    ' ... % Zero or more chars that are neither single
     '                     ' ... %   quotes (special) nor newlines (illegal).
     '         (           ' ... % Non-capturing grouping parenthesis.
     '           ''''      ' ... % An embedded (literal) single quote character.
     '           [^''\n]*  ' ... % Again, zero or more chars that are neither
     '                     ' ... %   single quotes nor newlines.
     '         )*          ' ... % Match zero or more times.
     '       ''            ' ... % Closing single quote that ends the string.
     '                     ' ...
     '   )*                ' ... % Match zero or more times.
     ' )                   ' ...
     ' [^\n]*              ' ... % What remains must be a comment.
              ];

  % Remove all the blanks from the regex.
  mainregex = mainregex(~isspace(mainregex));

变成了

mainregex  = '((^|\n)(([\]\)}\w.]''+|[^''%])+|''[^''\n]*(''''[^''\n]*)*'')*)[^\n]*'

并且应该被用作

C = regexprep(str, mainregex, '$1')

到目前为止,它经受住了我所有的测试,所以我认为这应该很好地解决我的问题:)

【讨论】:

    【解决方案5】:

    您对使用未记录的功能有何感想?如果您不反对,您可以使用mtree 函数来解析代码并剥离cmets。不涉及正则表达式,we all know 我们不应该尝试使用正则表达式解析上下文无关语法。

    此函数是用纯 M 代码编写的 MATLAB 代码的完整解析器。据我所知,这是一个实验性的实现,但它已经被 Mathworks 在一些地方使用(这与 MATLAB CodyContests 用于测量代码长度的函数相同),并且可以用于 @ 987654324@有用的东西。

    如果输入是一个字符串元胞数组,我们这样做:

    >> str = {..};
    >> C = deblank(cellfun(@(s) tree2str(mtree(s)), str, 'UniformOutput',false))
    C = 
        'myFun( { 'test', '%' } );'
        'sprintf( str, '%*8.0f%*s%c%3d\n' );'
        'sprintf( str, '%*8.0f%*s%c%3d\n' );'
        'sprintf( str, '%*8.0f%*s%c%3d\n' );'
        'A = A.';'
    

    如果你已经有一个 M 文件存储在磁盘上,你可以简单地剥离 cmets:

    s = tree2str(mtree('myfile.m', '-file'))
    

    如果您想看到 cmets 回来,请添加:mtree(.., '-comments')

    【讨论】:

    • 确切地说mtree调用了一个内置函数mtreemex,所以它不是一个纯M代码函数
    猜你喜欢
    • 2011-05-11
    • 1970-01-01
    • 2012-03-20
    • 2011-01-28
    • 1970-01-01
    • 2020-07-20
    • 1970-01-01
    • 2012-04-08
    相关资源
    最近更新 更多