高亮环境
说到为网页添加代码高亮功能,使用服务器端语言处理无疑是更高效、更兼容的做法(比如基于PHP的GeSHi)。然而这一方式主要面对的问题有三个:
- 加重了服务器负担,每次读/写都要将代码解析成正确显示的结果,而且代码过多时传输解析结果也会浪费一定的带宽。
- 增加语言种类或者维护升级时更新高亮程序相当费事,倘若支持热部署并且没有集群时还轻松些。
- 服务器存储的结果是原始的代码还是高亮后的结果?前者会使得每次读操作都要解析一下浪费资源;后者在修改原始代码的时候则会造成一定困难。引入缓存机制或许是个解决办法,但缓存本身就会增加技术维护量。
抛开服务器端,倘若这一切采用web端来做会是怎样的结果?
- 每次读时都要将代码解析成正确显示的结果,然而这一切是在客户端做的,与服务器无关,也不会浪费带宽。
- 维护高亮程序就是维护前端程序(js或者as等),成本要低得多。
- 服务器存储的是原始代码,不影响代码本身的修改,只需做简单的过滤即可,完全脱离了高亮逻辑。
也正是如此,web端语法高亮成为了主要的潮流。目前web端流行的编程语言无非js和as,silverlight尚需时日。其中js虽然有点兼容性问题,但在前端开发工程师手中早已有无数破解之道;as拥有跨平台、高性能以及良好的OOP支持(这里as主要指as3,下同),却未必如js那般近100%的支持。由此,现在能见到的web端高亮器几乎都是js写的,除了jssc(jssc前3版也是js写的,名称前缀即因此而来)。
对比参数
既然目标锁定了web端语法高亮器(废话,要不然文章标题不是白起了),那么衡量一款语法高亮程序就有了针对性。无非从以下几个方面进行对比:平台支持和兼容性、支持语法种类、程序体积大小、解析速度(性能)、解析结果正确性、功能体验、可扩展性。
- 平台支持和兼容性
无疑只有js和as的对比。
兼容性不说,as生成abc字节码跨平台支持,只需有avm虚拟机(Flash Player的虚拟机)支持即可;js虽然在各个核心中表现不同,但对前端开发工程师来说并不构成问题。
可到了平台支持上as就不完美了,毕竟它要依靠avm才能运行——这可能是用as来编写高亮程序唯一的弱点了,尽管Adobe吹嘘fp的普及率有97%;而js却在现代浏览器中得到普遍支持,除非客户端手动关闭它。
- 支持语法种类
这个要看作者原意提供多少种语法支持了。提供的语法当然是越多越好,但同时出现的问题就是写程序的人必须通晓所有语法特性才行。不可能幻想一个高亮解析器能够自动对所有编程语言进行正确解析,除非掌握这门语言的基本语法知识。
这对作者来说是个相当大的挑战,因为不可能一个人能够知晓所有编程语言的特点。良好的做法是设计好接口,让其它有兴趣的人参与进来,编写未实现的语法高亮程序。
- 程序体积大小
这点其实和上面一条互斥,支持的语法越多自然体积会更大。可行的办法无非一方面尽可能减少程序本身大小,提升代码复用读;另一方面用按需装载或按需组合只加载用到的那一部分(JSI?)。
- 解析速度(性能)
as在这方面有着先天性的优势。js则不一样了,依赖于引擎的不同,各浏览器的表现也不一致。而且,为了优化算法提高性能,js编写的高亮引擎往往需要牺牲一定的准确性来达到目的。
- 解析结果正确性
这个上面提到了,最正确的做法无非是针对某种语言的词法分析。然而这样做势必对实现语言有着很大的性能要求,js目前还是远远不能够胜任的,它只能采取某些方法进行折中,使得结果尽量正确。当然那些难以高亮的代码很少有人去写。
- 功能体验
行数、复制和折叠功能是最基本的。行数统计无需任何干预,html本身的ol节点就是顺序列表,自动支持行数;复制在各个浏览器下表现不一,最终还是需要通过flash player来实现(从这一点上说,无论任何前端高亮器其实都用到了js和as,只是侧重点不同);而折叠功能只能通过词法分析来准确实现,as的优势再次体现出来。
- 可扩展性
和第2点一样,要增加语法种类同时就考验了程序的可扩展性。目前所有高亮器都有着不错的支持,js高亮器需要注意的是提取所有语言的逻辑共性,词法分析则要针对每一种语言编写lexer,提取同类语言的通用部分。
高亮解析方式
这可能是初心者最关注的问题之一了。我们最终的目的是正确将一段代码解析成能够在网页上显示的结果,因此如何实现这个解析器(我更习惯这样叫它,因为jssc就是基于词法分析的,倘若是其它方式,称呼为高亮器可能更准确一点)的逻辑,便成了核心。从2007年的jssc 1开始,我大抵尝试过3种基本模式,这也是目前前端高亮器中被广泛采用的。以下将一一叙述它们的优缺点:
- 正则模式
说到高亮一段未知代码,可能你第一反应就是使用正则。没错,基于模式匹配来高亮程序代码,这个方法至今都被大部分人所采用,只是在它基础上进行了许多加工。考虑到如下js代码(以后例子都将以javascript举例):
典型的编程语言主要需要高亮以下几个部分:关键字(保留字)、数字(整数和浮点数)、字符(字符串)、注释。
其中关键字无法精确匹配,但每种语言对关键字的结构都有明确规定,如:以美元符号、下划线或者字母开头,后跟美元符号、下划线或者数字字母,如此用正则找出所有这种组合,再比较是否预留关键字即可;然而数字、字符、注释都具有一定的格式,采用正则很容易做到:
/^[$_a-z][$_a-z0-9]+/i、/^\d*?(\.\d+?)?$/、/^(“|’).*?\1$/、/\/\/.*?\n/。
这里面其实有很多问题,等到以后我们就会知道。
/*这个例子很简单,只需用正则匹配出多行注释、单行注释、关键字、字符串和数字即可。*/ for(var i = 0; i < 10; i++) { var s = "NO. " + i; alert(i); //循环10遍 } - 分区正则模式
最大的问题来自于交叉混合。试问:引号中出现注释该怎么办?注释中出现关键字怎么办?字符串跨行怎么办?数字在字符串里怎么办?这些都还只是初级问题,就能把正则模式完美KO掉,因此高亮器不可能只采用正则匹配便解析出所有。
仔细观察,问题的关键点在于交叉混合,只要把这个解决了,那么一切都如见天日。简单的做法便是在开头增加个预处理过程,将代码分成区块。
我们遍历代码字符串,遇到引号(无论单双引号)便寻找下一个引号。找到后要看是否被转义?一般情况下回答是否,那么这个区块也被分隔出来;但总有需要的情况出现,此时我们忽略它继续循环往下找,直到找到没被转义的字符串为止。
同样的,注释也是一样,不过注释有个好处是不存在转义问题。单行注释直接到行结尾,多行注释直接到结束注释符(*/)。
/*这个例子只用正则是做不到了,注释中有"字符串",字符串里可以有数字或关键字。 因此首先要做的是将代码分区,提出可能混淆的部分。*/ for(var i = 0; i < 10; i++) { alert("alert 10 times, this is NO." + i); } - 词法分析模式
新的问题来了。perl风格的正则表达式该怎么办?回答依然是分区,不过这可能有点困难,因为你很难将跨行除法与正则表达式区分开来。当遇到一个除号时,它可能就是一个除号,也可能是一个正则表达式的开头。想弄清它就需要回溯,根据之前的语境来判别。另外perl风格的正则表达式的flag亦是一个难点,分区很难将它们包括进来。正则表达式的字符集([]内的特殊符号可以省略转义)允许省略转义符,它处理起来也是相当得麻烦。
那么,有没有更好的解决办法呢?我们需要一个圣杯,来完美地实现解析要求。最终,埋藏在宝藏中的词法分析浮出水面。
词法分析概念
那么什么是词法分析?
简单来说,它是《编译原理》的基础入门知识。构造出来的编译器,在对我们平常书写的程序代码进行分析时,第一步所做的就是词法分析。具体的工作是,从左到右遍历源代码字符流,根据构词规则识别单词。比如如下代码:
var i = 0;
经过分析器后生成:
- var 关键字;
- i 变量名;
- = 赋值符号;
- 0 数字;
- ; 分号。
可以看出,词法分析将源代码切割为一个个最基本的逻辑单元(token),并去除了注释、空白等无用的符号。这和语法高亮很相似,借助其基本原理便可做出一个相当不错的解析器来。
jssc5正是采用了这种方法。

不错哦 ,支持army
不错,以前用PHP写过一个语法加亮程序,原理是把所有字符都遍历一遍,还有前后判断,性能比较差,看这篇文章收获不少。我也会尽快写出kindeditor可视化原理。
jssc 3 版本现在在哪里可以找到,想看下纯js写的版本~
@Colday: http://code.google.com/p/jssc/downloads/list
search里选择all就能看到。
不建议看那个版本的,实在太老了,代码也很糟糕。
[...] web端语法高亮原理:走进jssc的世界(一) [...]