《elisp 每日一练》—— syntax table 与 font lock

在前面一节的练习,我们做了一个简单的c 语言语法着色的效果。我们再回看一下代码发现,在设置关联的时候我们实际上没有设置 syntax-table,纯粹靠着正则表达式匹配特定字符和文本结构就完成了着色。这么做也有一个问题,就是它会遇到关键字、就着色,不管它出现的位置。读者可以使用下面的代码来测试一下

c 复制代码
// this is a line comment with keyword: return、this
/*this is a block comment with type int、float*/

int main()
{
    int x = 10;
    char* str = "this is a string with return and int";
    return 0;
}

本篇我们的目的就是要搞清楚syntax-table 在语法着色的作用,以及我们如何利用syntax-table 来解决这一问题

背景知识

实际上font lock 着色主要依赖与下面三个类型:

  • font-lock-keywords 里面的正则匹配,也就是我们前面定义的那个映射关系
  • 手动填充的 syntax-table 表,以及syntax-propertize-function 函数中将文本赋予的 syntax-table 属性
  • font-lock-syntactic-face-function 函数决定的注释和字符串用什么类型的face

我们说第一个是显性的,依赖完整正则匹配。后面两类却是依赖 syntax-table 的词法分析来完成工作。也就是说即使我们不使用 font lock 显式的通过正则表达式这种方式指定,只要我们正确的将对应的文本设置了syntax-table 属性,也可以完成语法着色的工作。

正常情况下,我们首先根据编程语言的语法规则,为各个字符设置语法标签,填入到 syntax-table 中,例如将 /**/ 设置为注释,将 " 设置为字符串。接着在利用 font lock 进行语法高亮时调用 syntax-ppss 进行逐段的扫描(这个函数我们前面介绍过,它会按照语法表 syntax-table 自动跳转到下一个语法元素位置)。如果发现当前在字符串或者注释中就会使用 font-lock-comment-face / font-lock-string-face 给这段文本上色。这部分的工作都是通过 syntax-table 完成的

对于块注释包含多行的情况下,Emacs 实际上还通过 syntax-propertize-function 利用文本属性中的 syntax-table 属性完成着色和 syntax-ppss 的跳转工作。

最后因为 syntax-table 中只规定了那么几种词法样式,并没有规定类型、变量、函数、常量这种高级的语法特性,所以语法着色层面还是需要依赖我们前面定义的正则表达式匹配规则。

最后我们再来看看我们之前设置的 font-lock-defaults 它到底接收什么样的值。

emacs-lisp 复制代码
(KEYWORDS [KEYWORDS-ONLY [CASE-FOLD [SYNTAX-ALIST ...]]])

其中各个参数的含义如下:

  • KEYWORDS: 就是我们前面一直在定义使用的规则,它可以是正则表达式和face 符号组成的cons cell,可以是正则表达式、捕获组和face符号组成的列表,也可以一个正则表达式带上多个捕获组和face
  • KEYWORDS-ONLY: 我们前面一直穿的是nil,nil表示即执行我们使用正则表达式在语法层面的着色,也执行 syntax-table 词法层面的着色,为 t 的话则只进行语法层面的着色
  • CASE-FOLD 正则匹配时是否忽略大小写,为nil 时表示不忽略,t为忽略,此时像 int、Int、INT 这类的关键都会着色
  • SYNTAX-ALIST 表示着色时使用的语法表,我们可以额外提供一个新的syntax-table 替换当前着色时使用的 syntax-table,默认使用的是当前buffer中的 syntax-table

练习

现在我们应该知道如何解决上面提到的,只要是 return、int 等关键字都会进行着色的问题了。我们实际上只需要添加关于注释和字符串的 syntax-table 项就可以解决这个问题

emacs-lisp 复制代码
(defvar my-simplec-mode-syntax-table
  (let ((st (make-syntax-table)))
	(modify-syntax-entry ?\" "\"" st) ;; 字符串
	(modify-syntax-entry ?/ ". 124" st) ;; 注释
	(modify-syntax-entry ?* ". 23" st) ;; 块注释
	(modify-syntax-entry ?\n "> b" st) ;; 注释结束符
	st))

(defvar my-simplec-font-lock-keywords
  `(
    ("\\<\\(if\\|else\\|while\\|for\\|return\\)\\>" . font-lock-keyword-face)
    ("\\<\\(int\\|float\\|double\\|char\\|void\\|bool\\)\\>" . font-lock-type-face)
    ("\\<\\(true\\|false\\|null\\|NULL\\)\\>" . font-lock-constant-face)

    ;; 函数定义
    ("\\<\\([a-zA-Z0-9_]+\\)\\s-+\\([a-zA-Z0-9_]+\\)\\s-*("
     (1 font-lock-type-face)
     (2 font-lock-function-name-face))

    ;; 变量定义
    ("\\<\\(int\\|float\\|double\\|char)\\s-+\\([a-zA-Z0-9_]+\\)"
     (1 font-lock-keyword-face)
     (2 font-lock-variable-name-face))
    )
  "MySimpleC 的 Font Lock 正则匹配与 Face 绑定规则。")

(define-derived-mode my-simplec-mode text-mode "MySimpleC" ;; 这里为了避免 prog-mode 的干扰改成了 text-mode
  "配置简单的 Font Lock 关键字代码高亮"
  (setq-local font-lock-defaults '(my-simplec-font-lock-keywords nil nil)))

这里我们添加了关于注释和字符串的 syntax-table 项,它们的着色就放到扫描时解决,因此就删除了正则表达式中关于注释着色的内容。

总结

到这里我们梳理了 syntax-table 在 font lock 中的作用。syntax-table 是我们在编辑时使用 forward-word 、syntax-ppss 等函数的基础。在此基础上,在启动 font lock 的时候会自动的利用syntab-table 进行词法层面的跳转,例如跳过注释。并且如果我们在 font-lock-defaults 参数中 KEYWORDS-ONLY 位置设置的是 nil 也会利用 syntax-table 中设置的字符串、注释等属性对这些元素进行着色。最后利用我们设置的正则表达式区分关键字、类型、变量、常量、函数名等语法单元进行语法层面的高亮。

相关推荐
Masimaro1 天前
《elisp 每日一练》—— 基于treesitter 的语法高亮
emacs
智购科技自动售货机工厂13 天前
数字人民币硬钱包支付失败,排查发现是NFC读卡器功率不足~YH
python·面试·架构·eclipse·emacs
Masimaro15 天前
《elisp 每日一练》—— define-derived-mode 管理 syntax-table
emacs
Masimaro22 天前
《elisp 每日一练》—— Major Mode 管理 abbrev-table
emacs
AI情绪识别开源24 天前
检信AI学业生涯智选系统技术方案概述
emacs
Masimaro1 个月前
《elisp 每日一练》—— Minor Mode 回顾与综合练习
emacs
aluluka1 个月前
《elisp 每日一练》——状态栏显示管理与modeline 动态更新
emacs·lisp·elisp
aluluka1 个月前
《elisp 每日一练》——minor mode 快捷键管理
emacs·lisp·elisp
Masimaro1 个月前
《elisp 每日一练》——状态栏显示管理与modeline 动态更新
emacs