【问题标题】:lightweight javascript to javascript parser轻量级 javascript 到 javascript 解析器
【发布时间】:2011-10-14 16:15:40
【问题描述】:

我将如何编写一个轻量级的 javascript 到 javascript 解析器。可以转换一些sn-ps代码的简单东西。

我想基本上将函数中的内部范围对象公开。

像这样的

var outer = 42;
window.addEventListener('load', function() {
   var inner = 42;
   function magic() {
       var in_magic = inner + outer;
       console.log(in_magic);
   }
   magic();
}, false);

将编译为

__Scope__.set('outer', 42);
__Scope__.set('console', console);
window.addEventListener('load', constructScopeWrapper(__Scope__, function(__Scope__) {
    __Scope__.set('inner', 42);
    __Scope__.set('magic',constructScopeWrapper(__Scope__, function _magic(__Scope__) {
        __Scope__.set('in_magic', __Scope__.get('inner') + __Scope__.get('outer'));
        __Scope__.get('console').log(__Scope__.get('in_magic'));
    }));
    __Scope__.get('magic')();
}), false);

Demonstation Example

这背后的动机是序列化函数和闭包的状态,并使它们在不同的机器(客户端、服务器、多台服务器)之间保持同步。为此,我需要[[Scope]]

的表示

问题:

  1. 我可以在不编写完整的 JavaScript 的情况下进行这种编译器 ->(略有不同)JavaScript 编译器吗?
  2. 我将如何编写这样的编译器?
  3. 我可以重复使用现有的 js -> js 编译器吗?

【问题讨论】:

  • 我提到了动机。我需要对闭包范围的编程访问,以便我可以在不同的物理机器上序列化和同步闭包。
  • 嗯,但你最终不会序列化几乎所有内容吗?词法作用域是非常“父母”的,而不是块作用域。你试图写一个像样的节点模块来实现水平可扩展性?
  • @davin 是的。我确实会序列化所有内容。目的是能够向后兼容。
  • 您可能想要重构编译后的代码是如何输出的。目前没有办法区分myvariable='string'var myvariable='mystring'
  • 我希望我能增加更多的赏金,因为我对实际做到这一点的简单方法非常感兴趣......

标签: javascript parsing compiler-construction


【解决方案1】:

您的问题与使用 JS Opfuscators 和 JS Compressors 解决的问题属于同一类问题——它们以及您需要能够将 JS 解析并重新格式化为等效脚本;

obfuscators here 上进行了很好的讨论,您的问题的可能解决方案可能是利用 FOSS 版本之一的解析和生成器部分。

一个标注,您的示例代码没有考虑您要设置/获取的变量的范围,这最终将成为您必须解决的问题。

加法

考虑到闭包定义函数的作用域问题,您可能不太可能将这个问题作为静态解析问题来解决,因为必须导入/导出闭包之外的作用域变量来解析/保存和重新实例化作用域。因此,您可能需要深入研究评估引擎本身,并可能获取 V8 引擎并对解释器本身进行破解——假设您不需要它是通用的跨所有脚本引擎并且您可以绑定它到您控制的单个实现。

【讨论】:

  • Magic 很难定义闭包..所以你必须为每个闭包的每个实例创建一些人工 ID,然后还要找出如何导入/导出环境变量的方法可用于每个闭包实例。
  • @Scren 闭包并不难。您只需对范围链的设置方式保持可靠的参考即可。真正的问题是确保evalwith 不会中断。这将是一个正确的痛苦,我可能会忽略。
  • 我认为你是对的——但这也意味着你只能通过完整的解析和跟踪变量名等来解决这个问题,这样你就可以为每个访问点创建上下文块。因此,您需要从混淆器/压缩器之一或通过从 V8 获取相关部分来利用其中一个解析器
【解决方案2】:

上面的重写有问题,你没有将魔法的初始化提升到范围的顶部。

有很多项目可以解析 JavaScript。

  1. Crock 的 Pratt parser 在适合“好的部分”的 JavaScript 上运行良好,而在其他 JS 上则不太适用。
  2. es-lab 解析器基于 ometa,可处理完整的语法,包括 Crock 解析器遗漏的许多极端情况。它的性能可能不如 Crock 的。
  3. narcissus 解析器和评估器。我对此没有太多经验。

还有许多用于 JavaScript 的高质量词法分析器,可让您在令牌级别操作 JS。这可能比听起来更难,因为 JavaScript 在词法上不是规则的,如果没有完整的解析,预测分号插入是很困难的。

我的es5-lexer 是一个精心构建且高效的 EcmaScript 5 词法分析器,提供标记化 JavaScript 的能力。它是启发式的,JavaScript 的语法在词法上不是规则的,但启发式非常好,它提供了一种转换标记流的方法,这样解释器就可以保证按照词法分析器解释标记的方式来解释它,所以如果你不相信你的输入,您仍然可以确定安全转换背后的解释是合理的,即使根据规范对于一些奇怪的输入是不正确的。

【讨论】:

  • 所以你的信息是,“使用一个糟糕的 (Pratt) 解析器”,或者使用 just 一个词法分析器(你不能用这个做任何严肃的 JavaScript 操作)?我只是不明白你为什么认为这是一个答案。
  • @Ira,如果输入是相当标准的非混淆 JS,那么他可以使用 Pratt 解析器。如果输入的是乱七八糟的 JS 并且性能不是那么重要,那么 es-lab 解析器就可以了。如果转换属于可以在词汇级别轻松处理的一组事物,则进行词汇转换。不能在词汇级别上进行严肃的 JavaScript 操作是错误的;通常使用解析树更容易做到这一点,但有时性能权衡证明了工程工作的合理性。
  • 如果我理解他的要求,OP 想要获取任意代码并对其进行操作。如果您必须对复杂语言进行任何有趣的操作,那么在词位上很难做到。这就是人们发明解析器的原因。在这种特殊情况下,OP 需要知道所有状态变量的值 --> 符号的值,本地或其他。如果不理解嵌套、作用域,我看不出他怎么能做到这一点……而且如果不进行解析,你会很疯狂。您仅使用词位完成了哪些具体的复杂任务?
  • @Ira,仅通过词法分析,我已经确定了所有函数体,以便为匿名函数分配可读名称,并为分析和动态调用图生成保留所有函数入口和出口的定时日志。我还完成了对 catch 和 finally 块的跟踪,以允许强制执行无法捕获的异常。状态变量的值不能用任何级别的静态分析来确定,因为它是不可判定的。但是,您可以通过匹配花括号并识别 var 声明和形式参数将所有符号与范围相关联,所有这些都可以通过词法完成。
  • 如果你使用词法标记并为看起来像函数原型的东西做所有嵌套的大括号计数和测试,你本质上只是在构建一个糟糕的解析器。对于您所描述的任务,也许一个糟糕的解析器可以很好地工作。随着您将越来越多的语言结构带入图片中,您将获得越来越真实的解析。如果您想进行复杂的转换(尝试从函数中提取语句),您几乎需要完整的解析器。我认为 OP 无法使用您建议的样式的解析器来完成他的任务。 YMMV。
【解决方案3】:

如果你想要一个简单的界面,你可以试试 node-burrito:https://github.com/substack/node-burrito

它使用 uglify-js 解析器生成一个 AST,然后递归遍历节点。您所要做的就是提供一个回调来测试每个节点。您可以更改您需要更改的那些,它会输出结果代码。

