parse
Rebolのparseは、split兼正規表現といいますか。正規表現というものは必ずしもPerlやgrepなんかのあの構文で書く必要は無くて、NFAまでいかなくても、あらかじめその言語のデータ構造にばらしておけば正規表現インタプリタが正規表現自体の字句解析を行うところを省けるので効率的です。はずです。でも事前にコンパイルするならともかくRebolはインタプリタでスクリプトの字句解析器よりは正規表現の字句解析器のほうがまだ速そうなんてことも頭をよぎりますが、通常のデータ操作で動的に正規表現を変更できるなどのメリットもありそうですので、意義を考えるのは後回しにして進みます。
実はRebolよりもHaskellあたりのほうがずっと、正規表現を言語のデータ構造として書き下すのには適してそうな気はします。Haskellのメタプログラミング拡張ではprintf表記などをコンパイル時に展開できるそうですので、正規表現表記された正規表現もコンパイル時に展開できたりしませんか。閑話休題。
parseには第二引数を文字列で渡すパターンとブロックで渡すパターンがあって、前者がsplit動作で分割された文字列の集合を返し、後者が正規表現でtrue/falseを返します。
Refinementsは/allと/caseがあり、/allは付けないとデフォルトで空白で分割したりスキップしたりしてくれますが、/allを付けると余計なことはしなくなります。つまり通常は/all必須。空白が単語や数値の途中にあっても気にしないならいらない。/caseは、デフォルトは大文字小文字を区別しませんが、/caseをつけることでケースセンシティブになります。
split動作のほうは前回使いましたので、残る正規表現動作を。
一般的な構文 | Rebolでの表現 |
---|---|
AB | A B |
A|B | A | B |
A? | A | none |
opt A | |
A* | any A |
A+ | some A |
A{n} | n A |
A{m-n} | m n A |
[AB-Z] | charset [A B - Z] |
. | skip |
.*A | thru A |
.*=A | to A |
(A) | [A] |
$ | end |
文字列表記では不可能だった再帰的なパターンの使用が可能です。括弧で囲んで実行文を書いておけば、そこに到達したときに実行されます。copy varで次のマッチを変数に格納できます。var:で位置を変数に格納できます。:varで再開位置も変えられるようです。あと文字列だけではなくて他のシリーズもマッチングできたり。入れ子構造へはintoで突入。
置換は、実行文としてinsert、remove、changeを埋め込みます。第一引数が位置、第二引数が新しい文字列。
気になった点として、欲張りマッチを行いかつバックトラックはされない様子。
で、例に漏れず電卓書いてみました。
REBOL
stack: copy
push: func [a][
append stack a
]
pop: function[][length result][
length: length? stack
result: stack/:length
stack: copy/part stack length - 1
result
]
dig: charset "0123456789"
val: [copy x [opt "-" any dig opt ["." some dig] ] (push to-decimal x) ]
ter: ["(" sum ")" | val]
pro: [ter ["*" pro (push pop * pop) | "/" pro (x: pop push pop / x) | none] ]
sum: [pro ["+" sum (push pop + pop) | "-" sum (x: pop push pop - x) | none] ]
exp: [sum end]
if parse ask "? " exp [
probe pop
][
print "parse error!"
]
パターン単位でのローカル変数が欲しい。あるいは式を実行した結果をマッチングに使うもの。そうすればstack要らないのに。bindやuseを使っても、パターンを組み上げるのはparseの実行の前に終わってますので無意味なのです。
簡単な方法は無いのかな…再帰できるのと使うのは別なのか。