《elisp 每日一练》—— define-derived-mode 管理 syntax-table

在前面的文章中,我们了解了,实际在通过 define-derived-mode 定义自己的Major Mode 的时候,在处理 syntax-table 时主要通过继承的方式继承父类中内容,然后通过 modify-syntax-entry 修改不符合当前语境下的项目。或者使用 syntax-propertize-function 通过正则表达式处理一些复杂的情况。今天我们深入探讨 define-derived-mode 宏是如何管理 syntax-table 以及如何通过继承机制获得父类的 syntax-table

背景知识

syntax-table 关键字

与 abbrev-table 类似,define-derived-mode 支持通过 :syntax-table 自定义syntax-table。我们通过一个简单的示例看看宏展开的代码:

emacs-lisp 复制代码
(define-derived-mode my/code-mode prog-mode "mycode"
  "my code 测试mode"
  :syntax-table (let ((my-code-syntax-table (make-syntax-table)))))

我们展开之后发现,与 syntax-table 相关的代码只有

emacs-lisp 复制代码
(set-syntax-table
	   (let ((my-code-syntax-table (make-syntax-table)))))

也就是说它仅仅将我们通过 :syntax-table 关键字传进来的 内容设置为当前buffer中的 syntax-table。此时继承相关的内容由我们自己管理。

因此这里得出的结论也是与 abbrev-table 类似:应该在外部定义一个与宏里面 syntax-table 命名相同的 syntax-table 变量,我们在外部只关心具体的定义,而其余细节交给宏来处理。

set-char-table-parent

我们观察在继承机制下,对于 syntax-table 的处理发现,继承机制实际上是通过 set-char-table-parent 函数来完成。

emacs-lisp 复制代码
(set-char-table-parent CHAR-TABLE PARENT)

这个代码是底层通过 c 实现的,我们可以简单的看看在Emacs中它是如何实现的。在我找到的一些源码资料中显示,这个函数的C语言实现如下:

C 复制代码
DEFUN ("set-char-table-parent", Fset_char_table_parent, Sset_char_table_parent,
       2, 2, 0,
       doc: /* Set the parent char-table of CHAR-TABLE to PARENT.
Return PARENT.  PARENT must be either nil or another char-table.  */)
  (Lisp_Object char_table, Lisp_Object parent)
{
  Lisp_Object temp;

  CHECK_CHAR_TABLE (char_table);

  if (!NILP (parent))
    {
      CHECK_CHAR_TABLE (parent);

      for (temp = parent; !NILP (temp); temp = XCHAR_TABLE (temp)->parent)
	if (EQ (temp, char_table))
	  error ("Attempt to make a chartable be its own parent");
    }

  set_char_table_parent (char_table, parent);

  return parent;
}

static void
set_char_table_parent (Lisp_Object table, Lisp_Object val)
{
  XCHAR_TABLE (table)->parent = val;
}

从这里我们可以看到,函数内部主要的工作实际上是将表中的 parent 指针指向真实的父类。所以说它的继承结构实际上构成了一个链表。

按照我们写程序的一般规律来讲,如果是一个链表结构,可以合理的推测它的查找流程应该是这样的:

  • 先在当前buffer 中记录的 syntax-table 表中查找
  • 如果未找到,则根据 parent 指针找到它的父表,在父表中查找
  • 根据记录的 parent 指针一级级的往后找,直到找到或者 parent 指针是空

我们可以在Emacs的源码中找到查表相关的代码:

emacs-lisp 复制代码
Lisp_Object
char_table_ref (Lisp_Object table, int c)
{
  struct Lisp_Char_Table *tbl = XCHAR_TABLE (table);
  Lisp_Object val;

  if (ASCII_CHAR_P (c))
    {
      val = tbl->ascii;
      if (SUB_CHAR_TABLE_P (val))
	val = XSUB_CHAR_TABLE (val)->contents[c];
    }
  else
    {
      val = tbl->contents[CHARTAB_IDX (c, 0, 0)];
      if (SUB_CHAR_TABLE_P (val))
	val = sub_char_table_ref (val, c, UNIPROP_TABLE_P (table));
    }
  if (NILP (val))
    {
      val = tbl->defalt;
      if (NILP (val) && CHAR_TABLE_P (tbl->parent))
	val = char_table_ref (tbl->parent, c);
    }
  return val;
}

别的先不用太纠结,我们看到它分了三种情况:

  • 如果是普通的 ASCII 字符,直接查找表中的 contents 数据
  • 如果是非 ASCII 字符,调用 sub_char_table_ref 查找
  • 最后如果当前表中未找到,它会通过 parent 指针找到父表,再递归调用一次

上面的代码中,我们看不到 syntax-table 的相关名称,转而退化为了 char-table。实际上 char-table 是Emacs专门为了快速查找 key 类型为 unicode 字符,而value 类型为对应数据这种映射关系而设计的数据结构。syntax-table 只是它里面的一种。如果深入了解Emacs之后会发现很多类似的结构。

总结

最后我们来总结一下关于 syntax-table 的相关内容。

  • 默认情况下,define-derived-mode 会根据传入的 parent 参数来执行继承操作。实现继承的主要方式就是调用 set-char-table-parent 将多个表组成一个链表,实际查找时按链表顺序进行查找。

  • 我们可以使用 :syntax-table 关键字指定 syntax-table,但是这个时候需要我们自己处理关于表的一切,define-derived-mode 只负责设置当前buffer 的 syntax-table 为我们指定的内容。

相关推荐
Masimaro8 天前
《elisp 每日一练》—— Major Mode 管理 abbrev-table
emacs
AI情绪识别开源9 天前
检信AI学业生涯智选系统技术方案概述
emacs
Masimaro13 天前
《elisp 每日一练》—— Minor Mode 回顾与综合练习
emacs
aluluka14 天前
《elisp 每日一练》——状态栏显示管理与modeline 动态更新
emacs·lisp·elisp
aluluka15 天前
《elisp 每日一练》——minor mode 快捷键管理
emacs·lisp·elisp
Masimaro15 天前
《elisp 每日一练》——状态栏显示管理与modeline 动态更新
emacs
Masimaro17 天前
《elisp 每日一练》——minor mode 开关变量的管理
emacs
Masimaro18 天前
《elisp 每日一练》—— Minor Mode 变量生成机制与状态控制
emacs
Masimaro19 天前
《elisp 每日一练》——文件与目录的回顾与综合练习
emacs