使JavaScript进行自我解析的教程——llamaduck方法介绍

49 阅读4分钟

用JavaScript解析JavaScript

上周末,我开始了llamaduck的工作--一个简单的工具,旨在找出你的代码是否能在新发布的node 0.6.0上运行。最终,它可能也能执行其他的兼容性评估任务,但我首先专注于简单的东西。

或者至少我认为它很简单。

自0.4.x以来的API变化列表看起来并不长,应该很容易消化。但事实证明,我几乎花了整个星期天的时间来弄清楚如何把javascript变成一个漂亮的可分析的AST

如果你不知道什么是AST--它是一个所谓的抽象语法树,这意味着无论实际的语法是什么,它看起来都应该是一样的。虽然不同的语言会有所不同。因此,CoffeeScript的AST看起来应该和JavaScript一样,但Python的会有所不同。

JavaScript的改动和变化

在程序中,人们可能需要对JavaScript进行大量的修改和变更,这对一些人来说无疑是一种挑战,因为他们要准确地掌握他们应该做什么来处理这样的事情。

众所周知的是,JavaScript的改变和变化会使你的生活在短期内更具挑战性,但从长远来看,它将以更好的产品作为回报。

我所发现的是,为了让JavaScript按照我所希望的方式排列,需要做很多的改变。这种编码有很多细小的技术方面需要严格遵守,除非有大量的时间坐下来弄清楚他们需要做什么,否则肯定不容易完成。

JavaScript仍然是计算机程序员用来把他们的材料放到互联网上的主要语言,所以对我来说,开始使用它作为主要工具来完成我自己的工作是非常有意义的。我一直在依靠JavaScript来完成大大小小的项目,我知道只有以对客户有效的方式来使用它,才能从它身上获得最大的价值。

所有程序员对JavaScript的使用越来越多,这让我相信,我必须继续研究如何充分利用这个程序和我所知道的技能,以最好的方式来获得我想要的结果。

当通过实际的 JavaScript程序运行时,程序员经常会遇到一些常见的问题。有的时候会出现一些异常行,还有一些情况的出现,让人明显感觉到某段代码就是不能如愿的。如果你遇到这种问题,只要知道这就是这个过程的意义所在。首先通过程序运行JavaScript是一个很好的方法,可以确保这些错误和遗漏从最终产品中消除。

仔细查看每一块,确保你了解你正在看的东西。你可能会发现,你可以做的事情毕竟还有很多,以帮助你的用户。

我的研究得出了 三个选项

  1. 使用一个解析器生成器和一个JavaScript语法,并希望得到最好的结果

  2. JSLint有一个解析器......大约在2000行左右

  3. Uglify-JS据说也有一个分析器

唯一可行的选择是uglify-js。它是一个包装整齐的node.js模块,做的事情比我需要的多一点,但至少它有一个易于使用的解析器,有一个公开的API接口。

得分!

下面是一个输出自己 AST的文件的例子,让你感受一下我在说什么。

var parser = require('uglify-js').parser;
var util = require('util');
 
(function get_ast (path, callback) {
    require('fs').readFile(path, 'utf-8', function (err, data) {
        if (err) throw err;
 
        callback(parser.parse(data));
    });
})('./example.js', function (data) {
    console.log(util.inspect(data, true, null));
});

这个文件自我解析,并输出一个编码为 javascript数组的树(滚过疯狂的部分,那里还有一些文字)。

[ 'toplevel',  [ [ 'var',      [ [ 'parser',          [ 'dot',            [ 'call',              [ 'name', 'require', [length]: 2 ],
              [ [ 'string', 'uglify-js', [length]: 2 ],
                [length]: 1 ],
              [length]: 3 ],
            'parser',
            [length]: 3 ],
          [length]: 2 ],
        [length]: 1 ],
      [length]: 2 ],
    [ 'var',      [ [ 'util',          [ 'call',            [ 'name', 'require', [length]: 2 ],
            [ [ 'string', 'util', [length]: 2 ], [length]: 1 ],
            [length]: 3 ],
          [length]: 2 ],
        [length]: 1 ],
      [length]: 2 ],
    [ 'stat',      [ 'call',        [ 'function',          'get_ast',          [ 'path', 'callback', [length]: 2 ],
          [ [ 'stat',              [ 'call',                [ 'dot',                  [ 'call',                    [ 'name', 'require', [length]: 2 ],
                    [ [ 'string', 'fs', [length]: 2 ], [length]: 1 ],
                    [length]: 3 ],
                  'readFile',
                  [length]: 3 ],
                [ [ 'name', 'path', [length]: 2 ],
                  [ 'string', 'utf-8', [length]: 2 ],
                  [ 'function',                    null,                    [ 'err', 'data', [length]: 2 ],
                    [ [ 'if',                        [ 'name', 'err', [length]: 2 ],
                        [ 'throw',                          [ 'name', 'err', [length]: 2 ],
                          [length]: 2 ],
                        undefined,
                        [length]: 4 ],
                      [ 'stat',                        [ 'call',                          [ 'name', 'callback', [length]: 2 ],
                          [ [ 'call',                              [ 'dot',                                [ 'name', 'parser', [length]: 2 ],
                                'parse',
                                [length]: 3 ],
                              [ [ 'name', 'data', [length]: 2 ], [length]: 1 ],
                              [length]: 3 ],
                            [length]: 1 ],
                          [length]: 3 ],
                        [length]: 2 ],
                      [length]: 2 ],
                    [length]: 4 ],
                  [length]: 3 ],
                [length]: 3 ],
              [length]: 2 ],
            [length]: 1 ],
          [length]: 4 ],
        [ [ 'string', './example.js', [length]: 2 ],
          [ 'function',            null,            [ 'data', [length]: 1 ],
            [ [ 'stat',                [ 'call',                  [ 'dot',                    [ 'name', 'console', [length]: 2 ],
                    'log',
                    [length]: 3 ],
                  [ [ 'call',                      [ 'dot',                        [ 'name', 'util', [length]: 2 ],
                        'inspect',
                        [length]: 3 ],
                      [ [ 'name', 'data', [length]: 2 ],
                        [ 'name', 'true', [length]: 2 ],
                        [ 'name', 'null', [length]: 2 ],
                        [length]: 3 ],
                      [length]: 3 ],
                    [length]: 1 ],
                  [length]: 3 ],
                [length]: 2 ],
              [length]: 1 ],
            [length]: 4 ],
          [length]: 2 ],
        [length]: 3 ],
      [length]: 2 ],
    [length]: 3 ],
  [length]: 2 ]

结论

现在我们有了一个简单的树,我们可以 递归分析并寻找不兼容的地方。但在做任何真正实用的事情之前,我需要弄清楚如何跟踪变量范围。这是真正困难的地方,因为代码需要检查变量何时成为关键部分,然后确认它们事实上最终会以关键方式被使用。

但是一旦破解了这个难题,llamaduck就会成为一个整洁的小工具,对很多事情都有用。