将invRegex.py移植到Javascript(Node.js)

9

我一直在尝试将 invRegex.py 移植到 node.js 实现中,但我仍然遇到了困难。感谢 ret.js 分词器,我已经拥有了正则表达式解析树,并且它的工作效果很好,但是实际生成并以内存高效方式连接所有不同元素的过程对我来说仍然非常具有挑战性。为了保持简单,让我们假设我有以下正则表达式:

[01]{1,2}@[a-f]

将此输入传递给invRegex.py会产生以下输出(转换为制表符以节省空间):

 0@a     0@b     0@c     0@d     0@e     0@f
00@a    00@b    00@c    00@d    00@e    00@f
01@a    01@b    01@c    01@d    01@e    01@f
 1@a     1@b     1@c     1@d     1@e     1@f
10@a    10@b    10@c    10@d    10@e    10@f
11@a    11@b    11@c    11@d    11@e    11@f

考虑到我能够获取每个单独的令牌并生成所有有效单独输出的数组:
[01]{1,2} = function () {
    return ['0', '00', '01', '1', '10', '11'];
};

@ = function () {
    return ['@'];
};

[a-f] = function () {
    return ['a', 'b', 'c', 'd', 'e', 'f'];
};

我可以计算所有数组的笛卡尔积,并获得相同的预期输出:

var _ = require('underscore');

function cartesianProductOf() {
    return _.reduce(arguments, function(a, b) {
        return _.flatten(_.map(a, function(x) {
            return _.map(b, function(y) {
                return x.concat([y]);
            });
        }), true);
    }, [ [] ]);
};

var tokens = [
    ['0', '00', '01', '1', '10', '11'],
    ['@'],
    ['a', 'b', 'c', 'd', 'e', 'f'],
];

var result = cartesianProductOf(tokens[0], tokens[1], tokens[2]);

_.each(result, function (value, key) {
    console.log(value.join(''));
});

这样做的问题在于它会将所有36个值保存在内存中。如果我使用稍微复杂一些的正则表达式,例如[a-z]{0,10},它将在内存中保留146813779479511个值,这是完全不可行的。我想以异步方式处理此巨大列表,将每个生成的组合传递给回调函数,并允许我在任何适当的点中断该过程,就像invRegex.py或这个Haskell包一样-不幸的是,我不懂Haskell,也不知道如何在Python和Javascript之间模仿生成器行为。
我在node 0.11.9(使用--harmony)中尝试了几个简单的生成器实验,例如以下代码:
function* alpha() {
    yield 'a'; yield 'b'; yield 'c';
}

function* numeric() {
    yield '0'; yield '1';
}

function* alphanumeric() {
    yield* alpha() + numeric(); // what's the diff between yield and yield*?
}

for (var i of alphanumeric()) {
    console.log(i);
}

毋庸置疑,以上方法不起作用。=/
我一直在苦苦挣扎,所以非常感谢任何对解决这个问题有帮助的帮助。
更新: 这是b[a-z]{3}的一个样本ret.js解析树:
{
    "type": ret.types.ROOT,
    "stack": [
            {
                "type": ret.types.CHAR,
                "value": 98 // b
            },
            {
                "type": ret.types.REPETITION,
                "max": 3,
                "min": 3,
                "value": {
                    "type": ret.types.SET,
                    "not": false,
                    "set": [
                        {
                            "type": ret.types.RANGE,
                            "from": 97, // a
                            "to": 122   // z
                        }
                    ]
                }
            }
        ]
    ]
}
SET/RANGE类型应该产生26个不同的值,而父级REPETITION类型应该将之前的值乘以3次方,产生17576种不同的组合。如果我想要像为cartesianProductOf生成扁平化的tokens数组一样生成一个扁平化的数组,那么中间的扁平化值将占用与实际笛卡尔积本身一样多的空间。
我希望这个例子能更好地解释我面临的问题。

如果需要理解ret.js的解析树结构,我编写了一个递归函数(https://gist.github.com/alixaxel/8745c98740837def2285),可以计算有效返回值的数量。 - Alix Axel
yield* 就像 Python 中的 yield from。另外,“我也不知道如何在 JavaScript 中模仿生成器行为。”具体是指哪种行为? - Benjamin Gruenbaum
Alix,你是否尝试使用像Q这样的更高级别的Promise库?请查看https://github.com/kriskowal/q - nico
也许算法定义的 yield *能帮助澄清它是如何工作的?要使您的字母数字组合正常工作,您需要分配 yield 的结果,然后进行连接。例如:var x,y; x = yield* alpha(); y = yield* numeric(); return x + y; - bishop
1
或者,您可以使用括号来澄清 yield* 绑定:yield (yield* alpha()) + (yield* numeric()); - bishop
显示剩余6条评论
6个回答

6
我建议你编写迭代器类。它们易于实现(基本上它们是状态机),具有较低的内存占用,可以组合使用以构建越来越复杂的表达式(请向下滚动以查看最终结果),并且生成的迭代器可以包装在枚举器中。
每个迭代器类都有以下方法:
- first:初始化状态机(第一个匹配项) - next:继续到下一个状态(下一个匹配项) - ok:最初为真,但一旦“next”超过最后一个匹配项,就变为假 - get:返回当前匹配项(作为字符串) - clone:克隆对象;对于重复很重要,因为每个实例都需要自己的状态
从最简单的情况开始:应该按字面意义匹配的一个或多个字符序列(例如/foo/)。不用说,这只有一个匹配项,因此在第一次调用“next”时,“ok”将变为假。
function Literal(literal) { this.literal = literal; }

Literal.prototype.first = function() { this.i = 0; };
Literal.prototype.next = function() { this.i++; };
Literal.prototype.ok = function() { return this.i == 0; };
Literal.prototype.get = function() { return this.literal; };
Literal.prototype.clone = function() { return new Literal(this.literal); };

字符类 ([abc]) 也很简单。构造函数接受一个字符字符串;如果您更喜欢数组,那也容易解决。

function CharacterClass(chars) { this.chars = chars; }

CharacterClass.prototype.first = function() { this.i = 0; };
CharacterClass.prototype.next = function() { this.i++; };
CharacterClass.prototype.ok = function() { return this.i < this.chars.length; };
CharacterClass.prototype.get = function() { return this.chars.charAt(this.i); };
CharacterClass.prototype.clone = function() { return new CharacterClass(this.chars); };

现在我们需要迭代器来组合其他迭代器以形成更复杂的正则表达式。一个序列只是两个或多个连续的模式(例如foo[abc])。
function Sequence(iterators) {
   if (arguments.length > 0) {
      this.iterators = iterators.length ? iterators : [new Literal('')];
   }
}
Sequence.prototype.first = function() {
   for (var i in this.iterators) this.iterators[i].first();
};
Sequence.prototype.next = function() {
   if (this.ok()) {
      var i = this.iterators.length;
      while (this.iterators[--i].next(), i > 0 && !this.iterators[i].ok()) {
         this.iterators[i].first();
      }
   }
};
Sequence.prototype.ok = function() {
   return this.iterators[0].ok();
};
Sequence.prototype.get = function() {
   var retval = '';
   for (var i in this.iterators) {
      retval += this.iterators[i].get();
   }
   return retval;
};
Sequence.prototype.clone = function() {
   return new Sequence(this.iterators.map(function(it) { return it.clone(); }));
};

另一种组合迭代器的方式是选择(也称为替代),例如foo|bar

function Choice(iterators) { this.iterators = iterators; }

Choice.prototype.first = function() {
   this.count = 0;
   for (var i in this.iterators) this.iterators[i].first();
};
Choice.prototype.next = function() {
   if (this.ok()) {
      this.iterators[this.count].next();
      while (this.ok() && !this.iterators[this.count].ok()) this.count++;
   }
};
Choice.prototype.ok = function() {
   return this.count < this.iterators.length;
};
Choice.prototype.get = function() {
   return this.iterators[this.count].get();
};
Choice.prototype.clone = function() {
   return new Choice(this.iterators.map(function(it) { return it.clone(); }));
};

通过组合现有的类可以实现其他正则表达式功能。类继承是一种很好的方法。例如,可选模式 (x?) 只是在空字符串和 x 之间进行选择。

function Optional(iterator) {
   if (arguments.length > 0) {
      Choice.call(this, [new Literal(''), iterator]);
   }
}
Optional.prototype = new Choice();

重复(x{n,m})是序列和可选项的组合。由于我必须继承其中之一,因此我的实现由两个相互依赖的类组成。

function RepeatFromZero(maxTimes, iterator) {
   if (arguments.length > 0) {
      Optional.call(this, new Repeat(1, maxTimes, iterator));
   }
}
RepeatFromZero.prototype = new Optional();

function Repeat(minTimes, maxTimes, iterator) {
   if (arguments.length > 0) {
      var sequence = [];
      for (var i = 0; i < minTimes; i++) {
         sequence.push(iterator.clone());   // need to clone the iterator
      }
      if (minTimes < maxTimes) {
         sequence.push(new RepeatFromZero(maxTimes - minTimes, iterator));
      }
      Sequence.call(this, sequence);
   }
}
Repeat.prototype = new Sequence();

正如我之前所说,迭代器可以被包装成枚举器。这只是一个循环,你可以在任何时候中断。

function Enumerator(iterator) {
   this.iterator = iterator;

   this.each = function(callback) {
      for (this.iterator.first(); this.iterator.ok(); this.iterator.next()) {
         callback(this.iterator.get());
      }
   };
}

现在是把所有东西放在一起的时候了。让我们来看一些愚蠢的正则表达式:

([ab]{2}){1,2}|[cd](f|ef{0,2}e)

组成迭代器对象非常简单:

function GetIterationsAsHtml() {

   var iterator = new Choice([
      new Repeat(1, 2,
         new Repeat(2, 2, new CharacterClass('ab'))),
      new Sequence([
         new CharacterClass('cd'),
         new Choice([
            new Literal('f'),
            new Sequence([
               new Literal('e'),
               new RepeatFromZero(2, new Literal('f')),
               new Literal('e')
            ])
         ])
      ])
   ]);

   var iterations = '<ol>\n';
   var enumerator = new Enumerator(iterator);
   enumerator.each(function(iteration) { iterations += '<li>' + iteration + '</li>\n'; });
   return iterations + '</ol>';
}

这将产生28个匹配项,但我会节省输出。
如果我的代码不符合软件模式、不兼容浏览器(在Chrome和Firefox上工作正常)、或者遭受了OOP差的问题,那么请原谅我。我只希望它能让概念更加清晰。
编辑: 为了完整起见,并且遵循OP的倡议,我实现了另外一个迭代器类:引用。
引用(\1 \2等)选取早先捕获组的当前匹配项(即括号中的任何内容)。它的实现与文字类型非常相似,因为它只有一个匹配项。
function Reference(iterator) { this.iterator = iterator; }

Reference.prototype.first = function() { this.i = 0; };
Reference.prototype.next  = function() { this.i++; };
Reference.prototype.ok    = function() { return this.i == 0; };
Reference.prototype.get   = function() { return this.iterator.get(); };
Reference.prototype.clone = function() { return new Reference(this.iterator); };

构造函数接收一个表示引用子模式的迭代器。以(foo|bar)([xy])\2\1为例(产生fooxxfoo, fooyyfoo, barxxbar, baryybar):
var groups = new Array();

var iterator = new Sequence([
   groups[1] = new Choice([new Literal('foo'), new Literal('bar')]),
   groups[2] = new CharacterClass('xy'),
   new Reference(groups[2]),
   new Reference(groups[1])
]);

在构建迭代器类的树时,可以指定捕获组。我现在仍然以手动方式执行此操作,但最终您希望将其自动化。这只是将解析树映射到类似的迭代器类树的问题。

编辑2: 这里有一个相对简单的递归函数,它将由ret.js生成的解析树转换为迭代器。

function ParseTreeMapper() {
    this.capturingGroups = [];
}
ParseTreeMapper.prototype.mapToIterator = function(parseTree) {
    switch (parseTree.type) {
        case ret.types.ROOT:
        case ret.types.GROUP:
            var me = this;
            var mapToSequence = function(parseTrees) {
                return new Sequence(parseTrees.map(function(t) {
                    return me.mapToIterator(t);
                }));
            };
            var group = parseTree.options ?
                new Choice(parseTree.options.map(mapToSequence)) : 
                mapToSequence(parseTree.stack);
            if (parseTree.remember) {
                this.capturingGroups.push(group);
            }
            return group;
        case ret.types.SET:
            return new CharacterClass(this.mapToCharacterClass(parseTree.set));
        case ret.types.REPETITION:
            return new Repeat(parseInt(parseTree.min), parseInt(parseTree.max), this.mapToIterator(parseTree.value));
        case ret.types.REFERENCE:
            var ref = parseInt(parseTree.value) - 1;
            return ref in this.capturingGroups ?
                new Reference(this.capturingGroups[ref]) :
                new Literal('<ReferenceOutOfRange>');
        case ret.types.CHAR:
            return new Literal(String.fromCharCode(parseTree.value));
        default:
            return new Literal('<UnsupportedType>');
    }
};
ParseTreeMapper.prototype.mapToCharacterClass = function(parseTrees) {
    var chars = '';
    for (var i in parseTrees) {
        var tree = parseTrees[i];
        switch (tree.type) {
            case ret.types.CHAR:
                chars += String.fromCharCode(tree.value);
                break;
            case ret.types.RANGE:
                for (var code = tree.from; code <= tree.to; code++) {
                    chars += String.fromCharCode(code);
                }
                break;
        }
    }
    return chars;
};

使用方法:

var regex = 'b[a-n]{3}';
var parseTree = ret(regex);    // requires ret.js
var iterator = new ParseTreeMapper().mapToIterator(parseTree);

我在这个演示中将所有组件放在一起:http://jsfiddle.net/Pmnwk/3/ 注意:许多正则表达式语法结构不受支持(锚点、前瞻、后顾、递归),但我想它已经与invRegex.py相当了。

1
@AlixAxel:像任何状态机一样,内存使用是恒定的(与匹配数量无关),因为迭代器类不存储匹配;每个实例只是持有一个简单的计数器来跟踪其进度。请注意,我的测试函数(GetIterationsAsHtml)并非经过内存优化(它将所有匹配累积到一个大字符串“iterations”中)。至于使用“yield”的方法,我预计在内存使用方面同样经济。Yield的好处是使您的迭代器类内部的代码更易读,但浏览器支持可能会成为问题。 - Ruud Helderman
1
一些编程语言有预定义的“迭代器”接口(http://en.wikipedia.org/wiki/Iterator_pattern),但由于其动态语义,JavaScript不需要正式的接口定义。可能会有针对典型实现的库,例如将迭代器作为数组/字典的包装器,但到目前为止我还没有找到。我的特定情况非常具体,所以我从头开始编写了所有内容,这并不难;我的答案包含完整的实现。要了解更多信息,请参阅GoF(http://en.wikipedia.org/wiki/Design_Patterns_(book)),其中详细介绍了迭代器模式。 - Ruud Helderman
@AlixAxel:请查看我在上面答案中的编辑2。我还重构了Sequence类,使其能够应对零长度序列的情况(最初,这些会产生无限重复)。 - Ruud Helderman
1
我最终找到了两个JavaScript库:wu.jsgoog.iter - Ruud Helderman
1
啊是的,在Go语言中,逗号操作符和内联减少被放弃了。无论如何,我的while循环等效于i--; this.iterators[i].next(); while (i > 0 && !this.iterators[i].ok()) { this.iterators[i].first(); i--; this.iterators[i].next(); }顺便说一句,如果您有进一步的Go问题,请开一个新的问题,保持当前线程专注于JavaScript。 - Ruud Helderman
显示剩余9条评论

2
这里有一个版本,它为输入的每个部分创建一个函数,并将它们组合起来以生成一个函数,该函数将生成每个正则表达式结果并将其馈入该参数:
//Takes in a list of things, returns a function that takes a function and applies it to
// each Cartesian product. then composes all of the functions to create an
// inverse regex generator.

function CartesianProductOf() {
    var args = arguments;
    return function(callback) {
        Array.prototype.map.call(args, function(vals) {
            return function(c, val) {
                vals.forEach(function(v) {
                    c(val + v);
                });
            };
        }).reduce(function(prev, cur) {
            return function(c, val) {
                prev(function(v){cur(c, v)}, val);
            };
        })(callback, "");
    };
}      

修改为基于解析树工作(从这里复制了一些代码):

//Takes in a list of things, returns a function that takes a function and applies it to
// each Cartesian product.

function CartesianProductOf(tree) {
    var args = (tree.type == ret.types.ROOT)? tree.stack :
                ((tree.type == ret.types.SET)? tree.set : []);

    return function(callback) {
        var funs = args.map(function(vals) {
            switch(vals.type) {
                case ret.types.CHAR:
                    return function(c, val) {
                        c(val + vals.value);
                    };
                case ret.types.RANGE:
                    return function(c, val) {
                        for(var i=vals.from; i<=vals.to; i++) {
                            c(val+String.fromCharCode(i));
                        }
                    };
                case ret.types.SET:
                     return function(c, val) {
                         CartesianProductOf(vals)(function(i) {c(val+i)});
                     };
/*                   return function(c, val) {
                        vals.set.forEach(function(v) {
                            c(val + v);
                        });
                    };        */
                case ret.types.REPETITION:
                    var tmp = CartesianProductOf(vals.value);

                    if(vals.max == vals.min) {
                        return fillArray(function(c, val) {
                            tmp(function(i){c(val+i);}); //Probably works?
                        }, vals.max);
                    } else {
                        return fillArray(function(c, val) {
                            tmp(function(i){c(val+i);});
                        }, vals.min).concat(fillArray(function(c, val) {
                            c(val);
                            tmp(function(i){c(val+i);});
                        }, vals.max-vals.min));
                    }
                default:
                    return function(c, val) {
                        c(val);
                    };
            }
        }).reduce(function(prev, cur) { //Flatten array.
            return prev.concat(cur);
        }, []);

        if(tree.type == rets.type.ROOT) //If it's a full tree combine all the functions.
            funs.reduce(function(prev, cur) { //Compose!
                return function(c, val) {
                    prev(function(v){cur(c, v)}, val);
                };
            })(callback, "");
        else                          //If it's a set call each function.
            funs.forEach(function(f) {f(callback, "")}); 
    };
}

function fillArray(value, len) {
    var arr = [];
    for (var i = 0; i < len; i++) {
        arr.push(value);
    }
    return arr;
}

如果您可以接受功能较少、更像C语言的解决方案:
function helper(callme, cur, stuff, pos) {
    if(pos == stuff.length) {
        callme(cur);
    } else 
        for(var i=0; i<stuff[pos].length; i++) {
            helper(callme, cur+stuff[pos][i], stuff, pos+1);
        }
}

function CartesianProductOf(callback) {
    helper(callback, "", Array.prototype.slice.call(arguments, 1), 0);
}

第一段代码片段可行,但其他两段不行。将第一个“helper”函数沿着降序遍历标记,与展开方式相反,有多容易呢?我将检查我的代码并尝试更改它以使其按这种方式工作,无论如何-我很快会发布其他反馈。谢谢顺便说一句。 - Alix Axel
是否可以将嵌套的数组展开成单个元素,而不是像 "[[[[["a", "b"], "c", "d"], ["e", 'f"]]]" 这样被压平,导致我们最终得到 "ace"、"bce"、"ade"、"dbe"、"acf"、"bcf" 等等? - cactus1
类似于[['0' -> ['', '0', '1'], '1' -> ['', '0', '1']], '@', ['a', 'b', 'c', 'd', 'e', 'f']](其中->表示父/子嵌套级别),更具代表性。解析树来自ret.js,您可以在此处查看简单输出:https://github.com/fent/ret.js/issues/1。 - Alix Axel
第二个示例看起来怎么样?您可以在switch语句中添加更多的情况,以扩展其与其他类型的配合。 - cactus1
第二个例子看起来很有前途,我要扩展它以处理其他令牌类型,并查看其性能如何。我想知道每个令牌类型的值是否需要计算n次,其中n是总父令牌组合数? - Alix Axel
显示剩余12条评论

