如何克服JavaScript代码中缺少Perl的\ G?

时间:2017-08-01 13:39:14

标签: javascript regex perl parsing

在Perl中,当一个人想要对字符串进行连续解析时,可以这样做     我的$ string =" 1#&#34 ;;

while () {
    if ( $string =~ /\G\s+/gc )    {
        print "whitespace\n";
    }
    elsif ( $string =~ /\G[0-9]+/gim ) {
        print "integer\n";
    }
    elsif ( $string =~ /\G\w+/gim ) {
        print "word\n";
    }
    else {
        print "done\n";
        last;
    }
}

来源:When is \G useful application in a regex?

它产生以下输出:

whitespace
word
whitespace
integer
whitespace
done

JavaScript (以及许多其他正则表达式)中,没有\G模式,也没有任何好的替代品。

所以我提出了一个非常简单的解决方案,符合我的目的。

<!-- language: lang-js --> 
//*************************************************
// pattmatch - Makes the PAT pattern in ST from POS
// notice the "^" use to simulate "/G" directive
//*************************************************
function pattmatch(st,pat,pos)
{
var resu;
pat.lastIndex=0;
if (pos===0)  
    return  pat.exec(st);    // busca qualquer identificador  
else  {
  resu = pat.exec(st.slice(pos));    // busca qualquer identificador  
  if (resu) 
      pat.lastIndex = pat.lastIndex + pos;
  return resu;
}  // if

}

因此,上面的示例在JavaScript(node.js)中看起来像这样:

<!-- language: lang-js -->
var string = " a 1 # ";
var pos=0, ret;  
var getLexema  = new RegExp("^(\\s+)|([0-9]+)|(\\w+)","gim");  
while (pos<string.length && ( ret = pm(string,getLexema,pos)) ) {
    if (ret[1]) console.log("whitespace");
    if (ret[2]) console.log("integer");
    if (ret[3]) console.log("word");
    pos = getLexema.lastIndex;
}  // While
console.log("done");

它产生的输出与 Perl 代码片段相同:

whitespace
word
whitespace
integer
whitespace
done

注意解析器停在#个字符处。可以从pos位置继续解析另一个代码段。

JavaScript 中有更好的方法来模拟Perl的/G正则表达式吗?

发布后期

为了好奇,我决定将我的个人解决方案与@georg提案进行比较。在这里,我没有说明哪个代码最好。对我来说,这是一个品味问题。

我的系统在很大程度上取决于用户交互,会变慢吗?

@ikegami写了关于@georg解决方案:

  

...他的解决方案增加了你的输入次数的减少   文件被复制......

所以我决定在一个重复代码1000万次的循环中比较两个解决方案:

<!-- language: lang-js -->
var i;
var n1,n2;
var string,pos,m,conta,re;

// Mine code
conta=0;
n1 = Date.now();
for (i=0;i<10000000;i++) {
  string = " a 1 # ";
  pos=0, m;  
  re  = new RegExp("^(\\s+)|([0-9]+)|(\\w+)","gim");  
  while (pos<string.length && ( m = pattMatch(string,re,pos)) ) {
    if (m[1]) conta++;
    if (m[2]) conta++;
    if (m[3]) conta++;
    pos = re.lastIndex;
  }  // While
}
n2 = Date.now();
console.log('Mine: ' , ((n2-n1)/1000).toFixed(2), ' segundos' );


// Other code
conta=0;
n1 = Date.now();

for (i=0;i<10000000;i++) {
  string = " a 1 # ";
  re  = /^(?:(\s+)|([0-9]+)|(\w+))/i;
  while (m = string.match(re)) {
   if (m[1]) conta++;
   if (m[2]) conta++;
   if (m[3]) conta++;
   string = string.slice(m[0].length)
 }
 }
n2 = Date.now();
console.log('Other: ' , ((n2-n1)/1000).toFixed(2) , ' segundos');

//*************************************************
// pattmatch - Makes the PAT pattern in ST from POS
// notice the "^" use to simulate "/G" directive
//*************************************************
function pattMatch(st,pat,pos)
{
var resu;
pat.lastIndex=0;
if (pos===0)  
    return  pat.exec(st);    
else  {
  resu = pat.exec(st.slice(pos)); 
  if (resu) 
      pat.lastIndex = pat.lastIndex + pos;
  return resu;
}  
} // pattMatch

结果:

  

我的:11.90 segundos
  其他:10.77个segundos

我的代码运行时间长约10%。它每次迭代花费大约110纳秒。

老实说,根据我个人的偏好,我认为在一个用户互动量大的系统中,这种效率损失对我来说是可以接受的。

如果我的项目涉及使用多维数组或巨大的神经网络进行繁重的数学处理,我可能会重新思考。

2 个答案:

答案 0 :(得分:4)

\G的功能以/y flag的形式存在。

var regex = /^foo/y;
regex.lastIndex = 2;
regex.test('..foo');   // false - index 2 is not the beginning of the string

var regex2 = /^foo/my;
regex2.lastIndex = 2;
regex2.test('..foo');  // false - index 2 is not the beginning of the string or line
regex2.lastIndex = 2;
regex2.test('.\nfoo'); // true - index 2 is the beginning of a line

但它很新。您将无法在公共网站上使用它。检查链接文档中的浏览器兼容性图表。

答案 1 :(得分:2)

看起来你有点过于复杂了。带有exec标记的g可以提供开箱即用的锚定功能:

var 
    string = " a 1 # ",
    re  = /(\s+)|([0-9]+)|(\w+)|([\s\S])/gi,
    m;

while (m = re.exec(string)) {
    if (m[1]) console.log('space');
    if (m[2]) console.log('int');
    if (m[3]) console.log('word');
    if (m[4]) console.log('unknown');    
}

如果您的正则表达式没有覆盖,并且您希望在第一个不匹配时停止,最简单的方法是匹配^并在匹配时删除字符串:

    var 
        string = " a 1 # ",
        re  = /^(?:(\s+)|([0-9]+)|(\w+))/i,
        m;

    while (m = string.match(re)) {
        if (m[1]) console.log('space');
        if (m[2]) console.log('int');
        if (m[3]) console.log('word');
        string = string.slice(m[0].length)
    }

    console.log('done, rest=[%s]', string)

这个简单的方法并不能完全取代\G(或“匹配来自”方法),因为它会丢失匹配的左上下文。