2008年12月7日星期日

Write Yourself a Scheme in 48 Hours/Parsing(三)

上一节的时候我们讨论到了用附加的解释器组件扩展功能的方法。这一次我们再加些料。 首先,我们定义出Scheme语言中的一些类型:

Haskell语言: Parsing 代码片段六

data LispVal = Atom String
| List [LispVal]
| DottedList [LispVal] LispVal
| Number Integer
| String String
| Bool Bool

事实上,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语言: Parsing 代码片段七

parseString :: Parser LispVal
parseString = do char '"'
x <- many (noneOf "\"")
char '"'
return $ String x

这儿又出来一新的妖招:我们没用 >>,而是用了一个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语言: Parsing 代码片段八

parseAtom :: Parser LispVal
parseAtom = do first <- letter <|> symbol
rest <- many (letter <|> digit <|> symbol)
let atom = first:rest
return $ case atom of
"#t" -> Bool True
"#f" -> Bool False
otherwise -> Atom atom

这里出现了一个新的 Pasec combinator,选择运算符 <|>。它尝试第一个parser,失败就尝试第二个。哪个成功就返回哪个parser的值。

读取语素的第一个字符以及其余部分后,我们需要把它们合在一起。let语法定义了一个atom变量,我们使用联接符:把它们联起来。如果不用:,还可以用 [first] ++ rest。

case是基本语句,语法书都讲得很明白,这里不多讨论了。

发芽网挂了……明天再继续吧……

有一件挺让人无语的事儿就是你费事儿排的版它总不给显示,在Blogger的编辑器上编辑源码真不是人干的事儿,现在我改用Muse了……不过还是比较麻烦的……有些代码我想还是放到发芽上比较方便。

没有评论: