1. 解析與 Tab 補全

解析與 Tab 補全 

此頁面描述 sbt 中的解析器組合器。這些解析器組合器通常用於解析使用者輸入,並為輸入任務命令提供 Tab 補全。如果您已經熟悉 Scala 的解析器組合器,則方法大多相同,只是它們的參數是嚴格的。還有兩種額外的方法用於控制 Tab 補全,將在本節末尾討論。

解析器組合器從較小的解析器建立解析器。Parser[T] 在其最基本用法中是一個函數 String => Option[T]。它接受一個 String 來解析,如果解析成功,則產生一個包裝在 Some 中的值,如果解析失敗,則產生 None。錯誤處理和 Tab 補全使這個情況更加複雜,但我們將在本討論中堅持使用 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 }

控制 Tab 補全 

大多數解析器都有合理的預設 Tab 補全行為。例如,字串和字元文字解析器會針對空輸入字串建議基礎文字。但是,要確定 charClass 的有效補全是不切實際的,因為它接受任意謂詞。examples 方法為此類解析器定義明確的補全

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

Tab 補全將使用範例作為建議。控制 Tab 補全的另一個方法是 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 組合器也用於轉換解析器的輸出。

會遞迴呼叫解析器,直到找到沒有可能選擇的簡單情況。