此頁面描述 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"
在這些範例中,隱式轉換會從 Char
或 String
產生一個文字 Parser
。其他基本解析器建構子是 charClass
、success
和 failure
方法
// 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 中的內建解析器。一些常用的內建解析器是
Space
、NotSpace
、OptSpace
和OptNotSpace
用於解析空格或非空格,無論是否需要。StringBasic
用於解析可能帶引號的文字。IntBasic
用於解析帶正負號的 Int 值。Digit
和HexDigit
用於解析單個十進位或十六進位數字。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").?
解析器組合器的一個關鍵方面是在過程中將結果轉換為更有用的資料結構。此方法的基本方法是 map
和 flatMap
。以下是 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 補全行為。例如,字串和字元文字解析器會針對空輸入字串建議基礎文字。但是,要確定 charClass
的有效補全是不切實際的,因為它接受任意謂詞。examples
方法為此類解析器定義明確的補全
val digit = charClass(_.isDigit, "digit").examples("0", "1", "2")
Tab 補全將使用範例作為建議。控制 Tab 補全的另一個方法是 token
。token
的主要目的是確定建議的界限。例如,如果您的解析器是
("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
組合器也用於轉換解析器的輸出。
會遞迴呼叫解析器,直到找到沒有可能選擇的簡單情況。