許可はきちんととりました。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
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.letter
はParsimmon.regex(/[a-z]/i)
と同等です。Parsimmon.letters
はParsimmon.regex(/[a-z]\*/i)
と同等です。Parsimmon.digit
はParsimmon.regex(/[0-9]/)
と同等です。Parsimmon.digits
はParsimmon.regex(/[0-9]\*/)
と同等です。Parsimmon.whitespace
はParsimmon.regex(/\s+/)
と同等です。Parsimmon.optWhitespace
はParsimmon.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)
: はanotherParser
がparser
に続いて現れると期待し、anotherParser
の結果を返します。注意:parser
の結果はここでは無視されます。parser.map(function(result) { return anotherResult; })
: はparser
の結果を与えられた関数で変更します。parser.skip(otherParser)
はotherParser
がparser
の後ろにあると期待しますが、parser
の結果を返します(訳者注:つまり、otherParserの分ストリームを進めますが後はparser
と同様に動作するということです)。parser.result(aResult)
: は全く同じ動作をするが、aResult
を返すという新しいパーサを返します。parser.many()
: はparser
が0回以上続くことを期待し、結果の配列を返します。parser.times(n)
: はparser
がちょうどn
回繰り返されることを期待し、結果の配列を返します。parser.times(min, max)
: はparser
がmin
回からmax
回繰り返されることを期待し、結果の配列を返します。parser.atMost(n)
: はparser
が多くともn
回繰り返されることを期待し、結果の配列を返します。parser.atLeast(n)
: はparser
が少なくともn
回繰り返されることを期待し、結果の配列を返します。parser.mark()
はstart
、value
、end
キーを持ったオブジェクトを返します。value
はパーサに返された元の値、start
とend
はパースされたテキストのストリーム中の位置を示す値です。parser.desc(description)
は失敗した時のメッセージが渡された description である新しいパーサを返します。例えばstring('x').desc('the letter x')
は 'the letter x' が期待されていた、と失敗した時に示します。
Tips and patterns
これらは伝統的な言語のほとんどのパーサに適応できます。それは何か別のことをあなたのパーサにする必要がある時にも可能です。
ほとんどのパーサにとって次のフォーマットは有用です。
lexeme
関数をパーサで無視したいもの(ホワイトスペース、コメントなど)全てをスキップするために定義します。多様なタイプのlexemeが必要になるでしょう。例を上げると次のようになります。javascript var ignore = whitespace.or(comment.many()); function lexeme(p) { return p.skip(ignore); }
全ての語彙素となるパーサをはじめに定義します(訳者注: 語彙素とは最も小さいパーツとなるパーサのことです)。これらのパーサはネイティブJavaScriptの値を返します。
javascript var lparen = lexeme(string('(')); var rparen = lexeme(string(')')); var number = lexeme(regex(/[0-9]+/)).map(parseInt);
トップレベルのパーサを1つ以上
lazy
を使い先に宣言します。このパーサはまだ定義されていないパーサを参照します。一般的にこのパーサは引数を多く受け取る.alt()
の呼び出しという形を取ります。javascript var expr = lazy('an expression', function() { return Parsimmon.alt(p1, p2, ...); });
そしてパーサを裏の裏まで作ります。これらのパーサは AST ノードもしくは他のあなたの構成したい言語特有のオブジェクトを返します。
javascript var p1 = ... var p2 = ...
最後に、トップレベルのパーサをエクスポートします。はじめに無視すべきものを無視することを忘れないで下さい。
javascript return ignore.then(expr.many());
Fantasyland
Parsimmon is also compatible with fantasyland. It is a Semigroup, an Applicative Functor and a Monad.