1. 解析和选项卡补全

解析和选项卡补全 

本页介绍 sbt 中的解析器组合器。这些解析器组合器通常用于解析用户输入并为 输入任务命令 提供选项卡补全。如果您已经熟悉 Scala 的解析器组合器,那么这些方法大部分相同,只是它们的參數是严格的。还有两种用于控制选项卡补全的附加方法,将在本节末尾讨论。

解析器组合器从较小的解析器构建解析器。Parser[T] 在最基本的用法中是函数 String => Option[T]。它接受要解析的 String,如果解析成功则生成一个包装在 Some 中的值,如果失败则生成 None。错误处理和选项卡补全使这幅图更加复杂,但在此讨论中我们将坚持使用 Option

以下示例假设以下导入:

import sbt._
import complete.DefaultParsers._

基本解析器 

最简单的解析器组合器匹配确切的输入

// A parser that succeeds if the input is 'x', returning the Char 'x'
//  and failing otherwise
val singleChar: Parser[Char] = 'x'

// A parser that succeeds if the input is "blue", returning the String "blue"
//   and failing otherwise
val litString: Parser[String] = "blue"

在这些示例中,隐式转换从 CharString 生成文字 Parser。其他基本解析器构造函数是 charClasssuccessfailure 方法

// A parser that succeeds if the character is a digit, returning the matched Char 
//   The second argument, "digit", describes the parser and is used in error messages
val digit: Parser[Char] = charClass( (c: Char) => c.isDigit, "digit")

// A parser that produces the value 3 for an empty input string, fails otherwise
val alwaysSucceed: Parser[Int] = success( 3 )

// Represents failure (always returns None for an input String).
//  The argument is the error message.
val alwaysFail: Parser[Nothing] = failure("Invalid input.")

内置解析器 

sbt 附带几个内置解析器,定义在 sbt.complete.DefaultParsers 中。一些常用的内置解析器是

  • SpaceNotSpaceOptSpaceOptNotSpace 用于解析空格或非空格,无论是否需要。
  • StringBasic 用于解析可能包含引号的文本。
  • IntBasic 用于解析带符号的 Int 值。
  • DigitHexDigit 用于解析单个十进制或十六进制数字。
  • Bool 用于解析 Boolean

有关详细信息,请参见 DefaultParsers API

组合解析器 

我们基于这些基本解析器来构建更有趣的解析器。我们可以按顺序组合解析器,在解析器之间进行选择,或重复解析器。

// A parser that succeeds if the input is "blue" or "green",
//  returning the matched input
val color: Parser[String] = "blue" | "green"

// A parser that matches either "fg" or "bg"
val select: Parser[String] = "fg" | "bg"

// A parser that matches "fg" or "bg", a space, and then the color, returning the matched values.
val setColor: Parser[(String, Char, String)] =
  select ~ ' ' ~ color

// Often, we don't care about the value matched by a parser, such as the space above
//  For this, we can use ~> or <~, which keep the result of
//  the parser on the right or left, respectively
val setColor2: Parser[(String, String)]  =  select ~ (' ' ~> color)

// Match one or more digits, returning a list of the matched characters
val digits: Parser[Seq[Char]]  =  charClass(_.isDigit, "digit").+

// Match zero or more digits, returning a list of the matched characters
val digits0: Parser[Seq[Char]]  =  charClass(_.isDigit, "digit").*

// Optionally match a digit
val optDigit: Parser[Option[Char]]  =  charClass(_.isDigit, "digit").?

转换结果 

解析器组合器的一个关键方面是沿途将结果转换为更有用的数据结构。为此,基本方法是 mapflatMap。以下是一些 map 的示例以及在 map 之上实现的一些便利方法。

// Apply the `digits` parser and apply the provided function to the matched
//   character sequence
val num: Parser[Int] = digits map { (chars: Seq[Char]) => chars.mkString.toInt }

// Match a digit character, returning the matched character or return '0' if the input is not a digit
val digitWithDefault: Parser[Char]  =  charClass(_.isDigit, "digit") ?? '0'

// The previous example is equivalent to:
val digitDefault: Parser[Char] =
  charClass(_.isDigit, "digit").? map { (d: Option[Char]) => d getOrElse '0' }

// Succeed if the input is "blue" and return the value 4
val blue = "blue" ^^^ 4

// The above is equivalent to:
val blueM = "blue" map { (s: String) => 4 }

控制选项卡补全 

大多数解析器都具有合理的默认选项卡补全行为。例如,字符串和字符字面量解析器将为空的输入字符串建议底层字面量。但是,为 charClass 确定有效补全是不切实际的,因为它接受任意的谓词。examples 方法为这样的解析器定义显式补全

val digit = charClass(_.isDigit, "digit").examples("0", "1", "2")

选项卡补全将使用这些示例作为建议。控制选项卡补全的另一个方法是 tokentoken 的主要目的是确定建议的边界。例如,如果您的解析器是

("fg" | "bg") ~ ' ' ~ ("green" | "blue")

那么,空输入的潜在补全是:console fg green fg blue bg green bg blue

通常,您希望建议更小的片段,或者建议的数量变得难以管理。更好的解析器是

token( ("fg" | "bg") ~ ' ') ~ token("green" | "blue")

现在,初始建议将是(用 _ 表示空格):console fg_ bg_

小心不要重叠或嵌套令牌,例如 token("green" ~ token("blue"))。行为是不确定的(将来应该会产生错误),但通常会使用最外层的令牌定义。

依赖解析器 

有时,解析器必须分析一些数据,然后需要解析更多数据,并且它依赖于之前的解析器。
获得此行为的关键是使用 flatMap 函数。

例如,将展示如何从有效项目列表中选择多个项目,并进行补全,但不能重复。空格用于分隔不同的项目。

def select1(items: Iterable[String]) =
  token(Space ~> StringBasic.examples(FixedSetExamples(items)))

def selectSome(items: Seq[String]): Parser[Seq[String]] = {
   select1(items).flatMap { v =>
   val remaining = items filter { _ != v }
   if (remaining.size == 0)
     success(v :: Nil)
   else
     selectSome(remaining).?.map(v +: _.getOrElse(Seq()))
 } 

如您所见,flatMap 函数提供了先前值。有了此信息,将为剩余项目构建新的解析器。map 组合器也用于转换解析器的输出。

解析器将递归调用,直到找到没有可能选择的简单情况。