运算符文法

*表达式的运算符表示

考虑一个数学式 2^5/7! ,或者加上圆括号 (2^5)/(7!) ,它的表达式写作

Divide[Power[2, 5], Factorial[7]]

实际上这里的圆括号是可省略的, 2^5/7! 这种写法通常被认为是使用了一种 运算符优先级语法 。其中 / ! ^ 都是所谓运算符,而 2 3 7 3! 3!^7 1/ 3!^7 都是表达式。

定义一种运算符时,须指定其一些属性。对于上面的例子,这些属性如下表:

运算符

元数

运算符位置

优先级

结合性

/

2

中缀

1

左结合性

^

2

中缀

2

左结合性

!

1

后缀

3

(无此属性)

这些属性会以下面的方式决定会得到怎样的表达式:

  • ! 的元数为1表明要结合附近的1个表达式,而后缀表明它将结合前面的表达式。

  • / 的元数为2表明要结合附近的2个表达式,而中缀表明它将结合前后2个表达式。 ^ 也一样。

  • 看上去 7 可能先和 ! 结合,也可能先和 / 结合。但 ! 的优先级 3 大于 / 的优先级 1 ,所以 3 先被 ! 抢到手。这时得到 2^5/(7!)

  • 结合性用于判断同优先级运算符的争抢赢家。如 1/3/5 到底是 (1/3)/5 还是 1/(3/5) 呢? / 的左结合性指明,应让位于左边的 / 成为争抢的赢家。

对于元数超过2的函数,则需要更多的设计(多元运算符、选项等),此处暂且不论。

用音调标记谓词的运算符

前文已解释如何用嵌套函数来作为复合句的模型,而良好定义的运算符文法可以表达任何嵌套表达式。接下来将给出一个方案,它对一个谓词进行音调标记来得到一个运算符。

自动填充

在一般的运算符优先级语法中,运算符不具备表达原子表达式(如前文的苏格拉底 soke )的能力。

本方案允许为一个谓词添加标记,使其第1或2个参数缺失(或者说插槽空置)时自动被一个变量(匿名,需要时可命名)自动填充。至于变量如何参与语义解释,此处不限定、可作进一步设计。一个典型的做法是设计一个1元函数,其副作用是对传入的变量进行一个量化的声明。

空气没毒
kote1[
        koqi1[
                fola1[]
        ],
        metu1[
                pila1[]
        ],
]

其中:

  • fola1[x] := 声明 x 被全称量化并返回 x

  • koqi1[x] := 断言「x 是空气」并返回 x

  • metu1[x] := 断言「x 没毒」并返回 x

  • pila1[x] := 声明 x 与本句中的唯一一个其他自动填充的匿名变量是语义等价的,并返回 x。

  • kote1[{expr1, ret1}, {expr2, ret2}] := 断言「若 expr1 中的命题为真则 expr2 中的命题为真」并返回 ret1

此处量化的作用域默认是整个句子,由于只有一个量词这不是问题。需要明确作用域的情况,可考虑lambda表达式(坑,计划在后文写)。

此外这个句子用到的定义未免太过复杂,最好的办法还是将这种模式封装在词典中。如定义「P的模型蕴含Q的模型」这样的高阶谓词(如Eberban的 mao )。

插槽交换

本方案允许为一个谓词添加标记,使其第1或2个插槽的位置交换。如,若对

suma[x, y] := 声明「x 小于 y」

添加此标记就得到相应的「大于」概念的谓词。

允许的运算符类型

所有一元运算符强制为后缀运算符,所有二元运算符为中缀。

音调标记

声调1

声调2

谓词元数

插槽交换

自动填充的插槽

返回值

运算符元数

\

\

1

原型

1

1

/

/

1

原型

1

1

0

\

/

2

原型

2

2

1

/

\

2

原型

1

1

1

/

2

原型

1

2

\

2

逆序

1

2

/

2

原型

2

2

\

2

逆序

2

2

注意仅支持其中一个插槽的自动填充,且自动填充的变量必须是返回值。

注意虽然在预期的设计中一个谓词应有确定的元数,本方案仍然用标记区分了不同元数的谓词,以确保无需词典数据库亦可还原表达式。

余下的一种音调组合是 -- ,这种词不表示运算符而标记处理运算符的函数,这些函数表达式的解析在通常的运算符结合之前处理,遵循波兰表示法。由于波兰表示法的解析需要知道元数,这种标记一般适合普适性较强的,如常用的高阶函数。最简单的情况是一元的高阶函数的情况,这时它可以视为对一类最高优先级的前缀运算符的支持。

现在看一个例句

甲小于12且是最小的。

它翻译为

téfú súmā fású māxī sùmà

其中用到的谓词定义如下

  • fasu[x] := 将变量x记为「甲」

  • tefu[x] := 断言「x 12」

  • suma[x, y] := 断言「x 小于 y」

  • 高阶函数 maxi[P][x] := 断言「在 x 所属的关联于 P 的偏序集合中 x 是一个极小元 。(约定其处理运算符后,保留音调所标记的属性。这个约定的问题是无法利用逆序的信息了。)

这些运算符的属性列在下面:

运算符

谓词元数

插槽交换

自动填充的插槽

返回值

运算符元数

téfú

1

原型

1

1

0

súmā

2

原型

2

2

fású

1

原型

1

1

0

māxī sùmà

1

原型

1

1

讨论

  • 这里为了规则的简单整齐,可能会导致一些音调模式不适当地过多出现。

  • 返回值的机制擅长将变量向上层(根节点)传递,但无法简单地在不同分支间传递。不过求值机制本身就是从深到浅,无结构预处理、深到浅、深到浅这三步估计已经够喝一壶了。如果目标是一个可由程序分析的语法,可能注定没特别好的办法。

  • 一维的文字序列中的运算符不擅长处理度超过2的顶点的问题没有改变。

  • 支持二元谓词逆序的目的有两个。一是为表达的灵活性(初版设计的灵活性实际上比这个还要强,标记需要大概两倍的状态数);二是高阶谓词如果不需要用到运算符的信息,比如若 māxī[P] 的处理结果强制为一个 \\,\\ 型的运算符,那么就不需要用到运算符本身的声调标记,那么其中包含的逆序信息可以用来区分「极大」和「极小」。