<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>army8735 &#187; 系列文章</title>
	<atom:link href="http://army8735.org/category/%e7%b3%bb%e5%88%97%e6%96%87%e7%ab%a0/feed" rel="self" type="application/rss+xml" />
	<link>http://army8735.org</link>
	<description>我可以A，我也可以-A，我可以同时A和-A。</description>
	<lastBuildDate>Fri, 25 Nov 2011 04:07:52 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>web端语法高亮原理：走进jssc的世界（五）</title>
		<link>http://army8735.org/2010/02/23/669.html</link>
		<comments>http://army8735.org/2010/02/23/669.html#comments</comments>
		<pubDate>Tue, 23 Feb 2010 06:45:47 +0000</pubDate>
		<dc:creator>army8735</dc:creator>
				<category><![CDATA[jssc]]></category>
		<category><![CDATA[系列文章]]></category>
		<category><![CDATA[语法高亮原理]]></category>

		<guid isPermaLink="false">http://army8735.org/?p=669</guid>
		<description><![CDATA[整体流程
终于说到整体流程上了。之前的文章一直在解说as中如何来做词法分析，js方面丝毫未提，更没说jssc到底在页面上是怎么工作的。现在，就来详细地解释一下。
环境需求：

现代浏览器（废话）
装有Adobe Flash Player插件，版本号为9或9以上
javascript启用
页面中放入jssc.swf文件

大致流程：

swf文件载入完毕
内嵌在swf中的js首先被执行，寻找页面中所有符合规则的pre节点
顺序遍历这些pre节点，提取每个pre节点的文本内容（即原始代码）
将原始代码传递给swf
swf对传递来的原始代码进行词法分析，并生成一段结果html片段（即一串li节点）
将结果传递回js
js对结果进行包装（一串li节点放入ol节点中，以及标题头、边距和复制等等）
将对应的原始pre节点隐藏（display:none）
将新生成的内容插入原始pre节点的前面

可以看出，js和as耦合的地方在哪里，以及它们之间是如何相互合作的。下面将对以上9个步骤细细说来，希望更多的开发者能提出改进意见～
1.载入swf
这里没什么好说的，在页面底部加入flash标签即可。值得注意的是不同浏览器中标签有所区别，理想情况下可以使用swfobject来插入标准的html标签。不过为了兼容以及简单，一般我是这样做的：
&#60;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5" style="position:absolute;visibility:hidden;"&#62;
	&#60;param name="movie" value="jssc5.swf"/&#62;
	&#60;param name="flashvars" value="find=brush"/&#62;
	&#60;embed src="jssc5.swf" flashvars="find=brush" name="jssc5" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/&#62;
&#60;/object&#62;
这里面有许多可配置的地方。
object的id和embed的name必须一致且唯一（应该是唯二才对），为了满足不同浏览器的需要。我设为jssc5，这也是默认值。如果和页面上某个节点有冲突需要修改，那么就需要传入参数修改配置。比如说变成jssc5_modify，就得在flashvars中这样做：
&#60;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;"&#62;
	&#60;param name="movie" value="jssc5.swf"/&#62;
	&#60;param...]]></description>
			<content:encoded><![CDATA[<h3>整体流程</h3>
<p>终于说到整体流程上了。之前的文章一直在解说as中如何来做词法分析，js方面丝毫未提，更没说jssc到底在页面上是怎么工作的。现在，就来详细地解释一下。</p>
<p>环境需求：</p>
<ol>
<li>现代浏览器（废话）</li>
<li>装有Adobe Flash Player插件，版本号为9或9以上</li>
<li>javascript启用</li>
<li>页面中放入jssc.swf文件</li>
</ol>
<p>大致流程：</p>
<ol>
<li>swf文件载入完毕</li>
<li>内嵌在swf中的js首先被执行，寻找页面中所有符合规则的pre节点</li>
<li>顺序遍历这些pre节点，提取每个pre节点的文本内容（即原始代码）</li>
<li>将原始代码传递给swf</li>
<li>swf对传递来的原始代码进行词法分析，并生成一段结果html片段（即一串li节点）</li>
<li>将结果传递回js</li>
<li>js对结果进行包装（一串li节点放入ol节点中，以及标题头、边距和复制等等）</li>
<li>将对应的原始pre节点隐藏（display:none）</li>
<li>将新生成的内容插入原始pre节点的前面</li>
</ol>
<p>可以看出，js和as耦合的地方在哪里，以及它们之间是如何相互合作的。下面将对以上9个步骤细细说来，希望更多的开发者能提出改进意见～</p>
<h3>1.载入swf</h3>
<p>这里没什么好说的，在页面底部加入flash标签即可。值得注意的是不同浏览器中标签有所区别，理想情况下可以使用swfobject来插入标准的html标签。不过为了兼容以及简单，一般我是这样做的：</p>
<pre class="brush:html">&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5" style="position:absolute;visibility:hidden;"&gt;
	&lt;param name="movie" value="jssc5.swf"/&gt;
	&lt;param name="flashvars" value="find=brush"/&gt;
	&lt;embed src="jssc5.swf" flashvars="find=brush" name="jssc5" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/&gt;
&lt;/object&gt;</pre>
<p>这里面有许多可配置的地方。</p>
<p><span style="color: #ff0000;">object</span>的<span style="color: #ff0000;">id</span>和<span style="color: #ff0000;">embed</span>的<span style="color: #ff0000;">name</span>必须一致且唯一（应该是唯二才对），为了满足不同浏览器的需要。我设为<span style="color: #ff0000;">jssc5</span>，这也是默认值。如果和页面上某个节点有冲突需要修改，那么就需要传入参数修改配置。比如说变成<span style="color: #ff0000;">jssc5_modify</span>，就得在flashvars中这样做：</p>
<pre class="brush:html">&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;"&gt;
	&lt;param name="movie" value="jssc5.swf"/&gt;
	&lt;param name="flashvars" value="find=brush&amp;swf=jssc5_modify"/&gt;
	&lt;embed src="jssc5.swf" flashvars="find=brush&amp;swf=jssc5_modify" name="jssc5_modify" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/&gt;
&lt;/object&gt;</pre>
<p>注意到param标签和embed标签中都得改，为了满足浏览器兼容需要……</p>
<p>同样，jssc会占用一个全局js变量名<span style="color: #ff0000;">jssc</span>（注意这个jssc是指变量名字符串，而前者是指软件），这是为了在js和as之间互相调用通信必须的。如果这个变量名和页面上的js变量有冲突，那么就会造成错误。所以它也可以进行修改配置，假如要改成<span style="color: #ff0000;">jssc_js</span>，就得在flashvars中这么做：</p>
<pre class="brush:html">&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;"&gt;
	&lt;param name="movie" value="jssc5.swf"/&gt;
	&lt;param name="flashvars" value="find=brush&amp;swf=jssc5_modify&amp;js=jssc_js"/&gt;
	&lt;embed src="jssc5.swf" flashvars="find=brush&amp;swf=jssc5_modify&amp;js=jssc_js" name="jssc5_modify" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/&gt;
&lt;/object&gt;</pre>
<p>最后，还有两个可以配置的参数：<span style="color: #ff0000;">css</span>和<span style="color: #ff0000;">url</span>。前者是指新生成的结果节点的class名称，你可以用firebug看到默认的亦是<span style="color: #ff0000;">jssc</span>；后者是指定swf的路径，这是为了在非ie浏览器下做copy使用（ie可以直接调用api做复制功能），从这个意义上说，整个swf文件其实是有两种功能——词法分析和复制按钮。这么做是为了减少http请求，同时js融合在as里也是为了减少http请求。</p>
<h3>2.内嵌的js执行</h3>
<p>js首先会寻找页面上所有符合规则的pre节点，那么是什么规则的？默认情况下，pre的class名称如果这样开头，js就会认为它是正确的（其实并不一定要求是开头，出现在其它位置也可以，但为了良好的习惯推荐如此）：</p>
<pre class="brush:html">&lt;pre class="brush:html"&gt;&lt;/pre&gt;</pre>
<p>这段被高亮的代码其实就是它本身。<span style="color: #ff0000;">brush</span>是<strong>键名</strong>，<span style="color: #ff0000;">html</span>是<strong>键值</strong>。假如想要高亮一段js代码，那么就需要更改键值：</p>
<pre class="brush:html">&lt;pre class="brush:js"&gt;&lt;/pre&gt;</pre>
<p>其它语言道理相同。另外，可以高亮的代码必须是jssc本身已经支持的语法种类，不支持的话也会被格式化，但没有高亮效果。</p>
<p>细心的人可能已经发现，<span style="color: #ff0000;">brush</span>键名和最开始那段代码中出现的相一致。没错，假如pre节点的class名称也有冲突，不能使用<span style="color: #ff0000;">brush</span>，要改成<span style="color: #ff0000;">brush_modify</span>，那么就要如此修改配置：</p>
<pre class="brush:html">&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;"&gt;
	&lt;param name="movie" value="jssc5.swf"/&gt;
	&lt;param name="flashvars" value="find=brush_modify&amp;swf=jssc5_modify&amp;js=jssc_js"/&gt;
	&lt;embed src="jssc5.swf" flashvars="find=brush_modify&amp;swf=jssc5_modify&amp;js=jssc_js" name="jssc5_modify" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/&gt;
&lt;/object&gt;</pre>
<p>注意param和embed中要修改2次。</p>
<p>在找到所有符合规则的pre节点之后，js将这些节点的引用存储下来，然后一一遍历它们。下面的经过就是每次遍历一个pre节点时所发生的。</p>
<h3>3.遍历提取内容</h3>
<p>提取pre节点的内容很简单，即我们熟知的<span style="color: #ff0000;">innerText</span>。不过在不同浏览器中也有让人头疼的地方。以前在做jssc4的时候，firefox2版本的<span style="color: #ff0000;">innerText</span>容量有限制，如果代码过多，提取的内容就不完整。另外也有低版本浏览器不支持<span style="color: #ff0000;">innerText</span>的可能性发生。所以综合考虑我写了个如下的js方法：</p>
<pre class="brush:js">function getText(node) {
	var code = node.textContent || node.innerText;
	if(!code &amp;&amp; node.firstChild) {
		code = node.firstChild.nodeValue;
	}
	return code || "";
}</pre>
<h3>4.传递内容给as</h3>
<p>这是as定义好的接口，js和as可以通过<span style="color: #ff0000;">ExternalInterface</span>类来实现互调。不再多说。</p>
<h3>5.as做词法分析</h3>
<p>这是前几篇一直在说的内容，不再多说。</p>
<h3>6.传递结果给js</h3>
<p>同第4点。</p>
<h3>7.js包装结果</h3>
<p>生成的结果其实是一串<span style="color: #ff0000;">li</span>节点，js要做的就是创建一个<span style="color: #ff0000;">ol</span>节点，然后设置<span style="color: #ff0000;">innerHTML</span>为传递来的结果字符串。如此最基本的高亮代码就完成了。之后是设置标题、起始行数、边距、复制功能和鼠标移入高亮当前行背景。</p>
<p>其中边距是指ol节点的<span style="color: #ff0000;">paddingLeft</span>值，因为代码行数不一样，所以左边距一定是根据行数动态进行计算的。在js中我的公式如下：</p>
<pre class="brush:js">oOl.style.paddingLeft = Math.max((line.length + 2) * 9, 30) + "px";</pre>
<h3>8.隐藏pre节点</h3>
<p>最后是将本次循环的这个pre节点隐藏。注意，这里是隐藏不是删除！因为后面还要用到代码复制功能，所以只能隐藏不能删除！</p>
<h3>9.插入结果</h3>
<p>将包装好的结果插入本次循环pre节点的前面，这轮循环就算走完了，再继续下一轮循环。</p>
<h3>结束语</h3>
<p>好了，整个系列文章算结束了。欢迎大家提出好的建议和想法，或者直接参与到开发当中来，亦或以别的方式来参与（比如说做wordpress的插件）。如果你能做出比jssc更好的高亮插件，那么我也会放弃jssc，投入新的、更好的项目中去的——这话说的好假：）</p>
]]></content:encoded>
			<wfw:commentRss>http://army8735.org/2010/02/23/669.html/feed</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
		<item>
		<title>web端语法高亮原理：走进jssc的世界（四）</title>
		<link>http://army8735.org/2010/02/02/608.html</link>
		<comments>http://army8735.org/2010/02/02/608.html#comments</comments>
		<pubDate>Tue, 02 Feb 2010 06:11:39 +0000</pubDate>
		<dc:creator>army8735</dc:creator>
				<category><![CDATA[jssc]]></category>
		<category><![CDATA[系列文章]]></category>
		<category><![CDATA[语法高亮原理]]></category>

		<guid isPermaLink="false">http://army8735.org/?p=608</guid>
		<description><![CDATA[内嵌解析
很容易遇到这样的情况：在需要高亮的代码中还混淆着其它语言种类的代码（最常见的例子为Html内嵌css和js，以下也将以此为例）。这是一件让人头疼的事情，因为无论采用何种方法，内嵌的语言和原本语言的规则一定是不同的。这意味着必须将它们区分开来对待。从这一点出发，自然而然就能引出问题的关键所在——如何区分？
剥离内容
先来考虑最简单的情况：
&#60;head&#62;
&#60;script&#62;var i = 0;&#60;/script&#62;
&#60;/head&#62;
假如没有第2行的script标签以及其内部的代码，那么整个就是纯html代码高亮，这个没有什么难度（如果已经完全理解前三篇的此法分析的话）。然而不凑巧的是，关键点就在于script标签中会出现js代码，html的此法分析中并没有js的词法规则，两者不能等同。那么怎么办呢？
答案是将它们剥离出来。上例是最简单的例子，我们在对html进行此法分析的时候，一旦读到了&#60;script&#62;开始标签，接着便去寻找&#60;/script&#62;结束标签（一般会使用String.indexOf()来查找），然后将标签里面的内容单独提取出来。这是第一步，如果做完的话，此时html的高亮结果应该是：除了js代码没有高亮（即默认颜色）以外，其它的html代码均被正确高亮了。
更复杂的情况
剥离到此还没有结束，因为剥离要考虑其它一些复杂的元素。看以下代码：
&#60;head&#62;
&#60;script&#62;var i = 0;
//&#60;/script&#62;
&#60;/script&#62;
&#60;/head&#62;
代码的第3行中，出现了单行注释，其中有被注释掉的script结束标签。这点需要格外注意。假若使用String.indexOf()来查询script结束标签的话，那么就会在第3行结束。这样就错了，因为第3行实际上是个注释，真正的结束符在第4行。
以此延伸，除了上面的情况以外，引号中的字符串、多行注释、正则里面均会出现类似情况。因此，单纯的String.indexOf()是肯定不行的。我们必须对js代码部分进行预处理。
预处理
在as的解析部分，实际上主要分为两大块：词法分析和存储结果。词法分析即是前面几篇一直在讲解的内容；存储结果即是将分析出来的代码链接起来，说白了就是简单的字符串拼接。
在html中的js代码可能会出现混淆script结束标签的情况之下，唯一解决的办法就是对js也进行简单的词法分析预处理，但不存储结果。因为分析是“读”，而存储是“写”。写的耗时要比读多多了，而且预处理只是为了防止注释、字符串和正则的混淆，不需要真正地进行解析，实现复杂读也比较低。等完全将js代码从html中剥离后，再交给js解析器来做真正的工作，如此也保证了代码的不重复。
&#60;head&#62;
&#60;script&#62;
var i = 0;
//&#60;/script&#62;
"&#60;/script&#62;"
/*&#60;/script&#62;*/
/[&#60;/script&#62;]/
&#60;/script&#62;
&#60;/head&#62;
html中的预处理，至少要保证能将3到7行的js代码完全剥离出来交给js解析器处理，其它html代码则由本身来完成高亮。
状态区分
接下来的难点可能还是在如何在html解析的时候完成区分上面。在这里，我设置了一个state变量用以标识状态。仔细考虑下html，无非发现它主要有以下几种状态：

html节点：即&#60;&#62;内的tag，还有随之的一些属性内容。如：&#60;img width=&#8221;100px&#8221;/&#62;。
text节点：文本内容，段落p中最常见到。
css节点：style中的css代码。
script节点：script中的js代码。

在默认最开始的时候，是文本节点。一旦遇到了左尖括号，并且随后跟的是个正确的节点名（&#60;x&#62;绝对不是个正确的节点，所以不能当成节点来处理），那么就进入节点状态来解析；当节点解析完了之后，返回文本状态。css和script节点是个两个特殊的节点，因为在它们的开始标签结束之后，要进行预处理查找结束标签。实际上会做其中的一个，另外一个也就懂了。
值得注意的是，html标签中有单个类型的存在，比如&#60;br/&#62;，它不需要成对出现，甚至可以写成&#60;br&#62;。省略/的又是另外一种自闭合类型。它们在处理起来有点麻烦，特别是涉及到深度折叠的时候。解决的办法也是设置状态变量，标识当前节点属于那种类型，以此来区分判断。
这篇写得可能有点简单，因为的确是比较抽象的东西。我也偷偷懒，相信能做到前章所提的词法分析的情况下，纯理论来读本篇也不是什么难事了。可能直接读我的源代码反而会更容易些。在下一篇当中，我会介绍as和js的交互以及jssc的大概处理流程，它将作为结束篇章。
]]></description>
			<content:encoded><![CDATA[<h3>内嵌解析</h3>
<p>很容易遇到这样的情况：在需要高亮的代码中还混淆着其它语言种类的代码（最常见的例子为Html内嵌css和js，以下也将以此为例）。这是一件让人头疼的事情，因为无论采用何种方法，内嵌的语言和原本语言的规则一定是不同的。这意味着必须将它们区分开来对待。从这一点出发，自然而然就能引出问题的关键所在——如何区分？</p>
<h3>剥离内容</h3>
<p>先来考虑最简单的情况：</p>
<pre class="brush:html">&lt;head&gt;
&lt;script&gt;var i = 0;&lt;/script&gt;
&lt;/head&gt;</pre>
<p>假如没有第2行的script标签以及其内部的代码，那么整个就是纯html代码高亮，这个没有什么难度（如果已经完全理解前三篇的此法分析的话）。然而不凑巧的是，关键点就在于script标签中会出现js代码，html的此法分析中并没有js的词法规则，两者不能等同。那么怎么办呢？</p>
<p>答案是将它们剥离出来。上例是最简单的例子，我们在对html进行此法分析的时候，一旦读到了&lt;script&gt;开始标签，接着便去寻找&lt;/script&gt;结束标签（一般会使用String.indexOf()来查找），然后将标签里面的内容单独提取出来。这是第一步，如果做完的话，此时html的高亮结果应该是：除了js代码没有高亮（即默认颜色）以外，其它的html代码均被正确高亮了。</p>
<h3>更复杂的情况</h3>
<p>剥离到此还没有结束，因为剥离要考虑其它一些复杂的元素。看以下代码：</p>
<pre class="brush:html">&lt;head&gt;
&lt;script&gt;var i = 0;
//&lt;/script&gt;
&lt;/script&gt;
&lt;/head&gt;</pre>
<p>代码的第3行中，出现了单行注释，其中有被注释掉的script结束标签。这点需要格外注意。假若使用String.indexOf()来查询script结束标签的话，那么就会在第3行结束。这样就错了，因为第3行实际上是个注释，真正的结束符在第4行。</p>
<p>以此延伸，除了上面的情况以外，引号中的字符串、多行注释、正则里面均会出现类似情况。因此，单纯的String.indexOf()是肯定不行的。我们必须对js代码部分进行预处理。</p>
<h3>预处理</h3>
<p>在as的解析部分，实际上主要分为两大块：词法分析和存储结果。词法分析即是前面几篇一直在讲解的内容；存储结果即是将分析出来的代码链接起来，说白了就是简单的字符串拼接。</p>
<p>在html中的js代码可能会出现混淆script结束标签的情况之下，唯一解决的办法就是对js也进行<strong>简单的词法分析预处理</strong>，但不存储结果。因为分析是“读”，而存储是“写”。写的耗时要比读多多了，而且预处理只是为了防止注释、字符串和正则的混淆，不需要真正地进行解析，实现复杂读也比较低。等完全将js代码从html中剥离后，再交给js解析器来做真正的工作，如此也保证了代码的不重复。</p>
<pre class="brush:html">&lt;head&gt;
&lt;script&gt;
var i = 0;
//&lt;/script&gt;
"&lt;/script&gt;"
/*&lt;/script&gt;*/
/[&lt;/script&gt;]/
&lt;/script&gt;
&lt;/head&gt;</pre>
<p>html中的预处理，至少要保证能将3到7行的js代码完全剥离出来交给js解析器处理，其它html代码则由本身来完成高亮。</p>
<h3>状态区分</h3>
<p>接下来的难点可能还是在如何在html解析的时候完成区分上面。在这里，我设置了一个state变量用以标识状态。仔细考虑下html，无非发现它主要有以下几种状态：</p>
<ol>
<li>html节点：即&lt;&gt;内的tag，还有随之的一些属性内容。如：&lt;img width=&#8221;100px&#8221;/&gt;。</li>
<li>text节点：文本内容，段落p中最常见到。</li>
<li>css节点：style中的css代码。</li>
<li>script节点：script中的js代码。</li>
</ol>
<p>在默认最开始的时候，是文本节点。一旦遇到了左尖括号，并且随后跟的是个正确的节点名（&lt;x&gt;绝对不是个正确的节点，所以不能当成节点来处理），那么就进入节点状态来解析；当节点解析完了之后，返回文本状态。css和script节点是个两个特殊的节点，因为在它们的开始标签结束之后，要进行预处理查找结束标签。实际上会做其中的一个，另外一个也就懂了。</p>
<p>值得注意的是，html标签中有单个类型的存在，比如&lt;br/&gt;，它不需要成对出现，甚至可以写成&lt;br&gt;。省略/的又是另外一种自闭合类型。它们在处理起来有点麻烦，特别是涉及到深度折叠的时候。解决的办法也是设置状态变量，标识当前节点属于那种类型，以此来区分判断。</p>
<p>这篇写得可能有点简单，因为的确是比较抽象的东西。我也偷偷懒，相信能做到前章所提的词法分析的情况下，纯理论来读本篇也不是什么难事了。可能直接读我的源代码反而会更容易些。在<a href="http://army8735.org/2010/02/23/669.html" target="_blank">下一篇</a>当中，我会介绍as和js的交互以及jssc的大概处理流程，它将作为结束篇章。</p>
]]></content:encoded>
			<wfw:commentRss>http://army8735.org/2010/02/02/608.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>web端语法高亮原理：走进jssc的世界（三）</title>
		<link>http://army8735.org/2009/11/13/390.html</link>
		<comments>http://army8735.org/2009/11/13/390.html#comments</comments>
		<pubDate>Fri, 13 Nov 2009 02:55:58 +0000</pubDate>
		<dc:creator>army8735</dc:creator>
				<category><![CDATA[jssc]]></category>
		<category><![CDATA[系列文章]]></category>
		<category><![CDATA[语法高亮原理]]></category>

		<guid isPermaLink="false">http://www.army8735.org/?p=390</guid>
		<description><![CDATA[区分正则
想要高亮正则表达式的前提是区分它。由于正则表达式以/开头，因此这和除法、单行注释、多行注释会产生混淆。那么究竟该如何辨别呢？

注释的区别最为简单，因为注释拥有最高优先级。除非注释符出现在字符串内，否则一旦开始，编译器便立刻识别//和/*。
如图所示，当向前看字符为斜线时，根据规则由初始状态0进入状态1。如果接下来读入的字符是*，则进入状态2，并成为多行注释；倘若是/，则进入状态3，变成单行注释；而其它的情况么，则是正则除法皆有可能。
我们不妨设想一下：正则和除法有哪些区别？从语境上说，原则性的区别就是除法的前面是被除数，正则的前面不是。而成为被除数的可能无非是以下3种：数字、变量、括号内的运算结果。我不知道js解析引擎是如何做的，它可能相当得严谨（也简单，因为它会忽略空白和注释）。但是在web端语法高亮的需求中，由于假设输入的代码一定是正确的，所以可以“投机取巧”般地识别二者：
//检查当前除号是否是perl风格正则表达式开头
protected function isPerlReg():Boolean {
	for (var j:int = index - 3; j &#62; -1; j--) {
		//先要忽略之前的单行注释
		if (j == singleCommentEnd) {
			j =...]]></description>
			<content:encoded><![CDATA[<h3>区分正则</h3>
<p>想要高亮正则表达式的前提是区分它。由于正则表达式以<span style="color: #0000ff;">/</span>开头，因此这和除法、单行注释、多行注释会产生混淆。那么究竟该如何辨别呢？</p>
<p><img class="aligncenter size-full wp-image-391" title="jssc-article-3-1" src="http://www.army8735.org/wp-content/uploads/2009/11/jssc-article-3-1.gif" alt="jssc-article-3-1" width="284" height="219" /></p>
<p>注释的区别最为简单，因为注释拥有<strong>最高优先级</strong>。除非注释符出现在字符串内，否则一旦开始，编译器便立刻识别<span style="color: #0000ff;">//</span>和<span style="color: #0000ff;">/*</span>。</p>
<p>如图所示，当向前看字符为斜线时，根据规则由初始状态0进入状态1。如果接下来读入的字符是<span style="color: #0000ff;">*</span>，则进入状态2，并成为多行注释；倘若是<span style="color: #0000ff;">/</span>，则进入状态3，变成单行注释；而其它的情况么，则是正则除法皆有可能。</p>
<p>我们不妨设想一下：正则和除法有哪些区别？从语境上说，原则性的区别就是除法的前面是被除数，正则的前面不是。而成为被除数的可能无非是以下3种：数字、变量、括号内的运算结果。我不知道js解析引擎是如何做的，它可能相当得严谨（也简单，因为它会忽略空白和注释）。但是在web端语法高亮的需求中，由于假设输入的代码一定是正确的，所以可以“投机取巧”般地识别二者：</p>
<pre class="brush:as">//检查当前除号是否是perl风格正则表达式开头
protected function isPerlReg():Boolean {
	for (var j:int = index - 3; j &gt; -1; j--) {
		//先要忽略之前的单行注释
		if (j == singleCommentEnd) {
			j = singleCommentStart;
			continue;
		}
		var cc:int = code.charCodeAt(j);
		//忽略空白
		if (Character.isBlank(cc) || Character.isLine(cc)) {
			continue;
		}
		//前面也是一个除号的话防止是多行结尾注释，需跳过注释段继续向前找
		else if (Character.isSlash(cc) &amp;&amp; Character.isStar(code.charCodeAt(j - 1))) {
			j = code.lastIndexOf("/*", j - 1);
		}
		//前面一个非空白字符若是字母数字下划线则为正则，否则除号
		else if (Character.isLetterOrDigit(cc) || Character.isUnderline(cc) || Character.isDollar(cc) || Character.isRightParenthese(cc)) {
			return false;
		}
		else {
			break;
		}
	}
	return true;
}</pre>
<p>程序开始<strong>回溯</strong>，我们要看在斜线符号之前的“东西”到底是什么——当然，空白符是不算在内的，因此循环体内首先要做的就是忽略空白。假如前面是标识符的组成或者右括号的话，那么显然它是个被除数，斜线也应该被解读为除号；而如果是其它情况的话，斜线则是正则表达式的开头。</p>
<p>除此外还有个很大的难点，就是斜线前面插入了一段注释，这也需要忽略（js引擎无需考虑这些，因为一开始注释就被剔除了，所以它会相对简单一些）。目前jssc 5 beta 3版仅能识别出多行注释，对于单行注释尚未解决，这是下个版本需要注意的问题。</p>
<blockquote><p>注：新版本已从语法层级上解决，方法为设置一个布尔标量，每次处理token时切换其正确状态，上述代码在新版本中已废弃。</p></blockquote>
<h3>识别正则</h3>
<p>区分出来之后别是识别正则，根据特点我们画出这样的DFA：</p>
<p><img class="aligncenter size-full wp-image-401" title="jssc-article-3-2" src="http://www.army8735.org/wp-content/uploads/2009/11/jssc-article-3-2.gif" alt="jssc-article-3-2" width="365" height="237" /></p>
<p>我们假设当前<span style="color: #0000ff;">/</span>已经被识别为正则表达式的开头，那么接下来的任务便是寻找另外一个结尾<span style="color: #0000ff;">/</span>。</p>
<p>在状态1中首先要明确对待的就是转义符，因为它会转义掉下面一个符号，所以要单独为其区分出一个状态2。而状态2上的条件就很宽松了：无论输入什么都会被转义掉，因此遇到任何字符（any）都会回到状态1。</p>
<p>值得注意的是，字符集（<span style="color: #0000ff;">[]</span>）是个特殊之处，因为它里面可以省略转义符（既可以有也可以没有），为此需要画出状态3和状态4来对待它（字符集中出现未被转义的<span style="color: #0000ff;">/</span>也是合法的，你不可能把它当作正则表达式的结束符）。</p>
<p>当真正出现结束符后进入状态5，此时正则并没有完全结束，因为后面还可能跟有譬如<span style="color: #0000ff;">i</span>这样的<strong>flag</strong>。在状态5上多加种条件循环会本身便可解决问题（更严谨的做法应该是flag仅允许出现一次）；而其它情况才是最终进入终结状态6。</p>
<pre class="brush:as">//perl正则
protected function dealPerlReg():void {
	var start:int = index - 2;
	var cc:int;
	outer:
	while (index &lt;= code.length) {
		readch();
		cc = peek.charCodeAt(0);
		//转义符
		if (Character.isBackslash(cc)) {
			readch();
			//后面是换行跳出
			if (Character.isLine(peek.charCodeAt(0))) {
				break;
			}
		}
		//[括号
		else if (Character.isLeftbracket(cc)) {
			while (index &lt;= code.length) {
				readch();
				cc = peek.charCodeAt(0);
				//转义符
				if (Character.isBackslash(cc)) {
					readch();
					if (Character.isLine(peek.charCodeAt(0))) {
						break outer;
					}
				}
				//]括号
				else if (Character.isRightbracket(cc)) {
					continue outer;
				}
			}
		}
		//行末尾
		else if (Character.isLine(cc)) {
			break;
		}
		//正则表达式/结束
		else if (Character.isSlash(cc)) {
			for (var i:int = 0; i &lt; 3 &amp;&amp; index &lt;= code.length; i++) {
				readch();
				cc = peek.charCodeAt(0);
				//是img中的一个，存入flag中，不分大小写
				if(cc == 103 || cc == 105 || cc == 109 || cc == 71 || cc == 73 || cc == 77) {
					//
				}
				//其它情况跳出
				else {
					break outer;
				}
			}
			break;
		}
	}
	//高亮
	result.append(HighLighter.regular(HtmlEscape.encode(code.slice(start, index - 1))));
}</pre>
<h3>其它</h3>
<p>其它符号的处理都很简单，不做任何高亮即可，只是要注意个别需要特殊编码的字符（如&amp;变为&amp;amp;）。在编译器的词法分析中，每个符号都是有语法意义的，有时符号的长度还不止一个（&lt;=就有2个符号）。幸运的是，在做语法高亮的情况下，这些全可以被“偷懒”掉。</p>
<p>下面贴一下c系列语言处理符号的代码，它相当得简单，可能唯一特殊之处就是其中对花括弧的深度处理了。</p>
<pre class="brush:as">//抽象类中的处理符号方法
protected function dealSign():void {
	result.append(HtmlEscape.encodeChar(peek));
	readch();
}

//c系列语言继承后覆盖的方法，处理符号同时要计算深度
protected override function dealSign():void {
	var cc:int = peek.charCodeAt(0);
	if (Character.isLeftBrace(cc)) {
		depth++;
	}
	else if (Character.isRightBrace(cc)) {
		depth--;
	}
	super.dealSign();
}</pre>
<p>在<a href="http://army8735.org/2010/02/02/608.html">下一篇</a>中，我将讲述jssc 5是如何做到嵌套高亮（html中内嵌css和js）的。</p>
]]></content:encoded>
			<wfw:commentRss>http://army8735.org/2009/11/13/390.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>web端语法高亮原理：走进jssc的世界（二）</title>
		<link>http://army8735.org/2009/11/06/329.html</link>
		<comments>http://army8735.org/2009/11/06/329.html#comments</comments>
		<pubDate>Fri, 06 Nov 2009 02:29:12 +0000</pubDate>
		<dc:creator>army8735</dc:creator>
				<category><![CDATA[jssc]]></category>
		<category><![CDATA[系列文章]]></category>
		<category><![CDATA[语法高亮原理]]></category>

		<guid isPermaLink="false">http://www.army8735.org/?p=329</guid>
		<description><![CDATA[有穷自动机（状态图）
处理词法分析的关键在于建立正确的DFA（确定的有穷自动机），而DFA是如何由NFA（不确定的有穷自动机）构造而来，NFA又是如何从正则表达式演变，不在本文讨论范围之内。我们只需要关心如何去画好这个状态图就行了。
先从最简单的开始。现在来了一串源码，我们的解析器想要识别出关键字if，那么该怎么办呢？下面是它的状态图：

我们把每个圈称作一个状态，圈内的数字用以标识不同的状态，而双边的圈则是终结状态。这个状态图没画完，因为状态4和状态5不是终结状态，却没有转换规则。这不是主要讨论。
一般情况下，状态0被称之为初始状态。解析器会设置一个向前看的哨兵，从左到右遍历源码。假设当前字符是i，很显然，它就是状态0可接受的唯一字符。于是开始执行转换规则，状态0根据规则转换到状态1，继续读入下一个字符。
状态1可接受的字符有2种：f和other。这里other不是说字符串，而是指除f以外的所有字符。假如是other的情况，显然它不是我们想要的关键字if，所以转而进入状态4，进行其它处理——至于处理什么、如何处理，暂时不需要关心，后面会说到；而如果是字符f，这就符合我们的预期，状态1通过规则转换到状态2。
可能你认为已经结束了，但事实并非这么简单。if两个字母是读出了，但却未必是关键字，后面如果跟着一个字母s呢？ifs，它可能是定义的一个变量名，又可能是个方法名。然而不管是什么，总归不是关键字。所以状态2接受2种字符：数字、字母、下划线（js应该还有美元符号$，因为它也可作为变量名组成），这条路通往状态5，我们不管；其它的情况就好办了，空白也好回车也好括号也好，它一定是if关键字，由此进入状态3。
上面说了两个圈儿的是终结状态，识别出if关键字后，状态3就结束了。此时解析器会进行回退，把if后面读入的向前看字符放回源码流中，继续下一次循环。经过这一轮番的解释，相信你能对词法分析有了大概的印象吧。同理，其它的关键字、数字、字符串、注释……也是如此识别的。
或许有人要问：一种语言的关键字那么多，为每个关键字写处理程序岂不是要累死？没错，事实上编译器也不会这样做。上面那个例子只是为了简单起见所举，真正可行的办法是只建立标识符的DFA，将所有标识符识别出来，然后一一对比，看此标识符是否是个关键字。
识别标识符

这是识别js语言标识符的状态图。不难看出，状态3和4都是可能的终结状态（实际上基于最小化DFA数量的原则，它们应该被合并为一个）。在初始状态0时，接受字母、下划线和美元符号，如果读入的向前看字符是的话，那么进入状态1。
状态1接受2种情况：字母、数字、下划线和美元符号，很明显这是js标识符的组成规定。这种情况下会进入状态2，并且状态2也接受同样的情况返回本身状态（标识符的长度是不定的，因此状态2会循环本身），它上面的弧线即表达了这个意思。只有当状态2时读入的向前看字符不是字母、数字、下划线或美元符号任一种的时候，才会进入终结状态3。此时这个标识符也识别出来了，和前面一样，回退继续处理。
状态1还有个接受other的情况，这是当标识符仅由一个字符组成时才会发生的。
以下是jssc中处理ecmascript语言中标识符的源代码：
protected function dealWord():void {
	var start:int = index - 1;
	var cc:int;
	//直到不是字母数字下划线美元符号为止
	while (index &#60;= code.length) {
		readch();
		cc = peek.charCodeAt(0);
		if (Character.isLetterOrDigit(cc) &#124;&#124; Character.isUnderline(cc) &#124;&#124;...]]></description>
			<content:encoded><![CDATA[<h3>有穷自动机（状态图）</h3>
<p>处理词法分析的关键在于建立正确的DFA（确定的有穷自动机），而DFA是如何由NFA（不确定的有穷自动机）构造而来，NFA又是如何从正则表达式演变，不在本文讨论范围之内。我们只需要关心如何去画好这个状态图就行了。</p>
<p>先从最简单的开始。现在来了一串源码，我们的解析器想要识别出关键字<span style="color: #0000ff;">if</span>，那么该怎么办呢？下面是它的状态图：</p>
<p style="text-align: center;"><img class="size-full wp-image-332  aligncenter" title="jssc-article-2-1" src="http://www.army8735.org/wp-content/uploads/2009/11/jssc-article-2-1.gif" alt="jssc-article-2-1" width="342" height="151" /></p>
<p>我们把每个圈称作一个<strong>状态</strong>，圈内的数字用以标识不同的状态，而双边的圈则是<strong>终结状态</strong>。这个状态图没画完，因为状态4和状态5不是终结状态，却没有转换规则。这不是主要讨论。</p>
<p>一般情况下，状态0被称之为初始状态。解析器会设置一个<strong>向前看</strong>的哨兵，从左到右遍历源码。假设当前字符是<span style="color: #0000ff;">i</span>，很显然，它就是状态0可接受的唯一字符。于是开始执行转换规则，状态0根据规则转换到状态1，继续读入下一个字符。</p>
<p>状态1可接受的字符有2种：<span style="color: #0000ff;">f</span>和<span style="color: #0000ff;">other</span>。这里other不是说字符串，而是指除f以外的所有字符。假如是other的情况，显然它不是我们想要的关键字if，所以转而进入状态4，进行其它处理——至于处理什么、如何处理，暂时不需要关心，后面会说到；而如果是字符f，这就符合我们的预期，状态1通过规则转换到状态2。</p>
<p>可能你认为已经结束了，但事实并非这么简单。if两个字母是读出了，但却未必是关键字，后面如果跟着一个字母s呢？ifs，它可能是定义的一个变量名，又可能是个方法名。然而不管是什么，总归不是关键字。所以状态2接受2种字符：数字、字母、下划线（js应该还有美元符号$，因为它也可作为变量名组成），这条路通往状态5，我们不管；其它的情况就好办了，空白也好回车也好括号也好，它一定是if关键字，由此进入状态3。</p>
<p>上面说了两个圈儿的是终结状态，识别出if关键字后，状态3就结束了。此时解析器会进行<strong>回退</strong>，把if后面读入的向前看字符放回源码流中，继续下一次循环。经过这一轮番的解释，相信你能对词法分析有了大概的印象吧。同理，其它的关键字、数字、字符串、注释……也是如此识别的。</p>
<p>或许有人要问：一种语言的关键字那么多，为每个关键字写处理程序岂不是要累死？没错，事实上编译器也不会这样做。上面那个例子只是为了简单起见所举，真正可行的办法是只建立标识符的DFA，将所有标识符识别出来，然后一一对比，看此标识符是否是个关键字。</p>
<h3>识别标识符</h3>
<p><img class="aligncenter size-full wp-image-335" title="jssc-article-2-2" src="http://www.army8735.org/wp-content/uploads/2009/11/jssc-article-2-2.gif" alt="jssc-article-2-2" width="360" height="204" /></p>
<p>这是识别js语言标识符的状态图。不难看出，状态3和4都是可能的终结状态（实际上基于最小化DFA数量的原则，它们应该被合并为一个）。在初始状态0时，接受字母、下划线和美元符号，如果读入的向前看字符是的话，那么进入状态1。</p>
<p>状态1接受2种情况：字母、数字、下划线和美元符号，很明显这是js标识符的组成规定。这种情况下会进入状态2，并且状态2也接受同样的情况返回本身状态（标识符的长度是不定的，因此状态2会循环本身），它上面的弧线即表达了这个意思。只有当状态2时读入的向前看字符不是字母、数字、下划线或美元符号任一种的时候，才会进入终结状态3。此时这个标识符也识别出来了，和前面一样，回退继续处理。</p>
<p>状态1还有个接受other的情况，这是当标识符仅由一个字符组成时才会发生的。</p>
<p>以下是jssc中处理ecmascript语言中标识符的源代码：</p>
<pre class="brush:as">protected function dealWord():void {
	var start:int = index - 1;
	var cc:int;
	//直到不是字母数字下划线美元符号为止
	while (index &lt;= code.length) {
		readch();
		cc = peek.charCodeAt(0);
		if (Character.isLetterOrDigit(cc) || Character.isUnderline(cc) || Character.isDollar(cc)) {
			//
		}
		else {
			break;
		}
	}
	//高亮
	var res:String = code.slice(start, index - 1);
	if (words.hasKey(res)) {
		res = HighLighter.keyword(res);
	}
	result.append(res);
}</pre>
<p>由于语法高亮毕竟还是有别于编译器的词法处理，所以为了简化功能和提高性能，很多地方还是不一样的。比如说上面代码中的index是个int型索引，用以保存向前看字符的下标，而peek则是当前的向前看字符。start保存着状态0时向前看字符的位置。循环体中不停地读入下一个字符（readch()方法），直到不是字母、数字、下划线或美元符号为止——这就是所谓的进入终结状态3——然后截取代码中从start到当前索引的代码。</p>
<p>之后的高亮处理先检查这段代码是否是保留字（words是个<strong>HashMap</strong>，预先保存着所有关键字），是的话高亮上颜色，不是则原封不动。</p>
<h3>识别字符串</h3>
<p><img class="aligncenter size-full wp-image-351" title="jssc-article-2-3" src="http://www.army8735.org/wp-content/uploads/2009/11/jssc-article-2-3.gif" alt="jssc-article-2-3" width="263" height="151" /></p>
<p>字符串的处理相对简单一些。js中单引号和双引号作用完全一样，所以用双引号来举例足够了。初始状态0，遇到引号后进入状态1。</p>
<p>在状态1中稍微有点绕人，如果遇到另外一个引号，那么自然这个字符串就结束了，进入终结状态2。但是要预防引号被转义的情况出现，所以当读到的向前看字符是<span style="color: #0000ff;">\</span>时，我们进入状态4，接着读入的任何字符都是被转义掉的，再返回状态1。最后需要注意的是弧线other，它依然回到状态1自己，这样就处理了不定长度的字符串格式。</p>
<p>插嘴一句：在windows的IE中，字符串跨行时所出现的可能不是标准的<span style="color: #0000ff;">\n</span>，而是<span style="color: #0000ff;">\r\n</span>，这在处理过程中会遇到麻烦。jssc 5对其多添加了一个状态来区分，但是更好的做法应该是在开头就把所有\r过滤掉。这可能稍微影响一点点性能。</p>
<pre class="brush:as">//字符串处理，单引号或多引号、是否允许转义折行
protected function dealString(char:String = "\"", wrap:Boolean = false):void {
	var start:int = index - 1;
	var cc:int;
	while (index &lt;= code.length) {
		readch();
		cc = peek.charCodeAt(0);
		//转义符
		if (Character.isBackslash(cc)) {
			readch();
			//转义符后是回车继续读入下一个
			if (Character.isEnter(peek.charCodeAt(0))) {
				readch();
			}
			//不允许转义换行跳出
			if (!wrap &amp;&amp; Character.isLine(peek.charCodeAt(0))) {
				break;
			}
		}
		//行末尾未转义换行或找到结束跳出
		else if (Character.isLine(cc) || char == peek) {
			break;
		}
	}
	//高亮
	result.append(HighLighter.string(HtmlEscape.encode(code.slice(start, index), HighLighter.endTag + getNewLine() + HighLighter.stringStart())));
	readch();
}</pre>
<p>我想它很明了，无需多做解释。只是要注意最后高亮步骤中要将换行符给替换掉，变成html可显示的换行。</p>
<h3>识别注释</h3>
<p>本来注释在词法分析中是被扔掉的部分，但在语法高亮中却不能这样做。注释的识别和字符串一样简单：多行注释和字符串类似，以<span style="color: #0000ff;">/*</span>开头，<span style="color: #0000ff;">*/</span>结尾，它让人省心的地方就是不存在转义问题；而单行注释也一样，<span style="color: #0000ff;">//</span>开头直接到行末尾。这里直接附上源码，状态图就懒地画了。</p>
<pre class="brush:as">//处理单行注释
protected function dealSingleComment():void {
	var i:int = code.indexOf("\n", index);
	index -= 2;
	//找不到换行符说明是最后一行
	if (i == -1) {
		i = code.length;
	}
	//本行存入
	result.append(HighLighter.comment(HtmlEscape.encode(code.slice(index, i))));
	index = i;
	readch();
}
//处理多行注释
protected function dealMultiComment():void {
	depth++;
	var i:int = code.indexOf("*/", index);
	index -= 2;
	//i为-1时直接注释到结尾
	if (i == -1) {
		i = code.length;
	}
	else {
		i += 2;
	}
	result.append(HighLighter.comment(HtmlEscape.encode(code.slice(index, i), HighLighter.endTag + getNewLine() + HighLighter.commentStart())));
	//调整索引和深度，并读取当前peek
	index = i;
	depth--;
	readch();
}</pre>
<h3>识别数字</h3>
<p>数字可能是最难处理的部分之一了，因为它包含小数。理想的方式是将整数和小数分开处理，jssc 5选择了比较复杂的方式——统一对待。不仅如此，我抽象出来一个方法，用它来尽可能地处理所有语言的数字。在java中，长整型可以以<span style="color: #0000ff;">L</span>结尾，浮点型可以以<span style="color: #0000ff;">D</span>或<span style="color: #0000ff;">F</span>结尾。这个问题想要统一解决也是相当得麻烦，所以建议大家不要像我一样，为节省并不起眼的程序大小而导致维护困难。</p>
<p><img class="aligncenter size-full wp-image-367" title="jssc-article-2-4" src="http://www.army8735.org/wp-content/uploads/2009/11/jssc-article-2-4.gif" alt="jssc-article-2-4" width="660" height="204" /></p>
<p>这是十进制数字的状态图，而js语言中E后面的+号还可以省略，这点需要额外注意（图上并没有显示出来）。下面是源码：</p>
<pre class="brush:as">//处理数字
protected function dealNumber():void {
	var cc:int = peek.charCodeAt(0);
	var start:int = index - 1, i:int = 0;
	var res:String;
	//以0开头需判断是否16进制
	if (Character.isZero(cc)) {
		readch();
		cc = peek.charCodeAt(0);
		//0后面是x或者X为16进制
		if (Character.isX(cc)) {
			readch();
			//寻找第一个非16进制字符，同时最大长度不能超过6（不包括开头的0x）
			for ( ; i &lt; 6 &amp;&amp; index &lt; code.length; i++) {
				if (!Character.isDigit16(peek.charCodeAt(0))) {
					break;
				}
				readch();
			}
			//高亮
			result.append(HighLighter.number(code.slice(start, index - 1)));
			return;
		}
		//其它非数字退出且不是小数点
		else if (!Character.isDigit(cc) &amp;&amp; !Character.isDecimal(cc)) {
			result.append(HighLighter.number("0"));
			return;
		}
	}
	//先处理整数部分
	while (Character.isDigit(peek.charCodeAt(0))) {
		readch();
	}
	cc = peek.charCodeAt(0);
	//整数后可能跟的类型L字母
	if (Character.isLong(cc)) {
		result.append(HighLighter.number(code.slice(start, index)));
		readch();
		return;
	}
	//小数部分
	else if (Character.isDecimal(cc)) {
		readch();
		while (Character.isDigit(peek.charCodeAt(0))) {
			readch();
		}
		//小数后可能跟的类型字母D、F
		if (Character.isFloat(peek.charCodeAt(0))) {
			readch();
			res = code.slice(start, index - 1);
			//防止.f出现
			if (res.length &gt; 2) {
				res = HighLighter.number(res);
			}
			result.append(res);
			return;
		}
	}
	cc = peek.charCodeAt(0);
	//指数E
	if (Character.isExponent(peek.charCodeAt(0))) {
		readch();
		cc = peek.charCodeAt(0);
		//+-号
		if (Character.isAdd(cc) || Character.isMinus(cc)) {
			readch();
		}
		//指数后面的数字
		while (Character.isDigit(peek.charCodeAt(0))) {
			readch();
		}
	}
	//高亮
	res = code.slice(start, index - 1);
	//防止.e之类的出现，即第一个字符是小数点的情况下判断第二个字符是否非数字
	if (!Character.isDecimal(res.charCodeAt(0)) &amp;&amp; !Character.isLetter(res.charCodeAt(1))) {
		res = HighLighter.number(res);
	}
	result.append(res);
}</pre>
<h3>其它</h3>
<p>空白符的处理没什么难度，它不需要被高亮。而js中处理折叠主要是针对符号<span style="color: #0000ff;">{</span>和<span style="color: #0000ff;">}</span>。我设置了一个叫做<span style="color: #0000ff;">depth</span>的变量，用以保存深度（使用firebug的用户可以看到代码行li节点上的name属性，那就是深度变量）。每当遇到<span style="color: #0000ff;">{</span>时深度自增；而遇到<span style="color: #0000ff;">}</span>时深度自减。这样就做到了折叠功能——点击每行向下遍历，折叠掉深度比它大的行，遇到相等或小于的时候中断遍历。</p>
<p>至于难点中的难点——区分除号和正则，以及正则的状态图处理，这是<a href="http://www.army8735.org/2009/11/13/390.html" target="_blank">下一篇</a>要重点讲述的地方。</p>
]]></content:encoded>
			<wfw:commentRss>http://army8735.org/2009/11/06/329.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>web端语法高亮原理：走进jssc的世界（一）</title>
		<link>http://army8735.org/2009/11/03/251.html</link>
		<comments>http://army8735.org/2009/11/03/251.html#comments</comments>
		<pubDate>Tue, 03 Nov 2009 02:12:41 +0000</pubDate>
		<dc:creator>army8735</dc:creator>
				<category><![CDATA[jssc]]></category>
		<category><![CDATA[系列文章]]></category>
		<category><![CDATA[语法高亮原理]]></category>

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

		<guid isPermaLink="false">http://www.army8735.org/?p=236</guid>
		<description><![CDATA[web端语法高亮器到底是什么时候开始流行的？
SyntaxHighlighter发表于2007年；SHJS的网站上写着copyright © 2007的字样；google-code-prettify的开源项目主页，最早的反馈亦是Mar 2007；就连jssc的雏形也是出生在2007年初的一堂《编译原理》实验课上。这一年，似乎成为高亮web代码的热潮期。
然而，一切仅仅是开始。随着Yahoo官方采用sh（SyntaxHighlighter），用js编写的它一夜成名。sh的确是目前所有已知web端语法高亮中最出色的一个，许多网站都在使用这家伙，它的地位可以称得上是霸主。不过，开源世界的代码永远是竞争激烈的，其它高亮器如雨后春笋般诞生，互相之间无不在攀比性能、功用、大小等等。记得在jssc 2发表的时候，还是个学生的我就把矛头直指sh，意欲一较高下。当然，结果就是另外一回事了。
时至今日，jssc历经5个版本，各方面都已发展至成熟。然而技术推动却一直只有我一个人，各种因素都有，技术门槛肯定是最重要的一个。于是，我决定开写一个系列文章来介绍web端语法高亮原理——不仅仅是帮助jssc的发展，更是为了共享经验、推动web端高亮技术的进步。本篇文章就是作为序言而写的。
以上即是简单的概念介绍，下面是系列文章的目录，链接不定时更新：

web端语法高亮原理：走进jssc的世界（一）
web端语法高亮原理：走进jssc的世界（二）
web端语法高亮原理：走进jssc的世界（三）
web端语法高亮原理：走进jssc的世界（四）
web端语法高亮原理：走进jssc的世界（五）

]]></description>
			<content:encoded><![CDATA[<p>web端语法高亮器到底是什么时候开始流行的？</p>
<p><a href="http://alexgorbatchev.com/wiki/SyntaxHighlighter" target="_blank">SyntaxHighlighter</a>发表于2007年；<a href="http://shjs.sourceforge.net/" target="_blank">SHJS</a>的网站上写着copyright © 2007的字样；<a href="http://code.google.com/p/google-code-prettify/" target="_blank">google-code-prettify</a>的开源项目主页，最早的反馈亦是Mar 2007；就连<a href="http://code.google.com/p/jssc/" target="_blank">jssc</a>的雏形也是出生在2007年初的一堂《编译原理》实验课上。这一年，似乎成为高亮web代码的热潮期。</p>
<p>然而，一切仅仅是开始。随着Yahoo官方采用sh（SyntaxHighlighter），用js编写的它一夜成名。sh的确是目前所有已知web端语法高亮中最出色的一个，许多网站都在使用这家伙，它的地位可以称得上是霸主。不过，开源世界的代码永远是竞争激烈的，其它高亮器如雨后春笋般诞生，互相之间无不在攀比性能、功用、大小等等。记得在jssc 2发表的时候，还是个学生的我就把矛头直指sh，意欲一较高下。当然，结果就是另外一回事了。</p>
<p>时至今日，jssc历经5个版本，各方面都已发展至成熟。然而技术推动却一直只有我一个人，各种因素都有，技术门槛肯定是最重要的一个。于是，我决定开写一个系列文章来介绍web端语法高亮原理——不仅仅是帮助jssc的发展，更是为了共享经验、推动web端高亮技术的进步。本篇文章就是作为序言而写的。</p>
<p>以上即是简单的概念介绍，下面是系列文章的目录，链接不定时更新：</p>
<ol>
<li><a href="http://www.army8735.org/2009/11/03/251.html" target="_blank">web端语法高亮原理：走进jssc的世界（一）</a></li>
<li><a href="http://www.army8735.org/2009/11/06/329.html" target="_blank">web端语法高亮原理：走进jssc的世界（二）</a></li>
<li><a href="http://www.army8735.org/2009/11/13/390.html" target="_blank">web端语法高亮原理：走进jssc的世界（三）</a></li>
<li><a href="http://army8735.org/2010/02/02/608.html" target="_blank">web端语法高亮原理：走进jssc的世界（四）</a></li>
<li><a href="http://army8735.org/2010/02/23/669.html" target="_blank">web端语法高亮原理：走进jssc的世界（五）</a></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://army8735.org/2009/11/02/236.html/feed</wfw:commentRss>
		<slash:comments>26</slash:comments>
		</item>
	</channel>
</rss>

