在前面介绍buffer 的时候,简单的介绍了 syntax-table的概念。它是一个存储了 ASCII/Unicode 字符与其语法类 (Syntax Class) 对应关心的表格。在前面的文章中,我只是介绍了如何使用它,例如我们介绍了 scan-sexps 函数,利用表中数据在语法单元间移动。而如果我们要创建自定义的 Major Mode,核心工作之一就是维护自己的 syntax-table。
背景知识
syntax class
前面提到:它存储的是字符到语法类的对应关心。我们先来介绍一下常用的语法单元 (Syntax Class)。常用的有:
| 语法类名称 | 字符代码 | 含义与示例 |
|---|---|---|
| 空白符 | ' ' 或 '-' | 空白字符(空格、Tab、换行符) |
| 单词组成字符 | 'w' | 构成普通单词的字符(字母、数字)。例如按 M-f 会穿过连续的 w |
| 符号组成字符 | '_' | 构成标识符/符号的字符(如 _ 或 -)。foo_bar 中 _ 设为 _ 时,它是一个完整 Symbol |
| 标点 | '.' | 标点符号(如 .、 ,、 ;) |
| 括号 | '(' / ')' | 成对出现的开/闭括号(如 ()、[]、{}) |
| 字符串引号 | " ' | "字符串分界符(如 "" 或 '' |
| 转义字符 | '\' | 转义字符(如 \) |
| 注释起始符 | < > | 界定注释的起始和结束位置 |
维护syntax-table 的一般操作
syntax-table 与一般的数据一样,对它的操作无外乎增删改查。我们还是按照这个思路来介绍。
首先我们可以使用 make-syntax-table 来创建一个空表。根据我们前面在分析 define-derived-mode 中看到的,它一般被定义为全局变量。
但是在实际开发中,一般不会从0开始,而是继承自某个父类,以此为基础进行个性化改造。对于编程语言,可以从 prog-mode-syntax-table 继承,而普通文本可以从 standard-syntax-table 。
在继承之后,针对一些不合适的项目,我们可以使用 modify-syntax-entry 设置字符的语法类。它的函数原形是:
(modify-syntax-entry CHAR NEWENTRY &optional SYNTAX-TABLE)- CHAR 是一个字符常量,也可以是一个 (MIN . MAX) 的范围,表示该范围内的字符都是这个Syntax Class
- NEWENTRY 是语法描述符,是一个字符串,它每个字符都有明确的含义。第一个字符是前面介绍的Syntax Class 符号;第二个参数是匹配字符,仅当第一个字符是
(或者)才有意义;第三个及之后的字符是语法标志,它是一组数字,用于描述多字符注释、可嵌套注释等高级行为。标志的顺序无关紧要。 - SYNTAX-TABLE 要修改的语法表,如果不填默认修改当前buffer中的syntax-table
NEWENTRY 详解
NEWENTRY 是一个相对复杂的数据,我们来通过简单的例子来说明
一个字符的情况
(modify-syntax-entry ?# "<" my-table)上面的代码,我们设置 # 字符为 shell 的注释。这段代码中,NEWENTRY 只有一个字符,表示 Syntax Class
括号的处理
(modify-syntax-entry ?\( "()" my-table)
(modify-syntax-entry ?\) ")(" my-table)这段代码,我们设置了一个括号区域,它的 NEWENTRY 域有两个字符,第一个字符 ( 表示这是一个左括号,而第二个表示与它匹配的右括号是 ),所以如果我们要添加一个描述中括号的规则可以这么写
(modify-syntax-entry ?\[ "(]" my-table)
(modify-syntax-entry ?\] ")[" my-table)注释
C 语言有两种注释符号,我们看看该如何在 syntax-table 中定义它们。首先我们来看看 /**/
(modify-syntax-entry ?/ ". 14" my-table)
(modify-syntax-entry ?* ". 23" my-table)既然 /* 是注释,为什么不用代表注释的 < 来设置呢,主要是因为 / 在C 语言中主要的含义是除法运算符,只有和 * 在一起的时候才作为注释。所以Emacs的做法是将它作为运算符处理。作为注释的作用则通过语法标记来完成。
Emacs中用于语法标志的数字含义为:
1:该字符是双字符注释起始符的第一个字符。
2:该字符是双字符注释起始符的第二个字符。
3:该字符是双字符注释结束符的第一个字符。
4:该字符是双字符注释结束符的第二个字符。
对于 /**/ 这样的注释来看,/ 它位于注释区域的第一个字符作为开始,注释的结束位置它是第4个字符。而 * 则对应于注释区域的第2和第三个字符。
那么根据这个标准,我们可以定义xml的注释 <!---->, 理论上我们可以依葫芦画瓢写成如下模式:
在xml中,注释是 <!----> 是否可以写为
(modify-syntax-entry ?< ". 1" my-table)
(modify-syntax-entry ?! ". 2" my-table)
(modify-syntax-entry ?- ".3456" my-table)
(modify-syntax-entry ?> ".7" my-table)但是遗憾的是,上述方案并不能正常工作。因为Emacs中对于syntax class ,它只支持两个字符,也就是语法标志只支持从1到4。对于这种复杂的类型,可以使用 syntax-propertize-function 函数,通过正则表达式来支持。
说完了 /**/,我们再来说另一种注释 //, 根据前面我们的描述,/ 本身是运算符,所以作为注释也是使用数字标识,那么它的写法如下:
(modify-syntax-entry ?/ ". 12" my-table)有的读者看到这里是不是认为上述写法没问题呢?// 作为注释它出现在第一和第二位置。如果你这么想,你可能只对了一半,作为 // 类型的注释,它确实出现在 1 4 两个位置,但是别忘了,/ 还可以以 /**/ 的形式出现。所以正确的写法应该是结合我们前面介绍的 /**/ 类型处理方式:
(modify-syntax-entry ?/ ". 124" my-table)字符串常量的处理
对于字符串常量来说,一般以 "" 进行分隔,它即作为字符串的开始也作为结束的标志,所以我们不需要像前面的括号那样专门制定一个闭合的字符。一般来讲,可以写下如下的代码来处理
(modify-syntax-entry ?\" "\"" my-table)这样就够了吗?
字符串还有很多复杂的变种,例如对于C++ 11以后的标准,它允许使用 R("") 来表示一个字符串。有的字符串里面采用 \" 表示字符串内部有" 这个字符。复杂的今天我们就不讨论了,我今天只处理 \ 作为转义字符的情况,可以写下如下代码处理:
(modify-syntax-entry ?\\ "\\" my-table)小结
本节的内容就结束了, 本节我们主要介绍了 modify-syntax-entry 函数,并且通过几个例子演示了它的使用方法。其实可以归纳为3类:
- 单字符就是一个 syntax class 的,直接使用单字符字符串指定
- 对于需要不同字符表示一个区间的,可以使用两个字符的字符串指定,第一个字符是 syntax class 类型,第二个字符表示闭合字符
- 对于4个字符组成的注释区间,可以使用数字 1 2 3 4 指明作为注释符号时它可以出现的位置
评论 (0)