【讨论】:

    【解决方案4】:

    鉴于您想要访问和恢复所有程序状态,我认为您的任务并不容易或短暂。问题之一是您可能必须在计算期间随时捕获程序状态,对吗?这意味着所示示例不太正确;它在执行该代码之前捕获了某种状态(除了您已经预先计算了初始化魔法的总和,并且在原始 JavaScript 的代码运行之前不会发生这种情况)。我假设您可能希望在执行期间的任何时刻捕获状态。

    您陈述问题的方式是您想要在 JavaScript 中使用 JavaScript 解析器。 我假设您正在想象您现有的 JavaScript 代码 J,包括这样的 JavaScript 解析器以及生成结果代码 G 所需的任何其他内容,并且当 J 启动时,它会将自身的副本提供给 G,制造序列化代码 S 并以某种方式加载它。 (如果 G 可以处理所有的 Javascript,我认为 G 相当大而且很老旧) 因此,您的 JavaScript 图像包含 J、大 G、S 并在启动时执行昂贵的操作(将 J 馈送到 G)。

    我认为可能更好地为您服务的是一个工具 G,它可以离线处理您的原始 JavaScript 代码 J,并生成程序状态/闭包序列化代码 S(用于保存和恢复该状态),可以添加/替换 J 以执行. J+S 被发送给客户端,客户端永远不会看到 G 或其执行。这将 S 的生成与 J 的运行时执行分离,节省了客户端执行时间和空间。

    在这种情况下,您需要一个工具来简化此类代码的生成。一个纯 JavaScript 解析器是一个开始,但可能还不够;您需要符号表支持才能知道哪个函数代码连接到函数调用 F(...),以及哪个变量定义在哪个范围内对应于对变量 V 的赋值或访问。您可能需要实际修改原始代码J 插入可以捕获程序状态的访问点。您可能需要流量分析来找出某些值的去向。在 JavaScript 中坚持所有这些会缩小您的解决方案范围。

    对于这些任务,您可能会发现program transformation 工具很有用。此类工具包含用于感兴趣的语言的解析器,构建表示程序的 AST,启用标识符到定义映射(“符号表”)的构建,可以对表示接入点插入的 AST 进行修改,或合成 AST代表您的演示示例,然后重新生成包含修改后的 J 和添加的 S 的有效 JavaScript 代码。 在我所知道的所有程序转换系统中(包括 Wikipedia 站点上的所有系统),没有一个是用 JavaScript 实现的。

    我们的DMS Software Reengineering Toolkit 就是这样一个程序转换系统,提供了我刚才描述的所有功能。 (是的,它又大又老;它必须处理真实计算机语言的复杂性)。它有一个JavaScript front end,其中包含一个完整的 AST 解析器,以及从修改或合成的 AST 重新生成 JavaScript 代码的机制。 (也大而苍白;好在 hoary + hoary 仍然只是 hoary)。如果有用,DMS 还为构建控制和数据流分析提供支持。

    【讨论】:

    • 您的 DMS 页面没有有用的“您可以在此处下载/安装/试用/购买”页面。无论如何我都找不到。
    • 它不是一个“下午就能下载玩”的工具。离线联系我进行更长时间的讨论;见简历。
    【解决方案5】:

    我会尝试寻找现有的解析器进行修改。也许你可以适应 JSLint/JSHint?

    【讨论】:

    • 我是这么想的,但与那些不了解代码的人一起乱搞是制造错误的可靠方法。我希望有一些解析技术不必编写完整的编译器
    • @Raynos:如果您想处理 JavaScript 引发的所有问题,您应该期望拥有一个完整的 JavaScript 前端(解析器、符号表构造)[但不是完整的编译器] .我认为“轻量级”一词不适用于此。
    • @Raynos:那你到底希望什么?您似乎想要一些无法处理所需复杂性的东西,并且您不想与可以处理的野兽打交道。 JSHint/JSLint 提案不需要您编写编译器;它要求您使用已经存在的 JavaScript 前端。有什么问题?
    • @IraBaxter 我很好奇是否还有其他解决方案,然后采用现有的 js 解析器并破解它以满足您的需求。
    • @Raynos:鉴于您有一个自定义问题,我怀疑您的解决方案是否会落在您身上。所以,你必须定制一些东西。鉴于您需要完整的 JavaScript 解析能力来有效地处理 Javascript(好吧,您可能不会,但这似乎是一个糟糕的选择,而且您在整个过程的大部分时间都不想感到惊讶)自定义现有的解析器似乎最好的选择。 (@missingno:+1)
    猜你喜欢
    • 2010-10-06
    • 2012-01-25
    • 2010-09-23
    • 1970-01-01
    • 2011-01-09
    • 1970-01-01
    • 2019-07-25
    • 2013-07-31
    • 2012-10-24
    相关资源
    最近更新 更多