个性分组符号(个性分组符号怎么打)

更新时间:2023-03-02 17:44:11 阅读: 评论:0

话题:#科技# #数学# #计算机语言# #LaTeX#

小石头/编

TeX在原子命令的执行上是严格按照顺序的,但是宏的展开却允许在顺序的基础上进行微调,这样做的因为是:宏展开相当于程序员编写代码的过程,对于程序员来说,只能是大部分时间按顺序coding,但有时会返回修改或暂时跳过。

在前面章节中,我们已经接触过二种调节展开顺序的用例,

保证尾递归:

defcs{ ... el ... expandafter cs fi}

通过 expandafter 使得 fi 先展开,而 fi 展开为空,于是 cs 就到了自己的尾部。

阻止定义时(高速模式)展开:

oks77={cmd}edefcs{ oexpand cmd he oks77}

命令 oexpand 会阻止 cmd 在cs的定义时展开, he会取出 oks77的内容cmd,但不会做进一步展开。

命令执行优先级,其实也需要改变了 执行顺序,考虑如下命令复合代码,

defa#1.{a[#1]} def#1.{b(#1)}a  x.. % => a[b(x])

输出结果出错了!这显然不是命令复合的本意,我们想要的结果是 ①

a[b(x)]

分析原因:第二行,由于 a 先被执行 所以  x 成了它的参数,于是展开为(注意:命令会吃的后面的空格),

a[ x].

接着被展开,x]匹配为它的参数,于是最终展开为,

a[b(x])

要修复这个BUG就要  先于 a 展开。这在其它 语言里,可以使用 括号 来调整表达式的优先级,但是TeX没有这个机制。不过可以使用 分组来 替代,第二行可改为,

a { x.}.

这次在a展开时,参数变成了一个分组 { x.},于是展开为,

a[{ x.}]

然后是再以x为参数展开,得到 ②,

a[{b(x)}]

最后,执行展开结果,分组符号不会输出,最终结果为就是①了。

这里必须注意,②和① 是有区别的,也即是说,在使用 分组调节优先级的同时引入了分组。若想要展开的直接结果就是 ① ,可以这样,

expandafter a  x..

命令 expandafter 保持  先于 a 被展开一次,它匹配参数是 x,于是展开为,

a b(x).

接着,a再以 b(x) 为参数,展开就是 ①了。

在一般情况下 ① 和 ② 并差别,可是在 let 命令中,就会不同。考虑用 csname命令来产生命令名,例如:

letcsname zeroendcsname=0 % ! Extra endcsname.

出错了!原因是 let 将 csname 当成了要复制的命令,而由于 = 可是省略,所有实际上是将 字符 z 赋值给了 csname,之后 ero 不展开,然后到endcsname 由于找不到 与之匹配的 csname 于是就多了出来 。

解决这个问题,就是要让 csname 先于 let 被执行。此时如果使用 分组,

let{csname zeroendcsname}=0 % ! Missing control quence inrted.

则不行了,原因是分组并不是命令不能被赋值。而使用 expandafter,

expandafterletcsname zeroendcsname=0zero % => 0

就没有问题了!

def 与 let 类似,例如:

defcsname zeroendcsname{0}zero % ! Undefined control quence.

出错了,原因是 第一行等于 重新定义了 csname。解决这个问题,同样需要使用 expandafter,

expandafterdefcsname zeroendcsname{0}zero % ! % => 0

OK!

直到现在,我们使用宏来生产代码都是,句法级别的,也就是以 标记(token)为单位的,而宏还可以深入到,词法级别,也就是可以分拆和组合命令名。

分拆命令名:

TeX 提供 命令 string 将 命令 转为 命令名字符串,例如:

string cs % => cs

但是之前会带有 转义符 ,这时可以写一个 单参数 空命令,吃掉它,

expandafter ail string cs % => cs

然后,将这个功能包成一个宏,

defuncsname#1{expandafter ailstring #1}uncsnamecs % => cs

我们就完成了分拆命令名的第一步,取出命令名。

接下就可以使用带定界符的宏进行模式匹配,进一步分析命令名。

组合命令名:

这个我们已经知道了就是 csname 命令,有三类对象和相互组合:

⑴ 立即字符:

defvalue{abc}expandafterletcsname abcendcsname=value

⑵ 宏:

def ame{b}expandafterletcsname a ame cendcsname=value

⑶ 标记列寄存器:

oks77={b}expandafterletcsname a he oks77 cendcsname=value

注:宏命令 char n 可以产生单个编码是n的字符,而 ^^n 可产生编码是 n±127 的字符。

好了,关于词法宏,的机制也就这么多了,下面举个栗子。

考虑,前文提到过的, TeX 生成标记的命令,

ewififabc

实际上,TeX仅仅为我们添加如下两行代码 ③,

defabctrue{letifabc=iftrue}defabcfal{letifabc=iffal}

这 功能上很简单,因此要实现 ewif 命令的关键是 从 ifabc 命令名中 解析出 abc ,再由 abc 组成出abctrue、abcfal 和 ifabc 三个命令。实现分析:

解析出abc,

考虑 使用 uncmd 可从 ifabc 先 中 解析出 ifabc,然后再使用 两次 tail 分别吃掉 if 就剩下了 abc,具体代码如下:

edefflag{uncsnameifabc}edefflag{expandafter ailflag} edefflag{expandafter ailflag}flag % => abc

值得一提:由于 expandafter 也是宏,于是可以被自己作用自己 用以达到 排序效果,例如上面的第二、三行代码可以改写为:

edefflag{expandafterexpandafterexpandafter ailexpandafter ailflag}

是不是很酷!

定义命令,

这个并不难,但要搞清,定义时 和运行时,(以 abctrue 为例)代码如下:

expandafteredefcsnameflag trueendcsname{ oexpandletcsname ifflagendcsname= oexpandiftrue}

试一试,

* showabctrue> abctrue=macro:->let ifabc =iftrue

和 ③ 处一摸一样,OK!

综上, ewif 的最终实现为,

def ewif#1{ edefflag{unwrap #1} edefflag{expandafter ailflag} edefflag{expandafter ailflag} expandafteredefcsnameflag trueendcsname{ oexpandletcsname ifflagendcsname= oexpandiftrue} expandafteredefcsnameflag falendcsname{ oexpandletcsname ifflagendcsname= oexpandiffal} abcfal}

试一试,

ewififabcifabc abc true el abc fal % => abc falfiabctrueifabc abc true % => abc trueel abc falfi

OK!

这词法宏看着不难,但是用起来很容易出来,大家使用时一定要谨慎。

在任何语言的使用的讨论中,数据结构都是不可避免的话题,而列表又是数据结构的核心之一。前面讨论过 逗号分隔 的 整数列,这里在我们谈谈 TeX 代码本身,即,标记列,标记列寄存器本来就是用来保存它的,它才是 TeX 的第一等公民,接下来,如果无特殊说明,所谓 列表 就是指它。

借助于前面讨论过的 edef + he 的特性,很容易将两个 标记列寄存器 合并起来,放在一个列表宏里,

edefc{ he oks0 he oks1}

于是要合并两个列表宏,只需要在这之前,将其内容保存在 这两个标记列寄存器中,

oks0=expandafter{a} oks0=expandafter{}

其中 expandafter 确保 a 被展开,而非以 命令的 形式 呆在 列表中。在这里标记列寄存器仅仅是临时被使用,我们的列表最终都保持在宏中。

将以上代码封装起来就是,

defconcat#1=#2{ oks0=expandafter{#2} oks1=expandafter{#3} edef#1{ he oks0 he oks1}}

测试一下,

edefa{a} edef{b}concatc=a& c % => ab

OK!

接着就是列表中往添加元素问题。之前的整数列 的元素 之间 是用 逗号分隔的,即,

n1, n2, n3

而这次的是标记列,每个元素item都是(一个或多个)标记,所以应该用 分组来分隔,然后为了将来通过宏参数的模式匹配来处理列表,还需要在每个分组前面再加上@用来定界,因此我们的列表元素是这样的,

@{item1}@{item2}@{item3}

这样以来,在列表末尾 添加元素的具体代码如下,

defappend#1 o#2{ oks0={@{#1}} oks1=expandafter{#2} edef#2{ he oks1 he oks0}}

测试测试,

letlst=emptyappend a olst append b olst append c olst showlst % -> @{a}@{b}@{c}.

OK! 当然,也允许向列表头添加元素,代码如下,

defunshift#1 o#2{ oks0={@@{#1}} oks1=expandafter{#2} edef#2{ he oks0 he oks1}}

测试一下,

unshift 0 olstshowlst % -> @{0}@{a}@{b}@{c}.

OK!

然后,就是取出列表的首项了,代码如下,

defshift#1 o#2{ defperformshift@##1##2 o##3##4{def##3{##1} def##4{##2}} expandafterperformshift#1 o#2#1}

试一试,

shift lst oshow % -> 0.showlst % ->@{a}@{b}@{c}.

OK!

最后,就是对列表中元素的处理问题了。在之前的整数列中,我们使用了函数式语言的传统方法,即,

把整数列中的每个元素取出来进行处理,然后再将处理结果组成新的整数列 ;

但这里的 标记列,其实就是 TeX 代码,它本身就可以运行的,如果我们用 而我们特意使用了 宏命令 @ 作为 分界符,它索然在参数匹配中不起作用,但是如果 列表去运行,就会被执行,不就相当于获得了 处理 对每个 元素的能力吗?

按照这个思路,我们只需要,将处理动作提前封装在 @, 然后让列表宏 执行,就可以了处理列表元素了。例如,计算列表的长度,

deflength#1 o#2{ #2=0 def@##1{advance #2 by 1} #1}

测试一下,

showlst % ->@{a}@{b}@{c}.lengthlst oeax % => 空showtheeax % -> 3.

完美!

将列表输出成我们熟悉的写法非常容易,

def helist#1{ eax=0 def@##1{ advance eax by 1 ifnum eax=1 el , fi ##1} #1} helistlst % => a,b,c

OK!

实现map功能同样非常容易,

defmap#1y#2 o#3{ let#3=empty def@##1{ edefmaptemp{#2##1} expandafterappend maptemp o#3} #1}

测试一下,

defuc#1{ if a#1 A el if b#1 B el if c#1 C el ... fififi}maplstyuc oashowa % ->@{ A }@{ B }@{ C }.

OK!

注意:由于这个map的实现是,在 定义时 调用 #2 的,所以 #2 中的实现不能使用 非运行时 的原子命令,比如 advance 等。

(好了,续三就写到这里吧!关于 TeX编程的更多内容,后续文章再讨论!)

本文发布于:2023-02-28 21:21:00,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/zhishi/a/1677750251110964.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:个性分组符号(个性分组符号怎么打).doc

本文 PDF 下载地址:个性分组符号(个性分组符号怎么打).pdf

标签:符号   个性
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 实用文体写作网旗下知识大全大全栏目是一个全百科类宝库! 优秀范文|法律文书|专利查询|