上一节的时候我们讨论到了用附加的解释器组件扩展功能的方法。这一次我们再加些料。
首先,我们定义出Scheme语言中的一些类型:
Haskell语言:
事
实上,Haskell里一切都可以看作是函数,data也不例外。千万不要拿这个东西去随便对应你以前见过的其它叫data的东西。事实上Haskell
里的Type、Class、Instance和Data跟OO语言里的概念都完全不是一回事儿。如果你有学过近世代数,可以去看看上节里推荐的T1的那篇
Monad教程。
这里,使用data,我们定义了 LispVal 可能的几种类型:
- Atom 是一个文本,它表示一个原子命名
- 若干 LispVal 的序列成为一个 List (注意 List 也是一种 data Lispval,所以这是一个递归的定义)
- . 联接列表和一个 LispVal 值,组成一个 DottedList
- Number 存储整数
- String 存储字符串
- Bool 存储逻辑值
因为Haskell的类型和构造器取自不同的命名空间,所以这里我们定义了与系统类型相同的String、Bool之类的类型,也不会靠成什么问题。类型和构造器都是PASCAL命名。
现在编写几个解释器函数。首先是字符串。字符串是一对双引号标记,包含若干文。
Haskell语言:
这
儿又出来一新的妖招:我们没用
>>,而是用了一个do。这是为了可以取到引号之间的值,这里我们用了char和many两个解析工具。按作者的解释,通常不需要取得
action返回值的时候(比如为了组合它们生成新的monad),使用>>,而需要取值并用于下一个action的时候,用
>>= 或 do-notation。
取值完成以后,我们将其 return 为一个 LispVal 。抽象数据类型中的每个构造器也同样可以看作是一个函数:返回一个该类型的值。函数进行参数的式匹配的时候,也可以根据data来匹配。
内
置函数 return 可以把我们的 LispVal 提升为一个Parser
monad。每一行do代码虽然都要求是同一个类型,但是但是我们的字符串解析函数只是返回了一个 LispVal,这时候就靠 return
帮我们搞定这个类型封装啦。这样,整个 parseString action 就成为了一个 Parser LispVal。
$只
是括号的简写 return $ String x 等同于 return (String
x),这个在Haskell的语法教程中都会有介绍。不过这里有特别提出,$是一个操作符,所以你能对一个函数做什么,就能对它做什么,传递、局部化等
等。在这里,它相当于一个 apply。
Atom 就是一个原子语素,一个字母或符号,跟随若干数值或字母、符号之类的:
Haskell语言:
这里出现了一个新的 Pasec combinator,选择运算符 <|>。它尝试第一个parser,失败就尝试第二个。哪个成功就返回哪个parser的值。
读取语素的第一个字符以及其余部分后,我们需要把它们合在一起。let语法定义了一个atom变量,我们使用联接符:把它们联起来。如果不用:,还可以用 [first] ++ rest。
case是基本语句,语法书都讲得很明白,这里不多讨论了。
Bloger真是神奇,动不动就把代码搞乱了,这边我也不贴了(CU居然没有Haskell支持,怨念),大家点链接进去看源码吧。
阅读(949) | 评论(0) | 转发(0) |