40行Haskell代碼的命令行解析器
第一步:定義選項(xiàng)結(jié)果的數(shù)據(jù)類型
為了簡單起見一汽,考慮三種情況:
- 獨(dú)立選項(xiàng):即沒有任何先導(dǎo)符號胀蛮,獨(dú)立存在的選項(xiàng)。
- 開關(guān)選項(xiàng):選項(xiàng)即自身翔始,表示開啟或者關(guān)閉某個(gè)功能倒脓。
- 帶值選項(xiàng):選項(xiàng)是前導(dǎo)符號加上后面的一個(gè)參數(shù)撑螺,表示取一個(gè)值(按字符串)
例子:
program -i input.txt -o output.txt s.log -k -v
-
-i
為帶值選項(xiàng),表示輸入文件名 -
-o
為帶值選項(xiàng)把还,表示輸出文件名 -
-k
和-v
是開關(guān)選項(xiàng) -
s.log
為獨(dú)立選項(xiàng)
我們使用Haskell的代數(shù)數(shù)據(jù)類型(ADT)定義上述選項(xiàng):
data CmdOptionResult = Standalone { value :: String }
| OptionSwitch { name :: String, flag :: Bool }
| OptionValue { name :: String, value :: String } deriving(Eq, Show, Read)
第二步:基本的解析器
- 開關(guān)解析器
parseSwitch _ [] = (Nothing, [])
parseSwitch (sname, lname) args@(arg:as)
= if arg == lname || arg == sname
then (Just (OptionSwitch lname True), as)
else (Nothing, args)
- 帶值選項(xiàng)解析器
parseValue _ [] = (Nothing, [])
parseValue (sname, lname) args@(arg:as)
= if arg == lname || arg == sname
then (Just (OptionValue lname (head as)), tail as)
else (Nothing, args)
解析器組合子
- 算符
<|>
.
parser1 <|> parser2
= (\as -> let (v1, s1) = parser1 as
(v2, s2) = parser2 as
in if v1 /= Nothing
then (v1, s1)
else (v2, s2))
- 多個(gè)選項(xiàng)的解析器的聚合
aggregate ps = foldl1 (<|>) ps
- 將解釋器重復(fù)應(yīng)用在一串參數(shù)上
repeat ps = let parser = aggregate ps
fromJust (Just x) = x
in \args -> let (v, s) = parser args
in if args == []
then []
else if v == Nothing -- match failed, so it is a standalone option value
then (Standalone (head args)):(Util.CmdParser.repeat ps (tail args))
例子
- 測試?yán)?/li>
parser_lst_example = [ parseSwitch ("-v", "--verbose")
, parseValue ("-i", "--input")
, parseValue ("-o", "--output")
, parseValue ("-m", "--mode")
, parseSwitch ("-h", "--help") ]
parser_example = Util.CmdParser.repeat parser_lst_example
args_example_1 = ["-i", "input.txt", "--output", "output.txt", "-v", "-m", "1"]
args_example_2 = ["-h"]
- 解析結(jié)果
parser_example args_example_1
[OptionValue {name = "--input", value = "input.txt"},OptionValue {name = "--output", value = "output.txt"},OptionSwitch {name = "--verbose", flag = True},OptionValue {name = "--mode", value = "1"}]
*Util.CmdParser> parser_example args_example_2
[OptionSwitch {name = "--help", flag = True}]