预览:http://jssc.googlecode.com/svn/trunk/jssc5/bin/index.html
下载:http://code.google.com/p/jssc/downloads/list
源码:http://jssc.googlecode.com/svn/trunk/jssc5/src/
好久不动,都生疏了。离上次5.1alpha版有好几个月了,今天释出beta版,添加了Python语法。
之前说过5.1版本最大的变动是使得添加新的语法变得更加容易,老的5.0版每个新语言都得写个词法分析器太累了,于是乎花大精力修改这一部分,添加了“匹配模式”类,用以方便添加新语法解析。
匹配模式的概念
简单地说,匹配模式就是描述某个语言基本词法单元(Token)的规则。以javascript举例,需要高亮的Token大致有注释、字符串、正则表达式、数字、关键字。其中关键字属于Word中的保留字。注释的匹配模式就是以/*开头*/结尾的代码、字符串是指引号之间的内容等等。
现有的几种匹配模式
所有匹配模式都实现IMatch接口,我目前定义了以下几种:CharacterSet、CompleteEqual、LinearSearch、LinearParse、IDMatch、RegularMatch。
CharacterSet 是个字符集匹配,它定义了Token以什么字符集为开头并以什么字符集为组成内容。比如js中的Word是以下划线、美元符号、字母为开头,下划线、美元符号、字母和数字为组成内容。出于初期设计的原因,字符集不能完全自定义,只能使用已经定义的若干常量。这个缺陷会在以后考虑。
CompleteEqual 是最简单的全等匹配,只有完全相等时才会匹配成功。在js中我用全等来查找花括号和圆括号,因为它们要参与计算深度(折叠功能)。
LinearSearch 线性查找,寻找以什么字符串为开头和结尾的内容。js中注释是以此实现的。注释是以/*开头*/结尾、或者//开头行末结尾。
LinearParse 线性分析和线性查找很像,只是它多了判断转义的逻辑,性能稍稍差一些。js中字符串是以此实现的。字符串以引号开始引号结束,但是结尾的引号可能存在转义的情况,字符串中也可能出现转义符,需要特殊对待。
IDMatch ID匹配和线性查找也有点相似,它定义了以什么字符串为开头,然后匹配一个正则表达式结果。js中并没有直接用到,但其实注释部分也可以用它来实现:以//为开头,匹配的正则表达式是//[^\n]*。至于为什么不用ID匹配来实现呢,那是因为正则的消耗比较大,为性能考虑才使用的线性查找。事实上只要是能用线性查找替换的地方,都应该这样做。
RegularMatch 看名字就知道这是终极解决方案了,和IDMatch的差异也只在于定义开头也是个正则。最消耗的方案。我根本没有用到它,放在那里有备无患而已。
ecmascript的示例
以下是用来解析js、as等语言的定义类文件:
package lexer.rule {
/**
* ...
* @author army8735
*/
import lexer.*;
import lexer.depth.*;
import lexer.match.*;
public class EcmascriptRule extends LanguageRule {
public function EcmascriptRule() {
var keywords:Array = "if else for break case continue function true use \
switch default do while int float double long short char null public super in false \
abstract boolean Boolean byte class const debugger delete static void synchronized this import \
enum export extends final finally goto implements protected throw throws transient \
instanceof interface native new package private try typeof var volatile Vector with \
document window return Function String Date Array Object RegExp Event Math Number \
decodeURI decodeURIComponent encodeURI encodeURIComponent escape isFinite isNaN namespace \
isXMLName parseFloat parseInt trace uint unescape XML XMLList undefined Infinity NaN".split(" ");
super(keywords, true);
addMatch(new CompleteEqual(Token.DEPTH, "{", LanguageLexer.IS_PERL_REG));
addMatch(new CompleteEqual(Token.DEPTH, "}", LanguageLexer.IS_PERL_REG));
addMatch(new CompleteEqual(Token.DEPTH, "[", LanguageLexer.IS_PERL_REG));
addMatch(new CompleteEqual(Token.DEPTH, "]", LanguageLexer.IS_PERL_REG));
addMatch(new CompleteEqual(Token.DEPTH, "(", LanguageLexer.IS_PERL_REG));
addMatch(new CompleteEqual(Token.DEPTH, ")", LanguageLexer.NOT_PERL_REG));
addMatch(new LinearSearch(Token.COMMENT, "//", "\n", false));
addMatch(new LinearSearch(Token.COMMENT, "/*", "*/", true));
addMatch(new LinearParse(Token.STRING, "'", "'", true, LanguageLexer.IS_PERL_REG));
addMatch(new LinearParse(Token.STRING, "\"", "\"", true, LanguageLexer.IS_PERL_REG));
addMatch(new CharacterSet(Token.ID, [
CharacterSet.LETTER,
CharacterSet.UNDERLINE,
CharacterSet.DOLLAR
], [
CharacterSet.LETTER,
CharacterSet.UNDERLINE,
CharacterSet.DOLLAR,
CharacterSet.DIGIT
], LanguageLexer.NOT_PERL_REG));
addDep(new TokenDepth(Token.DEPTH, "{", "}"));
addDep(new TokenDepth(Token.DEPTH, "[", "]"));
addDep(new TokenDepth(Token.DEPTH, "(", ")"));
}
}
}
super()构造函数的2个参数分别为保留字数组和语言本身是否支持Perl风格的正则表达式(perl风格的判别被直接集成在了父层解析器中)。
可以看到下方一系列的addMatch()方法,它按照出现顺序来添加匹配模式并排序优先级,先出现的匹配模式具有高优先级。
addDep()方法则是解析深度用的,有兴趣的可以看看——它也很容易被猜出来是怎么做的。
python的示例
python语言可能是比较特殊的例子了,因为它的深度折叠不是靠Token识别,而是看每行开始的空格?!我对python语法不大了解,所以简单搜索写了下,这也能体现出添加新语法文件方便多了的特性。
package lexer.rule {
/**
* ...
* @author army8735
*/
import lexer.*;
import lexer.depth.*;
import lexer.match.*;
public class PythonRule extends LanguageRule {
public function PythonRule() {
var keywords:Array = "and assert break class continue def del elif else \
except exec finally for from global if import in is lambda not or pass print raise \
return try yield while __import__ abs all any apply basestring bin bool buffer callable \
chr classmethod cmp coerce compile complex delattr dict dir divmod enumerate eval \
execfile file filter float format frozenset getattr globals hasattr hash help hex id \
input int intern isinstance issubclass iter len list locals long map max min next \
object oct open ord pow print property range raw_input reduce reload repr reversed \
round set setattr slice sorted staticmethod str sum super tuple type type unichr \
unicode vars xrange zip".split(" ");
super(keywords, true);
addMatch(new LinearSearch(Token.COMMENT, "#", "\n", false));
addMatch(new LinearSearch(Token.STRING, "'''", "'''", true, LanguageLexer.IS_PERL_REG));
addMatch(new LinearSearch(Token.STRING, '"""', '"""', true, LanguageLexer.IS_PERL_REG));
addMatch(new LinearParse(Token.STRING, "'", "'", true, LanguageLexer.IS_PERL_REG));
addMatch(new LinearParse(Token.STRING, "\"", "\"", true, LanguageLexer.IS_PERL_REG));
addMatch(new CharacterSet(Token.ID, [
CharacterSet.LETTER,
CharacterSet.UNDERLINE
], [
CharacterSet.LETTER,
CharacterSet.UNDERLINE,
CharacterSet.DIGIT
], LanguageLexer.NOT_PERL_REG));
}
}
}
python的深度解析直接内嵌集成了,所以没有出现addDep()。其中的疏漏欢迎指出。
想要添加新的语法文件更加欢迎,即使不了解as或者flash的,你也只需告诉我组成规则即可,譬如:python代码的注释部分需要高亮,它由#开头到行末结尾;java的Word由字母下划线开头、字母下划线数字组成……