好吧,我承认为了赶赶时髦,做了点自讨苦吃的东西,现在还只做了一半。先记下吧,怕有些东东搞忘了。
很惭愧,正则一直以来都是我的痛处,于是也想借着这个机会顺便练习下自己的正则,可惜做着做着我发现,潜意识里我为了逃避正则,最后却放弃了一心想提升的正则,把这些匹配做成了状态机...用了beautity.js的方式...
token的判断由正则演变成了模式匹配,好吧,我承认我错了。
this.wordchar ='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');
this.numbers ='0123456789'.split('');
this.operator ='+ - * % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |= ::'.split('');
this.lineKeywords ='continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(',');
我用的思路简单,基本一两句话就能说通,对你的source code一个字节一个字节进行轮询,以查询到的字符作为token的结果的开始,向后继续查找符合同一token规则的字符,直到不满足当前token规则,查询结束,期间轮询到字符加成字符串,就是符合当前token的一个word。
比如,简单的,我要找符合js语法规则的字符串,比如变量名,函数名,以及一些的其他的字段,暂且定这种规则为'TK_WORD',即普通word的token,它对应的字符集在上面的wordchar里面,那么轮询开始后,假如找到字符在对应wordchar字符集里面,那么"TK_WORD"token就起作用了,以此为基点向后继续查找所有满足wordchar字符,直到规则不满足而跳出,中间查询出来的连续字符串就成了符合这个‘TK_WORD’的一个word。
于是,一个word与token的对应关系就建立起来了,其后根据js语法规则定义出所有token,然后把你整个source code过一遍,每个token对应的word都找出来。
必然的,这些都不是最重要的,一个重头戏是有关于正则和除法的纠结不清的关系。
【正则&除法,夫妻相随】
通常情况下,其他情况其实都还好处理,比如标识符IDENTIFIER,字符串STRING,小括号,中括号表达式EXPRESSION,大括号BRACE,其他的运算符OPERATOR,等等,基本都是同一个思路出来的。
可是一旦遇上'/',似乎突然就麻烦了很多。
{
有可能是除法,没错...
也有可能是 //... 行内注释
也可能是 /* ... */ 多行注释
也可能是 /reg/ig 正则
}
恩恩,注释其实还好,它有最高优先级,只要注释符不出现在字符串中,一旦发现 // 或者 /* 开始,就进入注释状态,顶多在注释里加上行内或者多行的判断。
那么,正则和除法该怎么区分呢?光是token的确是不够的,我同意winter和hax的观点。 如果只通过token对当前的单字分析是无法得出正确结果的。比如
}
/ a / g //正则
或者:
+ function a() {
}
/ a / g
Army说在实际使用中,在假设输入代码正确的情况下,可以“投机取巧”,他的意思是:看在斜线符号之前的“东西”到底是什么——当然,空白符是不算在内的,因此循环体内首先要做的就是忽略空白。假如前面是标识符的组成或者右括号的话,那么显然它是个被除数,斜线也应该被解读为除号;而如果是其它情况的话,斜线则是正则表达式的开头。
这是建立在大多数情况下:成为被除数的可能是以下3种:数字、变量、括号内的运算结果。
仍然有很多例外的情况,比如上面的代码片段,仅从局部词法来看,那么,前面为‘}’ 也可能是除法...
由于js语言自动装箱的特殊性,推而广之,']'后面的也会是除法,比如:[12]/2/1 ,这个运算结果是6,数组隐式装箱toString后运算。
另外,一个关键点在于正则除以正则,这在词法中是完全通过的。比如下面的情况:
不仔细看上面的式子的话,可能有点乱,正则和除法和注释交错在一起。看console应该会明了一点。
【关于hax和winter的测试用例】
hax对一些常见的需要考虑的又有些困难的情况做过简单的总结。可以参考他很久前的两篇博客http://hax.javaeye.com/blog/181358 和 http://hax.javaeye.com/blog/183575
如他所说,如果要进行JS代码的parse、transform、预处理、代码生成甚或compile等等,就需要考虑到JS的语法。以下是个很不完整的代码片段,parser、lint、preprocessor、transformer等等都需要能正确处理它,才具有实用价值。
同时也提供了一个包含了多行注释、单行注释、字符串、多行字符串(非ES3标准,但各种引擎都支持)、转义字符等等 的例子-->如下:
此外,如上所说,出现‘/’的地方到底是除法还是正则的开始,是要在具体语境中才能看出来的,即(syntactic grammar contexts)
仅仅依靠syntax是不行的。
最后的/b/g 既没有被注释,也不是正则,其实应该回溯到之前的代码,忽略掉空白和注释之后其实应该是个换行除法(a/b/g)具体解析情况可以运行后看console...
我现在也只是处理了回溯一级的注释,如果有多段注释夹杂在换行除法中,目前没在考虑范围中...
winter前段时间也有个轻量级的js词法高亮的东东,他有个测试用例,我这里也贴一下:
看结果应该看得出来,还有纰漏,
1.在于现在对于出现'/'回溯的情况是:在忽略whitespace后,‘/’的前一个word如果是')',那么都会把这个 '/'当做除号。但是还有例外的情况没有考虑,比如: if (true)/reg/.test('abc') 这样的式子。因为 if条件句的缘故,回溯到的“)”就不是“被除数”了。所以在这种情况下,这应该是正则。
2.还有一个,‘/’回溯时,如果是‘}’,目前并没做特别处理,事实上是需要的,正如本文最开始举得例子一样,如果是类似于 void function(){}/a 或 +function(){}/a 或 -function(){}/a 等等function自运算的情况,同样应该把这个'/'考虑成除号。这也是目前还没做的。
要处理的东西实在很多,心里猫抓一样.... 高亮和格式化的话,可能的确得等等了...
这是到现阶段的代码:基本没用正则,稍显臃肿,在功能都没做好之前,更是没心情考虑所谓的效率了....