1
这个怎么样:
var tokens = [
    ['0', '00', '01', '1', '10', '11'],
    ['@'],
    ['a', 'b', 'c', 'd', 'e', 'f'],
];

function cartesianProductOf(arr, callback) {
  var cur = [];
  var f = function(i) {
    if (i >= arr.length) {
      callback(cur.join(''));
      return
    }
    for (var j=0; j<arr[i].length; j++) {
      cur[i] = arr[i][j];
      f(i+1);
    }
  };
  f(0);
}

cartesianProductOf(tokens, function(str) { console.log(str); });

非常好和干净,只有一个问题:这个能不能与n维数组一起使用如此处所见(也像invPython那样)?我将在我的代码上进行一些修改,然后再回来找你,但如果我不必事先展平令牌数组,那就太棒了。 - Alix Axel

1
看起来你在询问惰性笛卡尔积:你确实想要笛卡尔积,但是你不想预先计算它们(并消耗所有内存)。换句话说,你想遍历笛卡尔积。
如果是这样的话,你有没有查看过this Javascript implementation of the X(n) formula?使用它,你可以按自然顺序迭代它们 <<0,0,0>, <0,0,1>, <0,1,0>, ...> 或选择任意位置进行计算。
看起来你只需要执行以下操作:
// move through them in natural order, without precomputation
lazyProduct(tokens, function (token) { console.log(token); });

