2008年12月19日星期五

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

上一次,我们给出了原子(Atom)的解析器,其中已经把逻辑类型(#t/#f)的解析功能一并做进去了。

接下来是解析数值的组件:

Haskell语言: Parsing 代码片段九

parseNumber :: Parser LispVal
parseNumber = liftM (Number . read) $ many1 digit

这一段不难理解,主要是 $ 和 . 两个函数的运用。 many1 解析器匹配一个或多个参数,所以我们可以用来匹配一个或多个数字字符(0-9)。不过我们需要返回的是一个LispVal数值,所以还要把解析到的字符串 再转为数字。于是,我们用 . 组合 Number和read。它先将传入的参数作用于右值,再将得到的结果作用于左值。

many1返回的值实际上是 Parser String,而我们需要一个 String,还要返回一个 Parser LispVal。所以 Number . read 读不了它。liftM 起到析值的作用。也就是说,我们将many1 digit解析得到的 Parser String,传递给 $ (它等效于一对括号)的左值, liftM (Number . read)。

为了使用这个 Monad ,我们需要导入 Monad 模块:

import Monad

这种风格的编程--重度依赖函数组合、应用、传递--在 Haskell 代码中非常普遍。它通常可以使你在一行中写出非常复杂的算法。有时候你可能需要从右向左读Haskell代码(想想我们学过的那些数学公式)。

Barolo 号称意大利国酒--要知道正是古罗马的军团将亚平宁半岛的葡萄酒文化带到了法国和整个地中海沿岸,至今意大利仍是世界第一大葡萄酒生产国--但它的强劲却 令很多初尝葡萄酒的人退避三舍。很多初学者比较难接受Haskell浓烈粹的FP风格。我个人也仍处于努力学习的阶段。但是,也正是这种风格,是它的魅力 所在。

现在,我们可以编写一个 parserExpr 接受字符串、数值和原子语素了:

Haskell语言: Parsing 代码片段十

parseExpr :: Parser LispVal
parseExpr = parseAtom
<|> parseString
<|> parseNumber

然后修改 readExpr 以使其调用新的 parser:

Haskell语言: Parsing 代码片段十一

readExpr :: String -> String
readExpr input = case parse parseExpr "lisp" input of
Left err -> "No match: " ++ show err
Right _ -> "Found value"

最后,我要说的是,其实在这一章,我将代码文件分成了三份:BuildIn.hs,Parser.hs,Parsers.hs。其中 Parser.hs是主程序,其它两个文件定义为两个模块。但是多文件编译的话,学原文上的方法并不成功,可能是ubuntu默认的shell和 debian不同,不过更大的可能是高版本的ghc编译器参数变了,总之,我编译的时候用的是:

为了阅读方便,我完整给出三个文件的代码。

BuildIn.hs:

module BuildIn where

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

Parsers.hs:

module Parsers where

import Monad
import Text.ParserCombinators.Parsec hiding (spaces)
import BuildIn

symbol :: Parser Char
symbol = oneOf "!#$%&|*+-/:<=>?@^_~"

spaces :: Parser()
spaces = skipMany1 space

readExpr :: String -> String
readExpr input = case parse parseExpr "lisp" input of
Left err -> "No match: " ++ show err
Right val -> "Found value"

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

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

parseNumber :: Parser LispVal
parseNumber = liftM (Number . read) $ many1 digit

parseExpr :: Parser LispVal
parseExpr = parseAtom
<|> parseString
<|> parseNumber

Parser.hs

module Main where

import System.Environment
import Parsers

main :: IO ()
main = do args <- getArgs
putStrLn (readExpr (args !! 0))

没有评论: