首页 > 代码库 > 数据结构成为小语言

数据结构成为小语言

数据结构成为小语言

 

面向语言的开发并不一定意味着,一定要自己开发解析器或编译器。这就是说,我们将在下一章学习创建解析器,然后,把这样的解析器和本章介绍的方法照结合起来,去构建一个简单的编译器。可以通过创建数据结构和函数或模块,就能够做很多事,数据结构描述了准备做什么,而函数或模块定义了如何解释结构。

几乎可以用任何语言创建数据结构来表示一个程序,但是 F# 更适合。F# 的文字列表和数组很容易定义,不要求庞大的类型注解;它的联合类型能够创建结构,来表达相关的概念,但却不一定要包含相同类型的数据,可以用它来创建树型结构,这在创建语言时是非常有用的;最后,由于函数可以看作是值,因此,我们可以很容易把函数嵌入到数据结构中,这样,F# 的表达式就能成为语言的一部分,通常作为一个动作,来响应语言的特定条件。

我们先来看一下在FSharp.PowerPack.dll 中预定义的 DSL,通过 Arg(参数)模块能够快速构建命令行参数解析器。它是通过 F# 的联合和列表类型而实现的,先创建小语言,然后,用参数模块中的大量函数进行解释。

参数模块公开了一个元组类型argspec,它由两个字符串和一个联合类型 spec 组成。元组中的第一个字符串指定了命令行参数的名字,元组中的第二项是联合类型 spec,它指定了参数的内容,例如,它可以指定后面跟着的是字符串值,或者只是一个标志;它还可以指定如果发现命令行标记,应该做什么。元组中最后的字符串是关于这个标志做什么的文本描述,当命令行参数错误时会打印到控制台,对程序员来说也是一个有用的注释。

参数模块公开了两个函数用于解析参数:parse,它解析在命令行中传入的命令;和parse_argv,它要求直接传递参数给它。我们应该传递描述命令行参数的 argspec 类型列表的函数,所有被传递给函数的命令行参数都没有前缀 -,最后,是描述用法的字符串。

这个模块还公开了第三个函数,可以传递argspec 类型的列表,并直接写出用法。

下面的例子演示了如何以这种方法构建参数解析器,把从命令行收集的参数保存到标识符中准备以后使用,这里,是把它们定到控制台:

 

let myFlag = ref true

let myString = ref ""

let myInt = ref 0

let myFloat = ref 0.0

let (myStringList : string list ref) = ref []

 

let argList =

    [ ("-set", Arg.Set myFlag, "Sets the value myFlag");

      ("-clear", Arg.Clear myFlag,"Clears the value myFlag");

      ("-str_val", Arg.String(fun x-> myString:= x), "Sets the value myString");

      ("-int_val", Arg.Int(fun x-> myInt :=x), "Sets the value myInt");

      ("-float_val", Arg.Float(fun x-> myFloat:= x), "Sets the value myFloat") ]

 

ifSystem.Environment.GetCommandLineArgs().Length <> 1 then

  Arg.parse

    argList

    (funx -> myStringList := x :: !myStringList)

    "Argmodule demo"

else

  Arg.usage

    argList

    "Argmodule demo"

  exit1

 

printfn "myFlag: %b" !myFlag

printfn "myString: %s" !myString

printfn "myInt: %i" !myInt

printfn "myFloat: %f" !myFloat

printfn "myStringList: %A"!myStringList

 

当运行前面的代码,没有命令行参数,或者命令行参数错误时,程序会输出下面的结果:

 

Arg module demo

  -set:Sets the value my_flag

  -clear:Clears the value my_flag

  -str_val<string>: Sets the value my_string

  -int_val<int>: Sets the value my_int

  -float_val<float>: Sets the value my_float

  --help:display this list of options

  -help:display this list of options

 

当用下面的命令行运行前面的示例时:

 

args.exe -clear -str_val "helloworld" -int_val 10 -float_val 3.14 "file1" "file2""file3"::

 

输出如下:

 

myFlag: false

myString: hello world

myInt: 10

myFloat: 3.140000

myStringList: ["file3";"file2"; "file1"]

 

我特别喜欢这样的 DSL,因为它使问题更清晰,程序需要什么参数,如果接收到参数,应该进行什么样的处理。帮助文本也保存在这个结构中,有两个目的:如果命令行参数有错误,函数会自动输出一个帮助消息;当我们忘记什么参数时,可以提醒。我也喜欢用这种方式创建命令行解析器,因为,我用命令语言写过几个命令行解析器,那可不是令人满意的体验,最终必须写大量的代码,详细说明如何拆分命令行。如果你用.NET 写过代码,那么,通常会花太多的时间在调用字符串类型的IndexOf 和Substring 方法。