Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

Parsimmon のREADME.mdを翻訳しました。

許可はきちんととりました。Can I write article about README.md of parsimmon in Japanese? #44

本記事はhttps://github.com/jneen/parsimmon/blob/f67ed23a575ff5d60871fe671a432f50dc476ca5/README.mdを翻訳しております。最新版を翻訳しているわけではないので注意してください。

Copyright (c) 2011-present J. Adkisson (http://jneen.net). LICENSE: MIT

Build Status

Parsimmon

Parsimmon

(by @jneen and @laughinghan)

Parsimmonは小さいパーサーで作られた大きいパーサーを書くための小さいライブラリです。APIはparsecとPromises/Aにインスパイアされました。

Quick Example

var regex = Parsimmon.regex;
var string = Parsimmon.string;
var optWhitespace = Parsimmon.optWhitespace;
var lazy = Parsimmon.lazy;

function lexeme(p) { return p.skip(optWhitespace); }

var lparen = lexeme(string('('));
var rparen = lexeme(string(')'));

var expr = lazy('an s-expression', function() { return form.or(atom) });

var number = lexeme(regex(/[0-9]+/).map(parseInt));
var id = lexeme(regex(/[a-z_]\w*/i));

var atom = number.or(id);
var form = lparen.then(expr.many()).skip(rparen);

expr.parse('3').value // => 3
expr.parse('(add (mul 10 (add 3 4)) (add 7 8))').value
  // => ['add', ['mul', 10, ['add', 3, 4]], ['add', 7, 8]]

Explanation

Parsimmonのパーサーはテキストのストリームへのアクションと、そのアクションが成功した際に返すオブジェクトもしくは失敗した際のメッセージの挙動を表したオブジェクトです。 例えば string('foo') は 'foo' という文字列をもしストリームの正当が'foo'の時に返し、そうでないとき失敗を返します。

コンビネーターのメソッドの .map を使うとパーサーが返す値を変えることができます。例えば、

string('foo').map(function(x) { return x + 'bar'; })

はストリームが'foo'で始まる時に 'foobar' を返します。

digits.map(function(x) { return parseInt(x) * 2; })

は'12'という文字列が出てきた際に 24 という数値を返します。 .result メソッドは定数をパーサの返す値とすることができます。

パーサの .parser(str) メソッドを呼び出すとその文字列をパースし、statusというプロパティを持ったオブジェクトを返します。statusはパースが成功したかどうかを表します。成功した場合、valueプロパティにパースした結果が入ります。成功しなかった場合、indexとexpectedプロパティにパースエラーの位置と何がパース対象として想定されていたのか示すメッセージが入っています。エラーオブジェクトをパース元の文字列と共に Parsimmon.formatError(source, error) に渡すとhuman-readableなエラーオブジェクトを得ることができます。

Full API

Included parsers / parser generators:

  • Parsimmon.string("my-string") は"my-string"を期待し、"my-string"を返すパーサです。
  • Parsimmon.regex(/myregex/, group=0) はストリームが与えられた正規表現にマッチすることを期待し、マッチしたグループのうちgroupで渡されたグループもしくはすべてのマッチした部分を返すパーサです。
  • Parsimmon.succeed(result) は文字列を何も消費せず result を返すパーサです(訳者注:文字列を消費するというのは、ストリームの現在まで読み込んだ位置を進めることです)。
  • Parsimmon.seq(p1, p2, ... pn) はいくつでもパーサを受け取ることができ、受け取ったパーサが順に現れると期待するパーサです。受け取ったパーサのそれぞれの結果の配列を返します。
  • Parsimmon.alt(p1, p2, ... pn) はいくつでもパーサを受け取ることができ、受け取ったパーサの中で最初に成功したパーサの結果を返し、中間の結果は破棄されます。
  • Parsimmon.sepBy(content, separator) は2つのパーサを受け取り、複数の separator に区切られた content を期待し、 content の配列を返します。
  • Parsimmon.sepBy1(content, separator)Parsimmon.sepByと同様ですが、 content が少なくとも1つあることを期待します。

  • Parsimmon.lazy(f) はパーサを返す関数を受け取ります。受け取ったパーサはパーサが使われて初めて評価されます。これはまだ定義されていないパーサを参照する際に便利です。

  • Parsimmon.lazy(desc, f)Parsimmon.lazy と同様ですが、期待された値に desc をセットします(下の .desc() も参照ください)。
  • Parsimmon.fail(message)
  • Parsimmon.letterParsimmon.regex(/[a-z]/i) と同等です。
  • Parsimmon.lettersParsimmon.regex(/[a-z]\*/i) と同等です。
  • Parsimmon.digitParsimmon.regex(/[0-9]/) と同等です。
  • Parsimmon.digitsParsimmon.regex(/[0-9]\*/) と同等です。
  • Parsimmon.whitespaceParsimmon.regex(/\s+/) と同等です。
  • Parsimmon.optWhitespaceParsimmon.regex(/\s\*/) と同等です。
  • Parsimmon.any はストリームの文字列を1つ消費し返します。
  • Parsimmon.all はストリームの残り全ての文字列を消費し返します。
  • Parsimmon.eof はストリームの終わりを期待します。
  • Parsimmon.index はパースしている現在地点のインデックスを返します。
  • Parsimmon.test(pred) は述部をパスした時にストリームの現在の文字を返します(訳者注:述部とはここではpred引数に渡された関数のことです)。
  • Parsimmon.takeWhile(pred) は現在地点から述部がパスしなくなるまでストリームを消費し返します。

Adding base parsers

Parsimmon.custom を使うことで新しいパーサ(含まれているパーサに類似している)を追加することができます。

function notChar(char) {
  return Parsimmon.custom(function(success, failure) {
    return function(stream, i) {
      if (stream.charAt(i) !== char && stream.length <= i) {
        return success(i+1, stream.charAt(i));
      }
      return failure(i, 'anything different than "' + char + '"');
    }
  });
}

このパーサは次のように今ある全てのパーサと同じように使い構成することができます。

var parser = seq(string('a'), notChar('b').times(5));
parser.parse('accccc');

Parser methods

  • parser.or(otherParser): は parser を試し、失敗したら otherParser を試す新しいパーサを返します。
  • parser.chain(function(result) { return anotherParser; }): This allows you to dynamically decide how to continue the parse, which is impossible with the other combinators. は parser を試し、成功した時に与えられた関数をパース結果と共に呼び出す新しいパーサを返します。与えられた関数は次に試される新しいパーサを返します。 parser.chain を使うことで他のコンビネータにはできない、直接どのようにパースを続けるかを決めることができるようになります。
  • parser.then(anotherParser): は anotherParserparser に続いて現れると期待し、 anotherParser の結果を返します。注意: parser の結果はここでは無視されます。
  • parser.map(function(result) { return anotherResult; }): は parser の結果を与えられた関数で変更します。
  • parser.skip(otherParser)otherParserparser の後ろにあると期待しますが、 parser の結果を返します(訳者注:つまり、otherParserの分ストリームを進めますが後は parser と同様に動作するということです)。
  • parser.result(aResult): は全く同じ動作をするが、 aResult を返すという新しいパーサを返します。
  • parser.many(): は parser が0回以上続くことを期待し、結果の配列を返します。
  • parser.times(n): は parser がちょうど n 回繰り返されることを期待し、結果の配列を返します。
  • parser.times(min, max): は parsermin 回から max 回繰り返されることを期待し、結果の配列を返します。
  • parser.atMost(n): は parser が多くとも n 回繰り返されることを期待し、結果の配列を返します。
  • parser.atLeast(n): は parser が少なくとも n 回繰り返されることを期待し、結果の配列を返します。
  • parser.mark()startvalueend キーを持ったオブジェクトを返します。value はパーサに返された元の値、startend はパースされたテキストのストリーム中の位置を示す値です。
  • parser.desc(description) は失敗した時のメッセージが渡された description である新しいパーサを返します。例えば string('x').desc('the letter x') は 'the letter x' が期待されていた、と失敗した時に示します。

Tips and patterns

これらは伝統的な言語のほとんどのパーサに適応できます。それは何か別のことをあなたのパーサにする必要がある時にも可能です。

ほとんどのパーサにとって次のフォーマットは有用です。

  1. lexeme 関数をパーサで無視したいもの(ホワイトスペース、コメントなど)全てをスキップするために定義します。多様なタイプのlexemeが必要になるでしょう。例を上げると次のようになります。

    javascript var ignore = whitespace.or(comment.many()); function lexeme(p) { return p.skip(ignore); }

  2. 全ての語彙素となるパーサをはじめに定義します(訳者注: 語彙素とは最も小さいパーツとなるパーサのことです)。これらのパーサはネイティブJavaScriptの値を返します。

    javascript var lparen = lexeme(string('(')); var rparen = lexeme(string(')')); var number = lexeme(regex(/[0-9]+/)).map(parseInt);

  3. トップレベルのパーサを1つ以上 lazy を使い先に宣言します。このパーサはまだ定義されていないパーサを参照します。一般的にこのパーサは引数を多く受け取る .alt() の呼び出しという形を取ります。

    javascript var expr = lazy('an expression', function() { return Parsimmon.alt(p1, p2, ...); });

  4. そしてパーサを裏の裏まで作ります。これらのパーサは AST ノードもしくは他のあなたの構成したい言語特有のオブジェクトを返します。

    javascript var p1 = ... var p2 = ...

  5. 最後に、トップレベルのパーサをエクスポートします。はじめに無視すべきものを無視することを忘れないで下さい。

    javascript return ignore.then(expr.many());

Fantasyland

Parsimmon is also compatible with fantasyland. It is a Semigroup, an Applicative Functor and a Monad.