使用parsimmon库解析基于缩进的语言

时间:2016-11-09 02:50:14

标签: javascript parsing parser-combinators

我的问题受this one启发,但对于javascript,使用parsimmon解析器 - 组合器库。我想解析缩进敏感的语言,比如python或yaml。

我设法将该答案中的scala示例轻松转换为javascript - 关键是parsimmon中的chain函数,相当于scala中的>>运算符&#39 ; s解析器组合器 - 它们都使用解析器和返回解析器的函数,并将第一个解析器的结果传递给函数以选择下一个解析器。

然而,我无法完全理解如何使其递归。这个例子是一个单独的块 - 我不知道如何创建嵌套块,跟踪需要处理deque的级别来解析像python这样的东西。

2 个答案:

答案 0 :(得分:1)

嗯,这是一种方法。它可能不是最好的,它绝对不是直观的(我不确定我理解为什么它有效,而且我写了它:)但它看起来非常强大。

基本上它说:treeline,可选地后跟block。反过来,blocktree s的缩进列表。

indent是一个获取当前缩进级别的函数,并返回一个解析器,该解析器需要缩进的行数大于该值。返回的解析器返回一堆先前的缩进级别。

我之前说它很健壮 - 事实上,它健壮。它接受真正应该抛出错误的输入:如果你缩进到与前一级别不匹配的缩进级别,它基本上“向上舍入”到下一个缩进级别。我不确定修复它的逻辑应该去哪里 - 与解析器“链”混合的相互递归很难遵循!

var {regex, seq} = Parsimmon;

function tree(state) {
    return seq(
        line,
        block(state).atMost(1).map(x => x[0]? x[0] : [])
    );
}

function block(state) { 
    return indent(state)
        .chain(tree).atLeast(1);
}

function indent(state) {
    return regex(/\s/).atLeast(state + 1)
        .map(x => x.length)
        .desc('indent');
}

let item = regex(/[^\s].*/).desc('item');
let line = item.skip(regex(/\n?/));
let start = block(-1);

let result = start.parse('top item 1\n  sub item 1\n  sub item 2\n' + 
    '    even deeper\n  sub item 3\ntop item 2');
console.log(JSON.stringify(result['value'], null, 2));
<script src="https://cdn.rawgit.com/jneen/parsimmon/master/src/parsimmon.js"></script>

答案 1 :(得分:0)

为了解析嵌套块,您基本上必须预处理输入脚本并在缩进增加或减少的地方插入一些预定义的特殊字符INDENT / DEDENT,然后才应应用解析规则。这些特殊字符相当于传统语言中的大括号 { 和 },因此预处理有效地将您的语言从基于缩进的语言转换为以大括号分隔的语言,以用于解析过程的内部目的。

作为如何完成此操作的示例,您可以查看 Hypertag,这是一种在 Python 中实现的基于缩进的语言。特别是,您可能希望查看 preprocess() 方法和 grammar 规范 - 后者假定已经执行了预处理并且所有缩进都已替换为 INDENT_* 和 DEDENT_* 字符。