Skip to content

Instantly share code, notes, and snippets.

@lemonadern
Created February 28, 2023 01:36
Show Gist options
  • Select an option

  • Save lemonadern/fada6f936bc5f218ae4bc0bc53b0411a to your computer and use it in GitHub Desktop.

Select an option

Save lemonadern/fada6f936bc5f218ae4bc0bc53b0411a to your computer and use it in GitHub Desktop.

原文: https://github.com/rust-lang/regex/blob/master/HACKING.md

正規表現ライブラリをハックし、使いこなすためのフレンドリーなガイドです。

Your friendly guide to hacking and navigating the regex library.

このガイドは、RustとCargoに慣れていることと、このクレートの少なくともユーザ向けのガイドをよく読んでいることを前提にしています。

This guide assumes familiarity with Rust and Cargo, and at least a perusal of the user facing documentation for this crate.

このライブラリの実装の背景を知りたいのであれば、 Russ Cox による、有限オートマトンを利用した「正規表現の実装(Implementing Regular Expressions)」シリーズの記事を読むのが最適です。 https://swtch.com/~rsc/regexp/

If you're looking for background on the implementation in this library, then you can do no better than Russ Cox's article series on implementing regular expressions using finite automata: https://swtch.com/~rsc/regexp/

アーキテクチャの概要

Architecture overview

既にご存知かもしれませんが、このライブラリは有限オートマトンを利用して正規表現を実行しています。特に、設計の目標は、正規表現と検索対象のテキストの双方に対して線形の探索をおこなうことです。この設計目標を達成することはそれほど難しくありません。 https://swtch.com/~rsc/regexp/regexp2.html で説明されている Pike VM (Tompson's construction に似ていますが、グループのキャプチャをサポートしています) を実装することで実現できます。 このライブラリにおける Pike VM の実装は src/pikevm.rs にあります。

