PEG 语法,它接受三个任意顺序的可选元素

PEG grammar which accepts three optional elements in any order

假设我们有三个元素 a bc

一个有效的表达式使用这三个元素(和可选的空格)。

  1. 这三个要素中至少有一个必须存在。
  2. 所有三个元素都是可选的(只要至少存在其他两个元素之一,请参见 1)。
  3. 提供这三个元素的顺序并不重要。

是否有一种惯用的方法来编写满足这三个要求的 PEG 语法?

我在 http://pegjs.org/online 玩过 peg.js 并解决了 (1)(前瞻)和 (2),但 (3) 没有解决。有什么建议吗?

e = &(a / b / c) (a? b? c?) 

a = 'a' _
b = 'b' _
c = 'c' _

_ = [ \t]*

真正唯一的可能是列出所有六个可能的顺序,因为 PEG 没有 "unordered permutation" 运算符。 (传统的上下文无关文法也没有,所以大致相同的过程是必要的。

例如,您可以使用:

a (b c? / c b?)? / b (a c? / c a?)? / c (a b? / b a?)?

但是为更多的备选方案构建显然是乏味的。

通过接受 xy、...的任意列表,然后检查语义动作中的重复,通常更容易解决 "a list of x, y, ... in any order but without repetitions" 等问题。这不仅使语法更容易编写,而且允许更有意义的错误消息。

感谢 peg.js 的强大功能,如果元素列表 s 是一些元素集 S 的组合(不允许重复)。基本思想是计算 S 的幂集并将 s 的每个元素映射到一个素数。 S 的每个元素映射到其对应元素的素数乘积,即 S 的幂集的每个元素映射到唯一的数字。集合 sS 中元素的组合,当且仅当 s 中相应素数的乘积在从 S 计算的素数乘积中。 (我想,执行此检查的方法不止一种 :-))。下面是 peg.js 的解决方案,其中包含 5 个元素,我认为它非常有效。 (使用 & { predicate } 时的一个小问题:内部的 javascript 是用参数对象中的所有命名表达式调用的,因此 (a / b /c /d /e)+ 必须有一个名称,例如 el:(a / b /c /d /e)+ )。

{
    // array of elements (expressions)
    var data = ['a','b','c', 'd', 'e'];

    // map elements to primes
    var primemap = {
       a: 2,
       b: 3,
       c: 5,
       d: 7,
       e: 11
    };

    // powerset of an array
    function powerset(arr) {
        var ps = [ [] ];
        for (var i=0; i < arr.length; i++) {
            for (var j = 0, len = ps.length; j < len; j++) {
                ps.push(ps[j].concat(arr[i]));
            }
        }
        return ps;
    }

    // compute the product of primes corresponding to each element of an array arr
    function primeprod(arr) {
       return arr.reduce( function(p,c) { return p * primemap[c] }, 1 );  
    }

    // compute powerset and remove empty set at index 0 of the powerset
    var ps = powerset(data);
    ps.splice(0,1);
    // map elements of powerset to products of primes
    var prods = ps.map( function(el) { return primeprod(el); });

    // returns true if an arr is a combination of the elements
    function isCombination(arr) {
       return prods.indexOf(primeprod(arr)) !== -1
    }
}

expr =  exp / blankline;

exp = (el:(a / b / c / d / e)+ &{ return isCombination(Array.prototype.slice.call(arguments)[0]); } {return el; } ) rest*

a = _ a:'a' {return a; }
b = _ b:'b' {return b; }
c = _ c:'c' {return c; }
d = _ d:'d' {return d; }
e = _ e:'e' {return e; }

rest = [^abcde]

blankline =
    [ \t]* ("\n" / eof) { return []; }

_ = [ \t]*
eof = !.