// or...
// pick ones out at random
var LP = new LazyProduct(tokens);
console.log(LP.item(7));   // the 7th from the list, without precompute
console.log(LP.item(242)); // the 242nd from the list, again without precompute

我一定是错过了什么......? 在 X(n) 公式的情况下,生成器只是多余的。 更新
在JSFiddle中 我放置了一个有仪表的版本的 lazyProduct 代码,一个标记数组的示例数组,并使用这些 tokens 调用了 lazyProduct
当你不进行修改地运行代码时,你会看到它生成了样例数组tokens中期望的输出0@a等。我认为链接已经很好地解释了逻辑,但简单来说,如果你取消lazyProduct中的插装,你会注意到有两个关键变量lensplens是传入的每个数组(在数组的数组中)的长度的预计算。p是一个堆栈,它保存当前路径(例如,如果你在“第1个数组第3个索引,第2个数组第2个索引和第3个数组第1个索引”,则p表示该路径),并将其传递到你的回调函数中。
我的回调函数只是按照你的示例对参数进行连接,但这些仅是映射到原始数组的数组中相应值的p
如果你进一步进行性能分析,你会发现建立笛卡尔积所需的空间仅限于调用回调函数所需的内容。试着将其应用在你最坏情况下的令牌之一,看看效果。

更新2
我编写了大约75%基于笛卡尔积的方法。我的API接受一个ret.js解析树,将其转换为RPN,然后生成一组集合传递到X(n)计算器中。使用@ruud的示例([ab]{2}){1,2}|[cd](f|ef{0,2}e),将生成以下内容:

new Option([
    new Set([
        new Set(new Set(['a','b']), new Set['a','b'])),
        new Set(new Set(['a','b']), new Set['a','b']))
    ]),
    new Set(
        ['c', 'd'],
        new Option([
            new Set(['f']),
            new Set(['e']]),
            new Set(['e','f']),
            new Set(new Set(['e','f']), new Set(['e', 'f']))
        ])
])

棘手的部分是嵌套选项(有缺陷)和反向字符类和反向引用(不支持)。

这种方法变得脆弱,实际上迭代器解决方案更优秀。将您的语法树转换为迭代器应该相当简单。感谢这个有趣的问题!


这让我想起了阶乘数系统,并且这可能正是我需要的。让我玩一下它,稍后再回复你。谢谢你提供的非常有趣的网站。=) - Alix Axel
我已经盯着这段代码一段时间,试图理解它如何适应我的需求,但是考虑到像 [a-z]{10} 这样的正则表达式,我仍然需要预先计算并在内存中保存 26^10 个值,对吗?只有在那之后我才能将其传递给 lazyProduct - 或者我还有其他方法吗? - Alix Axel
不是的,唯一的预处理是计算数组长度(在数组的数组中)。有关详细信息,请参阅我的更新。 - bishop
我想表达的是,由于ret.js生成了一个n维解析树结构,所以我需要首先将其展平为二维数组才能使用这种方法。首先展平它的问题在于,我实际上会生成几乎所有可能的组合,这将占用大量内存。当我提到“tokens”数组时,我的问题有点误导人:虽然可以这样做,但从内存消耗的角度来看并不可行。这有意义吗?我稍后会尝试发布一个ret.js解析树的示例。 - Alix Axel
这是a|b[a-z]{3}ret.js解析树。如果我要将其展开并扩展为二维数组,它看起来像[['a','baaa',...,'bzzz']],包含17577个元素(实际上是所有可生成的元素)。类似[['a'],['b'],['a',...,'z'],['a',...,'z'],['a',...,'z']]的东西会更短,但会产生无效输出(那些以a为前缀的)。 - Alix Axel
显示剩余7条评论

0

我想分享一下我使用生成器基于invRegex.py开发的内容:

var ret = require('ret');

var tokens = ret('([ab]) ([cd]) \\1 \\2 z');
var references = [];

capture(tokens);
// console.log(references);

for (string of generate(tokens)) {
    console.log(string);
}

function capture(token) {
    if (Array.isArray(token)) {
        for (var i = 0; i < token.length; ++i) {
            capture(token[i]);
        }
    }

    else {
        if ((token.type === ret.types.ROOT) || (token.type === ret.types.GROUP)) {
            if ((token.type === ret.types.GROUP) && (token.remember === true)) {
                var group = [];

                if (token.hasOwnProperty('stack') === true) {
                    references.push(function* () {
                        yield* generate(token.stack);
                    });
                }

                else if (token.hasOwnProperty('options') === true) {
                    for (var generated of generate(token)) {
                        group.push(generated);
                    }

                    references.push(group);
                }
            }

            if (token.hasOwnProperty('stack') === true) {
                capture(token.stack);
            }

            else if (token.hasOwnProperty('options') === true) {
                for (var i = 0; i < token.options.length; ++i) {
                    capture(token.options[i]);
                }
            }

            return true;
        }

        else if (token.type === ret.types.REPETITION) {
            capture(token.value);
        }
    }
}

function* generate(token) {
    if (Array.isArray(token)) {
        if (token.length > 1) {
            for (var prefix of generate(token[0])) {
                for (var suffix of generate(token.slice(1))) {
                    yield prefix + suffix;
                }
            }
        }

        else {
            yield* generate(token[0]);
        }
    }

    else {
        if ((token.type === ret.types.ROOT) || (token.type === ret.types.GROUP)) {
            if (token.hasOwnProperty('stack') === true) {
                token.options = [token.stack];
            }

            for (var i = 0; i < token.options.length; ++i) {
                yield* generate(token.options[i]);
            }
        }

        else if (token.type === ret.types.POSITION) {
            yield '';
        }

        else if (token.type === ret.types.SET) {
            for (var i = 0; i < token.set.length; ++i) {
                var node = token.set[i];

                if (token.not === true) {
                    if ((node.type === ret.types.CHAR) && (node.value === 10)) {
                    }
                }

                yield* generate(node);
            }
        }

        else if (token.type === ret.types.RANGE) {
            for (var i = token.from; i <= token.to; ++i) {
                yield String.fromCharCode(i);
            }
        }

        else if (token.type === ret.types.REPETITION) {
            if (token.min === 0) {
                yield '';
            }

            for (var i = token.min; i <= token.max; ++i) {
                var stack = [];

                for (var j = 0; j < i; ++j) {
                    stack.push(token.value);
                }

                if (stack.length > 0) {
                    yield* generate(stack);
                }
            }
        }

        else if (token.type === ret.types.REFERENCE) {
            console.log(references);
            if (references.hasOwnProperty(token.value - 1)) {
                yield* references[token.value - 1]();
                // yield references[token.value - 1]().next().value;
            }

            else {
                yield '';
            }
        }

        else if (token.type === ret.types.CHAR) {
            yield String.fromCharCode(token.value);
        }
    }
}

我还没有弄清楚如何实现捕获组/引用以及在REPETITION令牌类型中产生的值尚未按词典顺序生成,但除此之外它可以工作。


顺便说一下,我在你的gist上留言了。 - cactus1
1
将生成器推到引用数组中是个好主意,但你应该提前这样做,而不是在繁忙生成匹配时。捕获组的编号根据它们的词法位置;这是静态的,应由解析器确定,而不是由生成器确定。顺便说一下,如果你还感兴趣:我扩展了我的早期答案,也加入了引用。这真的很容易。抱歉,那是我自己解决方案的无耻宣传。;-) - Ruud Helderman
@Ruud: 的确,我也注意到了完全相同的问题,并提出了 升级版本,但是 yield* references[]... 仍然会产生所有可能的选项而不是实际上生成的最后一个选项。仍然不知道如何修复它,我会检查你的最新版本,看起来非常有前途! - Alix Axel
1
我明白你的意思; 我猜 yield* references[token.value - 1](); 应该被替换为返回生成器 当前 值的某个方法或属性,但是我在 MDN 上找不到这样的内容。有任何想法吗?无论如何,DIY 迭代器可以让你更加掌控,所以我很高兴你喜欢我的方法。如果你需要更多帮助,请告诉我。 - Ruud Helderman

0
这里已经有很多好的答案了,但我特别想让生成器部分能够工作,而这对您来说并没有效果。看起来您试图做到这一点:
//the alphanumeric part
for (x of alpha()) for (y of numeric()) console.log(x + y);

//or as generator itself like you wanted
function* alphanumeric() {
    for (x of alpha()) for (y of numeric()) yield(x + y);
}
//iterating over it
for (var i of alphanumeric()) {
    console.log(i);
}

输出:

a0
a1
b0
b1
c0
c1

您可以在正则表达式匹配中使用此方法来获取笛卡尔积。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接