用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是一个很好的方法,可以确保这些错误和遗漏从最终产品中消除。
仔细查看每一块,确保你了解你正在看的东西。你可能会发现,你可以做的事情毕竟还有很多,以帮助你的用户。
我的研究得出了 三个选项:
-
使用一个解析器生成器和一个JavaScript语法,并希望得到最好的结果
-
JSLint有一个解析器......大约在2000行左右
-
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就会成为一个整洁的小工具,对很多事情都有用。