As you probably already know, this library executes regular expressions using finite automata. In particular, a design goal is to make searching linear with respect to both the regular expression and the text being searched. Meeting that design goal on its own is not so hard and can be done with an implementation of the Pike VM (similar to Thompson's construction, but supports capturing groups), as described in: https://swtch.com/~rsc/regexp/regexp2.html --- This library contains such an implementation in src/pikevm.rs.

(Pike VMを)高速化するのはもっと困難です。 Pike VM における重要な問題点の一つは、ある時点で(オートマトンの)複数の状態になる可能性があり、その状態間でキャプチャ位置をシャッフルする必要があることです。また、Pike VM は、同じ空動作(epsilon-transitions)を何度もたどることに多くの時間を費やしています。

Making it fast is harder. One of the key problems with the Pike VM is that it can be in more than one state at any point in time, and must shuffle capture positions between them. The Pike VM also spends a lot of time following the same epsilon transitions over and over again.

Pike VM を高速化するための一つのテクニックを採用できます:正規表現から1つ以上のリテラルの接頭辞を取り出し、検索対象のテキストから、それらの接頭辞に対するマッチを素早く見つける特別なコードを実行するのです。すると、Pike VM はほとんどの検索で回避され、代わりに接頭辞が見つかった時にだけ実行されます。

We can employ one trick to speed up the Pike VM: extract one or more literal prefixes from the regular expression and execute specialized code to quickly find matches of those prefixes in the search text.The Pike VM can then be avoided for most the search, and instead only executed when a prefix is found.

接頭辞を見つけるためのコードは、 regex-syntax クレートにあります。(このリポジトリの中にです。)リテラルを検索するコードは src/literals.rs にあります。

 The code to find prefixes is in the regex-syntax crate (in this repository). The code to search for literals is in src/literals.rs.

一つ以上のリテラル接頭辞が見つかったときは、 aho-corasick クレートを使った Aho-Corasick DFA にフォールバックします。一つだけのリテラルのときには、Boyer-Mooreアルゴリズムの変形を使います。

When more than one literal prefix is found, we fall back to an Aho-Corasick DFA using the aho-corasick crate. For one literal, we use a variant of the Boyer-Moore algorithm.

Aho-Corasick と Boyer-Moore のどちらも、適切な場合に memchr を利用します。このライブラリの Boyer-Moore バリアントも、初歩的な頻度解析によって memchr の実行に適したバイト数を選択します。

Both Aho-Corasick and Boyer-Moore use memchr when appropriate. The Boyer-Moore variant in this library also uses elementary frequency analysis to choose the right byte to run memchr with.

もちろん、接頭辞リテラルの抽出によってできる高速化には限りがあります。全ての正規表現が接頭辞リテラルを持つわけではありません。

Of course, detecting prefix literals can only take us so far. Not all regular expressions have literal prefixes.

この問題を解決するため、Pike VM を実行するための異なるアプローチであるバックトラックを試みます。実装は src/backtracking.rs にあります。

To remedy this, we try another approach to executing the Pike VM: backtracking, whose implementation can be found in src/backtrack.rs.

バックトラックが高速になりうる理由の一つは、キャプチャグループの過剰なシャッフルを回避できることです。もちろん、バックトラックは実行時間の指数的な増大の影響を受けやすいので、既に訪れた状態へと再び訪れることがないように、訪れた状態を記録しておきます。これによって線形時間での実行が保証されますが、訪れた状態を記録しておくためのメモリとして代償を払っています。メモリが必要になるため、このエンジンは小さな検索文字列と小さな正規表現に対してのみ利用します。

One reason why backtracking can be faster is that it avoids excessive shuffling of capture groups. Of course, backtracking is susceptible to exponential runtimes, so we keep track of every state we've visited to make sure we never visit it again. This guarantees linear time execution, but we pay for it with the memory required to track visited states. Because of the memory requirement, we only use this engine on small search strings and small regular expressions.

最後に、このライブラリの本当の主戦力は src/dfa.rs にある「遅延型の」DFAです。 DFA が Pike VM と異なる点は、DFAがメモリ上で明示的に表現されることと、ある時点につき一つの状態しかありえないことです。

Lastly, the real workhorse of this library is the "lazy" DFA in src/dfa.rs. It is distinct from the Pike VM in that the DFA is explicitly represented in memory and is only ever in one state at a time.

DFA はテキストを計算しながら計算され、検索テキストの各バイトが最大でも1つのDFAの状態になることから、「遅延型」と呼ばれています。これは状態のキャッシュによって高速化されています。

It is said to be "lazy" because the DFA is computed as text is searched, where each byte in the search text results in at most one new DFA state. It is made fast by caching states.

DFA は、指数関数的な状態増加の影響を受けやすいです。(最悪のケースでは、キャッシュの内容に関係なく、入力バイトごとに新しい状態を計算することになります。)

DFAs are susceptible to exponential state blow up (where the worst case is computing a new state for every input byte, regardless of what's in the state cache).

メモリの浪費を避けるため、遅延 DFA は境界付きキャッシュ (bounded cache) を使用します。キャッシュがいっぱいになったらそのキャッシュは削除され、状態の計算が再び始まります。キャッシュがあまりに頻繁に削除されるようであれば、DFAは諦め、検索は前述したようなアルゴリズムの一つへとフォールバックされます。

To avoid using a lot of memory, the lazy DFA uses a bounded cache. Once the cache is full, it is wiped and state computation starts over again. If the cache is wiped too frequently, then the DFA gives up and searching falls back to one of the aforementioned algorithms.

上記の全てのマッチングエンジンは、正確に同じマッチングセマンティクスを公開します。これはテストされています。(後述の、テストに関するセクションを参照してください。)

All of the above matching engines expose precisely the same matching semantics. This is indeed tested. (See the section below about testing.)

続くサブセクションでは、ライブラリの残りの部分についてと、それぞれのマッチングエンジンが実際にどのように利用されているかを説明します。

The following sub-sections describe the rest of the library and how each of the matching engines are actually used.

正規表現のパース

Parsing

正規表現は、このリポジトリの中でメンテされている regex-syntax クレートを使ってパースされます。 regex-syntax クレートは抽象構文を定義し、パースエラーが発生したときの非常に詳細なエラーメッセージを提供しています。

Regular expressions are parsed using the regex-syntax crate, which is maintained in this repository. The regex-syntax crate defines an abstract syntax and provides very detailed error messages when a parse error is encountered.

パースは別のクレートによって行われます。それによってパース部分を他の人が利用することができます。またそうなっている理由は、正規表現ライブラリの他の部分からは比較的切り離されているからです。

Parsing is done in a separate crate so that others may benefit from its existence, and because it is relatively divorced from the rest of the regex library.

regex-syntax クレートは、正規表現から接頭辞リテラルや接尾辞リテラルを抽出する洗練されたサポートを提供しています。

The regex-syntax crate also provides sophisticated support for extracting prefix and suffix literals from regular expressions.

コンパイル

Compilation

コンパイラは src/compile.rs にあります。コンパイラに対する入力は正規表現の抽象構文で、出力はマッチングエンジンが検索をおこなうために利用する一連のオペコードです。(マッチングエンジンは、小さな仮想機械とも考えられます。)

The compiler is in src/compile.rs. The input to the compiler is some abstract syntax for a regular expression and the output is a sequence of opcodes that matching engines use to execute a search. (One can think of matching engines as mini virtual machines.)

一連のオペコードは、非決定性有限オートマトンの特殊なエンコーディングです。特に、オペコードは明確に空動作に依存しています。

The sequence of opcodes is a particular encoding of a non-deterministic finite automaton. In particular, the opcodes explicitly rely on epsilon transitions.

a|b のようなシンプルな正規表現を考えます。これは以下のようにコンパイルされます:

000 Save(0)
001 Split(2, 3)
002 'a' (goto: 4)
003 'b'
004 Save(1)
005 Match

Consider a simple regular expression like a|b. Its compiled form looks like this: (コードブロックは省略)

1列目は命令ポインタで、2列目は命令を表しています。Save 命令は、入力の現在位置がキャプチャされた位置に保存されるべきということを表しています。 Split 命令は、プログラム中の分岐(例:空動作)を表しています。 ab といった命令は、 'a''b' といったリテラルバイトがマッチするべきということを示しています。

The first column is the instruction pointer and the second column is the instruction. Save instructions indicate that the current position in the input should be stored in a captured location. Split instructions represent a binary branch in the program (i.e., epsilon transitions). The instructions 'a' and 'b' indicate that the literal bytes 'a' or 'b' should match.

古いバージョンでは、コンパイルはこのようになっています:

000 Save(0)
001 Split(2, 3)
002 'a'
003 Jump(5)
004 'b'
005 Save(1)
006 Match

In older versions of this library, the compilation looked like this: (コードブロックは省略)

特に、プログラムのある地点から別の地点へと移動するだけの空の命令は削除されました。代わりに、全ての命令には goto ポインタが埋め込まれています。これによってたどらなければならない空動作が一つ減るので、 Pike VM では少しだけパフォーマンスが改善しました。

In particular, empty instructions that merely served to move execution from one point in the program to another were removed. Instead, every instruction has a goto pointer embedded into it. This resulted in a small performance boost for the Pike VM, because it was one fewer epsilon transition that it had to follow.

命令は他にもあり、それらは src/prog.rs で定義およびドキュメントがなされています。

There exist more instructions and they are defined and documented in src/prog.rs.

コンパイルにはいくつかのノブ(knob)があり、残念なことに複雑な不変条件があります。言い換えると、コンパイル結果は2種類のプログラムがありえます: Unicode scalar の値に対して実行するプログラムと、 生の(raw)バイトに対して実行するプログラムです。

Compilation has several knobs and a few unfortunately complicated invariants.Namely, the output of compilation can be one of two types of programs: a program that executes on Unicode scalar values or a program that executes on raw bytes.

前者(Unicode Scalar)の場合では、マッチングエンジンは UTF-8 のデコードをおこない、Unicode のコードポイントを使った命令を実行する役割を担っています。

In the former case, the matching engine is responsible for performing UTF-8 decoding and executing instructions using Unicode codepoints.

後者(raw bytes)の場合では、プログラムは暗黙的に UTF-8 のデコードをおこなうため、マッチングエンジンはバイト列に対して実行することができます。

In the latter case, the program handles UTF-8 decoding implicitly, so that the matching engine can execute on raw bytes.

バイト列ベースのプログラムが必要な遅延DFAを除いたすべてのマッチングエンジンは、 Unicode またはバイト列ベースのプログラムを実行することができます。

All matching engines can execute either Unicode or byte based programs except for the lazy DFA, which requires byte based programs.

一般に、 (1) 遅延DFAはメモリ効率よく符号化をおこなうためにバイトベースのプログラムを必要としており、 (2) Pike VM は Unicode 文字クラスを少ない命令へとインライン化することによって空動作を減らすことができ、大きなメリットがある ために、バイトベースとUnicode Scalarの両方の表現を維持することにしました。

In general, both representations were kept because (1) the lazy DFA requires byte based programs so that states can be encoded in a memory efficient manner and (2) the Pike VM benefits greatly from inlining Unicode character classes into fewer instructions as it results in fewer epsilon transitions.

注意: UTF-8 のデコードは、utf8-ranges クレートによって、コンパイルされたプログラムへと組み込まれています。 このライブラリにおけるコンパイラは、巨大な文字クラス(例:\pL)のサイズを減らすために、よくある接尾辞を取り除いています。

N.B. UTF-8 decoding is built into the compiled program by making use of the utf8-ranges crate. The compiler in this library factors out common suffixes to reduce the size of huge character classes (e.g., \pL).

命令セットが分かれることによって、一般には NFA の実行と遅延DFAの実行のために2つのプログラムをコンパイルしなければならないのが残念なところです。

A regrettable consequence of this split in instruction sets is we generally need to compile two programs; one for NFA execution and one for the lazy DFA.

しかし実際にはそれよりも悪い事がおこります:遅延DFAは一度の一度のスキャンでマッチの開始位置を見つけることができないため、終了位置を見つけたあとに逆方向に検索を実行しなければなりません。後方探索をおこなうためには、逆コンパイルした正規表現が必要になります。

In fact, it is worse than that: the lazy DFA is not capable of finding the starting location of a match in a single scan, and must instead execute a backwards search after finding the end location. To execute a backwards search, we must have compiled the regular expression in reverse.

これはつまり正規表現のコンパイル結果が、一般に3つの異なるプログラムになるということです。 (1) 正規表現が単語境界のアサーションを使わず、
(2) 呼び出し側がサブキャプチャの位置を要求しない 場合であれば、 Unicodeプログラムは必要ないので、 Unicodeプログラムを遅延コンパイルすることができるでしょう。

This means that every compilation of a regular expression generally results in three distinct programs. It would be possible to lazily compile the Unicode program, since it is never needed if (1) the regular expression uses no word boundary assertions and (2) the caller never asks for sub-capture locations.

実行

Execution

執筆時点では、このライブラリには4つのマッチングエンジンがあります:

  1. Pike VM (キャプチャをサポートしている)
  2. 境界付きバックトラッキング(キャプチャをサポートしている)
  3. Literal substring あるいは multi-substring の探索
  4. 遅延DFA (Unicode の単語境界アサーションのサポートなし)

最初の2つのマッチングエンジンだけが、全ての正規表現プログラムを実行できます。しかしそれらは最も遅いです。つまり、 (1) 正規表現に関する様々な事実と、(2) 呼び出し側が何を必要としているか を知っているロジックが必要だということです。この情報を使うことで、どのエンジンを使うべきかを決めることができます。

At the time of writing, there are four matching engines in this library:

  1. The Pike VM (supports captures).
  2. Bounded backtracking (supports captures).
  3. Literal substring or multi-substring search.
  4. Lazy DFA (no support for Unicode word boundary assertions).

Only the first two matching engines are capable of executing every regular expression program. They also happen to be the slowest, which means we need some logic that (1) knows various facts about the regular expression and (2) knows what the caller wants. Using this information, we can determine which engine (or engines) to use.

どのエンジンで実行するかを選択するロジックは src/exec.rs にあり、 Exec type でドキュメントされています。 Exec value は( src/prog.rs で定義された)正規表現のプログラムを内包していて、 検索対象のテキストに対して実際に正規表現を実行するのにひつような全ての要素が含まれています。

The logic for choosing which engine to execute is in src/exec.rs and is documented on the Exec type. Exec values contain regular expression Programs (defined in src/prog.rs), which contain all the necessary tidbits for actually executing a regular expression on search text.

ほとんどの場合で、実行ロジックは単純であり、前述した各エンジンの制限に忠実に従うことになります。 src/exec.rs において最もクセのある部分は、遅延DFAの実行です。前方検索と後方検索が必要で、呼び出し元がキャプチャ位置を要求した場合は、 Pike VM または バックトラックのどちらかにフォールバックすることになります。

The hairiest part of src/exec.rs by far is the execution of the lazy DFA, since it requires a forwards and backwards search, and then falls back to either the Pike VM or backtracking if the caller requested capture locations.

また、Exec type はそれぞれの種類のマッチングエンジンごとに、変更可能なスクラッチスペースがあります。 このスクラッチスペースは探索の最中に利用されます(たとえば、遅延DFAでは、それ移行の探索で再利用されるコンパイルされた状態が格納されます。)

The Exec type also contains mutable scratch space for each type of matching engine. This scratch space is used during search (for example, for the lazy DFA, it contains compiled states that are reused on subsequent searches).

https://github.com/rust-lang/regex/blob/master/HACKING.md#programs

プログラム

Programs

正規表現プログラムは、基本的にはコンパイラが生成するオペコードの列と、正規表現に関する様々な事実(アンカーされているかどうか、キャプチャ名など)を加えたものです。

A regular expression program is essentially a sequence of opcodes produced by the compiler plus various facts about the regular expression (such as whether it is anchored, its capture names, etc.).

regex! マクロ

The regex! macro

regex! マクロはもう存在しません。これは regex クレートの黎明期にコンパイラプラグインとして開発されたものです。当時のマッチングエンジンは Pike VM のみで、 regex! マクロもそれ自体が Pike VMでした。実行時に構築される動的な Pike VM と比較した少ない利点は次のとおりです。

The regex! macro no longer exists. It was developed in a bygone era as a compiler plugin during the infancy of the regex crate. Back then, then only matching engine in the crate was the Pike VM. The regex! macro was, itself, also a Pike VM. The only advantages it offered over the dynamic Pike VM that was built at runtime were the following:

  1. 構文チェックがコンパイル時に行われます。 正規表現がコンパイルできなければ。正規表現を使っている Rust のプログラムはコンパイルできません。
  2. 正規表現のサイズに比例していたオーバーヘッドを削減します。このオーバーヘッドの大部分はヒープアロケーションであり、コンパイラプラグインではほとんど解消されています。
  1. Syntax checking was done at compile time. Your Rust program wouldn't compile if your regex didn't compile.
  2. Reduction of overhead that was proportional to the size of the regex. For the most part, this overhead consisted of heap allocation, which was nearly eliminated in the compiler plugin.

ここで大事なのは、コンパイラプラグインは正規表現エンジンのわずかな高速版であったということです。regex クレートが進化するにつれ、他の正規表現エンジン(DFA, 境界付きバックトラック)や洗練されたリテラルの最適化が進みました。

The main takeaway here is that the compiler plugin was a marginally faster version of a slow regex engine. As the regex crate evolved, it grew other regex engines (DFA, bounded backtracker) and sophisticated literal optimizations.

regex マクロはこれに追従せず、結果として動的なエンジンよりも(劇的に)遅くなりました。

The regex macro didn't keep pace, and it therefore became (dramatically) slower than the dynamic engines.

regex マクロの残された使いみちは、正規表現が正しいことをコンパイル時に保証することだけでした。幸運にも Clippy(Rust の lint ツール)は正規表現をチェックしてくれるので、このユースケースはほとんど Clippy によって置き換えられています。

The only reason left to use it was for the compile time guarantee that your regex is correct. Fortunately, Clippy (the Rust lint tool) has a lint that checks your regular expression validity, which mostly replaces that use case.

さらに、 regex コンパイラプラグインはメンテナンスが終了しています。誰も文句はありませんでした。当時は、そのまま削除することが賢明な選択肢であるように思いました。

Additionally, the regex compiler plugin stopped receiving maintenance. Nobody complained. At that point, it seemed prudent to just remove it.

コンパイラプラグインは戻ってくるのでしょうか?将来のことはわかりませんが、場合によっては動的なエンジンよりも速いものを作る機会がそこにあるのは間違いありません。しかしそれはチャレンジングです!現時点では、そのようなものを作る計画はありません。

Will a compiler plugin be brought back? The future is murky, but there is definitely an opportunity there to build something that is faster than the dynamic engines in some cases. But it will be challenging! As of now, there are no plans to work on this.

Testing

Testing

成熟した正規表現ライブラリには、テストスイートが重要です。このライブラリのテストの一部は、 Glenn Fowler 氏の AT&T テストスイート(執筆時点では、このオンラインプレゼンスは無くなっているようです。)から拝借しています。 テストスイートのコードは、 src/testdata にあります。 scripts/regex-match-tests.py は src/testdata にあるテストスイートを取り込み、 tests/matches.rs を生成しています。

A key aspect of any mature regex library is its test suite. A subset of the tests in this library come from Glenn Fowler's AT&T test suite (its online presence seems gone at the time of writing). The source of the test suite is located in src/testdata. The scripts/regex-match-tests.py takes the test suite in src/testdata and generates tests/matches.rs.

tests/tests.rs には、他にもたくさんの手書きしたテストやリグレッションテストがあります。これらのいくつかは RE2 から来ています。

There are also many other manually crafted tests and regression tests in tests/tests.rs. Some of these tests were taken from RE2.

テストが複雑化する最大の原因は、次の問に答えることに関連しています: 「すべてのマッチングエンジンをチェックするには、どのようにテストを再利用すればよいのでしょうか?」 1つのアプローチとしては、全てのテストを何らかのフォーマット(AT&Tのテストスイートなど)にエンコードして、各マッチングエンジンのテストをコード生成する方法があります。このライブラリでは、テストしたいマッチングエンジンごとに Cargo.toml のエントリポイントを作成する方法を取っています。エントリポイントは以下の通りです:

The biggest source of complexity in the tests is related to answering this question: how can we reuse the tests to check all of our matching engines? One approach would have been to encode every test into some kind of format (like the AT&T test suite) and code generate tests for each matching engine. The approach we use in this library is to create a Cargo.toml entry point for each matching engine we want to test. The entry points are:

  • tests/test_default.rs - Regex::new のテスト
  • tests/test_default_bytes.rs - bytes::Regex::new のテスト
  • tests/test_nfa.rs - 全ての正規表現に対して NFA のアルゴリズムを使い、 Regex::new のテストをする
  • tests/test_nfa_bytes.rs - 全ての正規表現で NFA のアルゴリズムを使い、_任意の_バイトベースのプログラムを使用した Regex::new のテストをする
  • tests/test_nfa_utf8bytes.rs - 全ての正規表現で NFA のアルゴリズムを使い、 UTF-8 バイトベースのプログラムを使用した Regex::new のテストをする
  • tests/test_backtrack.rs - すべての正規表現に対してバックトラックを使い、 Regex::new のテストをする
  • tests/test_backtrack_bytes.rs - 全ての正規表現に対してバックトラックを使い、_任意の_バイトベースをプログラムを使用した Regex::new のテストをする
  • tests/test_backtrack_utf8bytes.rs - すべての正規表現に対してバックトラックを使い、UTF-8 バイトベースのプログラムを使用した Regex::new のテストをする
  • tests/test_crates_regex.rs - quickcheck が生成したランダムな入力に対して、全てのバックエンドが同じように動作することを確認するためのテストです。このテストは RUST_REGEX_RANDOM_TEST 環境変数で有効にする必要があります。(下記を参照)
  • tests/test_default.rs - tests Regex::new
  • tests/test_default_bytes.rs - tests bytes::Regex::new
  • tests/test_nfa.rs - tests Regex::new, forced to use the NFA algorithm on every regex.
  • tests/test_nfa_bytes.rs - tests Regex::new, forced to use the NFA algorithm on every regex and use arbitrary byte based programs.
  • tests/test_nfa_utf8bytes.rs - tests Regex::new, forced to use the NFA algorithm on every regex and use UTF-8 byte based programs.
  • tests/test_backtrack.rs - tests Regex::new, forced to use backtracking on every regex.
  • tests/test_backtrack_bytes.rs - tests Regex::new, forced to use backtracking on every regex and use arbitrary byte based programs.
  • tests/test_backtrack_utf8bytes.rs - tests Regex::new, forced to use backtracking on every regex and use UTF-8 byte based programs.
  • tests/test_crates_regex.rs - tests to make sure that all of the backends behave in the same way against a number of quickcheck generated random inputs. These tests need to be enabled through the RUST_REGEX_RANDOM_TEST environment variable (see below).

遅延DFAと素朴なリテラルエンジンは全ての正規表現に対して利用できるわけではないので、このリストに記載されていません。代わりに、必要なら tests/test_dynamic.rs で遅延DFAとリテラルエンジンのテストをおこなっています。

The lazy DFA and pure literal engines are absent from this list because they cannot be used on every regular expression. Instead, we rely on tests/test_dynamic.rs to test the lazy DFA and literal engines when possible.

テストを何度も実行するとき、 cargo test は全てのエントリポイントを実行するため、全てのコンパイルには時間がかかる場合があります。コンパイル時間を少しだけ短縮するには、 cargo test --test default を使用すると、 tests/test_default.rs エントリポイントのみを使用するようになります。

Since the tests are repeated several times, and because cargo test runs all entry points, it can take a while to compile everything. To reduce compile times slightly, try using cargo test --test default, which will only use the tests/test_default.rs entry point.

ランダムなテストには時間がかかるので、デフォルトでは有効化されていません。ランダムなテストを実行するためには、 cargo test を起動する前に RUST_REGEX_RANDOM_TEST 環境変数を何らかの値に設定します。この変数はコンパイル時に検査されるため、テストが実行されていないようであれば、cargo clean をする必要があるかもしれないことに注意してください。

The random testing takes quite a while, so it is not enabled by default. In order to run the random testing you can set the RUST_REGEX_RANDOM_TEST environment variable to anything before invoking cargo test. Note that this variable is inspected at compile time, so if the tests don't seem to be running, you may need to run cargo clean.

ベンチマーク

Benchmarking

このクレートにおけるベンチマークは、たくさんのマイクロベンチマークによって構成されています。現在は、2つの主要なベンチマークがあります: このライブラリの設立時に採用されたベンチマーク( bench/src/misc.rs )と、様々な最適化をするための新しいベンチマークです。具体的には、後者は bench/src/sherlock.rs にあり、いくつかの解析も含んでいます。また、後者のベンチマークは全て同じ長さの入力に対して実行されるのに対し、前者のベンチマークは様々な長さの文字列に対して実行されます。

The benchmarking in this crate is made up of many micro-benchmarks. Currently, there are two primary sets of benchmarks: the benchmarks that were adopted at this library's inception (in bench/src/misc.rs) and a newer set of benchmarks meant to test various optimizations. Specifically, the latter set contain some analysis and are in bench/src/sherlock.rs. Also, the latter set are all executed on the same lengthy input whereas the former benchmarks are executed on strings of varying length.

また、パースやコンパイルに関するベンチマークもあります。

There is also a smattering of benchmarks for parsing and compilation.

ベンチマークは、メインの regex クレートとは分けて依存関係を管理できるよう、別のクレートにあります。

Benchmarks are in a separate crate so that its dependencies can be managed separately from the main regex crate.

ベンチマークはテストと同じような変わった設定になっています。複数のエントリポイントがあります:

  • bench_rust.rs - Regex::new のベンチマーク
  • bench_rust_bytes.rs bytes::Regex::new のベンチマーク
  • bench_pcre.rs - PCRE のベンチマーク
  • bench_onig.rs - Oniguruma のベンチマーク

Benchmarking follows a similarly wonky setup as tests. There are multiple entry points:

  • bench_rust.rs - benchmarks Regex::new
  • bench_rust_bytes.rs benchmarks bytes::Regex::new
  • bench_pcre.rs - benchmarks PCRE
  • bench_onig.rs - benchmarks Oniguruma

PCRE と Oniguruma のベンチマークは、成熟した正規表現ライブラリとの比較用に存在しています。一般には、この正規表現ライブラリは良好な結果を示しています。(PCREでは単に動作が遅すぎる、あるいはまったく実行できないベンチマークもいくつかあります。)他の正規表現ライブラリのベンチマーク(特にRE2)を追加したいと思っています。

The PCRE and Oniguruma benchmarks exist as a comparison point to a mature regular expression library. In general, this regex library compares favorably (there are even a few benchmarks that PCRE simply runs too slowly on or outright can't execute at all). I would love to add other regular expression library benchmarks (especially RE2).

マッチングエンジンの一つを開発していてベンチマークを見たいのであれば、以下を実行します:

$ (cd bench && ./run rust)

If you're hacking on one of the matching engines and just want to see benchmarks, then all you need to run is:

$ (cd bench && ./run rust)

結果を古いベンチマークと比較する場合、これを試してください:

$ (cd bench && ./run rust | tee old)
$ ... make it faster
$ (cd bench && ./run rust | tee new)
$ cargo benchcmp old new --improvements

If you want to compare your results with older benchmarks, then try:

$ (cd bench && ./run rust | tee old)
$ ... make it faster
$ (cd bench && ./run rust | tee new)
$ cargo benchcmp old new --improvements

ユーティリティの cargo-benchcmp はこちらにあります: https://github.com/BurntSushi/cargo-benchcmp

The cargo-benchcmp utility is available here: https://github.com/BurntSushi/cargo-benchcmp

./bench/run ユーティリティは、PCREとOnigurumaベンチマークに対しても実行できます。./bench/bench --help を参照してください。

The ./bench/run utility can run benchmarks for PCRE and Oniguruma too. See ./bench/bench --help.

Dev Docs

Dev Docs

初めてコードベースへと潜り込むときに、クレートのドキュメントは素晴らしい情報になります。クレートの利用者が実装を気にすることなくインターフェースを利用できるよう、 rustdoc はデフォルトでプライベートクレートメンバのドキュメントを削除しています。普通これは良いことですが、 regex 内部の開発を始めたいときはその限りではありません。クレート内のたくさんのプライベートメンバは rustdoc スタイルのコメントによってよくドキュメントされており、この機会を逃すのは残念なことです。次のようにしてプライベートドキュメントを生成できます:

$ rustdoc --crate-name docs src/lib.rs -o target/doc -L target/debug/deps --no-defaults --passes collapse-docs --passes unindent-comments

When digging your teeth into the codebase for the first time, the crate documentation can be a great resource. By default rustdoc will strip out all documentation of private crate members in an effort to help consumers of the crate focus on the interface without having to concern themselves with the implementation. Normally this is a great thing, but if you want to start hacking on regex internals it is not what you want. Many of the private members of this crate are well documented with rustdoc style comments, and it would be a shame to miss out on the opportunity that presents. You can generate the private docs with:

そして、ブラウザで target/doc/regex/index.html を見るだけです。

Then just point your browser at target/doc/regex/index.html.

内部利用のための developer docs の生成についてのさらなる情報は、rust-lang/rust#15347 を参照してください。

See rust-lang/rust#15347 for more info about generating developer docs for internal use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment