40行Haskell代码的命令行参数解析器
2018-07-22 本文已影响1人
DarkBubble
40行Haskell代码的命令行解析器
第一步:定义选项结果的数据类型
为了简单起见,考虑三种情况:
- 独立选项:即没有任何先导符号,独立存在的选项。
- 开关选项:选项即自身,表示开启或者关闭某个功能。
- 带值选项:选项是前导符号加上后面的一个参数,表示取一个值(按字符串)
例子:
program -i input.txt -o output.txt s.log -k -v
-
-i
为带值选项,表示输入文件名 -
-o
为带值选项,表示输出文件名 -
-k
和-v
是开关选项 -
s.log
为独立选项
我们使用Haskell的代数数据类型(ADT)定义上述选项:
data CmdOptionResult = Standalone { value :: String }
| OptionSwitch { name :: String, flag :: Bool }
| OptionValue { name :: String, value :: String } deriving(Eq, Show, Read)
第二步:基本的解析器
- 开关解析器
parseSwitch _ [] = (Nothing, [])
parseSwitch (sname, lname) args@(arg:as)
= if arg == lname || arg == sname
then (Just (OptionSwitch lname True), as)
else (Nothing, args)
- 带值选项解析器
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))
- 多个选项的解析器的聚合
aggregate ps = foldl1 (<|>) ps
- 将解释器重复应用在一串参数上
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))
例子
- 测试例子
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"]
- 解析结果
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}]