1. パースとタブ補完

パースとタブ補完 

このページでは、sbtのパースコンバイナについて説明します。これらのパースコンバイナは、一般的にユーザー入力の解析と、入力タスクおよびコマンドのタブ補完に使用されます。Scalaのパースコンバイナに既に精通している場合、メソッドはほとんど同じですが、引数は厳密です。セクションの最後に、タブ補完を制御するための追加の2つのメソッドについて説明します。

パースコンバイナは、小さなパーサーからパーサーを構築します。最も基本的な使用方法における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"

これらの例では、暗黙の変換によって、CharまたはStringからリテラル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
  • 符号付きInt値を解析するためのIntBasic
  • 1つの10進数または16進数の数字を解析するためのDigitHexDigit
  • Boolean値を解析するためのBool

詳細は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")

タブ補完は、例を候補として使用します。タブ補完を制御するもう1つの方法は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コンバイナを使用してパーサーの出力を変換します。

可能な選択肢がない自明なケースが見つかるまで、パーサーは再帰的に呼び出されます。