恕我简陋,恕我臃肿

时间:2021-09-02 00:30:56

    好吧,我承认为了赶赶时髦,做了点自讨苦吃的东西,现在还只做了一半。先记下吧,怕有些东东搞忘了。

很惭愧,正则一直以来都是我的痛处,于是也想借着这个机会顺便练习下自己的正则,可惜做着做着我发现,潜意识里我为了逃避正则,最后却放弃了一心想提升的正则,把这些匹配做成了状态机...用了beautity.js的方式...

    token的判断由正则演变成了模式匹配,好吧,我承认我错了。      

               this .whitespace      =   \n\r\t ' .split( '' );

            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对当前的单字分析是无法得出正确结果的。比如

function  a() {

}
/ a / g  //正则

或者:

var  g  =   2 ;
+ function  a() {

}
/ a / g
这又是除法,可能在实际的代码中不太可能有这种情况的代码片段,可是这在syntax中是能通过的,不能弃它不顾。

Army说在实际使用中,在假设输入代码正确的情况下,可以“投机取巧”,他的意思是:看在斜线符号之前的“东西”到底是什么——当然,空白符是不算在内的,因此循环体内首先要做的就是忽略空白。假如前面是标识符的组成或者右括号的话,那么显然它是个被除数,斜线也应该被解读为除号;而如果是其它情况的话,斜线则是正则表达式的开头。

     这是建立在大多数情况下:成为被除数的可能是以下3种:数字、变量、括号内的运算结果。
     仍然有很多例外的情况,比如上面的代码片段,仅从局部词法来看,那么,前面为‘}’ 也可能是除法...
     由于js语言自动装箱的特殊性,推而广之,']'后面的也会是除法,比如:[12]/2/1 ,这个运算结果是6,数组隐式装箱toString后运算。

请把console打开,运行下面的东东:

另外,一个关键点在于正则除以正则,这在词法中是完全通过的。比如下面的情况:

不仔细看上面的式子的话,可能有点乱,正则和除法和注释交错在一起。看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自运算的情况,同样应该把这个'/'考虑成除号。这也是目前还没做的。

     要处理的东西实在很多,心里猫抓一样.... 高亮和格式化的话,可能的确得等等了...
     这是到现阶段的代码:基本没用正则,稍显臃肿,在功能都没做好之前,更是没心情考虑所谓的效率了....