さっき作った (@MakeNowJust)
https://medium.com/front-end-hacking/javascript-whats-new-in-ecmascript-2018-es2018-17ede97f36d5
構文: (?<= ... )
↑の構文: (?<! ... )
"Pokemon Go"、"Surface Go"の"Go"には
マッチするけれど、
"Golang"の"Go"の部分には
マッチしない。
/(?<=Pokemon |Surface )Go/
"example_test.go"のような
"_test.go"で終わる文字列には
マッチしないが、
"example.go"のような、
その他の".go"で終わる文字列には
マッチする。
/^.*(?<!_test)\.go$/
正規表現の/^/と同じで、
文字列の先頭にのみマッチする。
(直前で任意の文字にマッチしない位置は
先頭しかないので)
/(?<!.)/
ただし、.NET以外は
後読みの中に書けるのは固定の文字列だけなど、
制約がある。
https://go-review.googlesource.com/c/go/+/98760
↓↓↓
このような特徴を持った正規表現エンジンは
(自分の知る限り)
世界でこれだけ!!!
"regexp"パッケージ
golang/goのsrc/regexp/以下
https://github.com/golang/go/tree/master/src/regexp
正規表現をパース→ASTにする: regexp/syntax/parse.go
ASTをProgに変換する: regexp/syntax/compile.go
Progを実行: regexp/exec.go
正確にはProgの実行方法は三種類あり、
与えられた正規表現に応じて最適なものが使われる。
(上のものほど速いが使える適用できる正規表現が少ない。)
regexp/onepass.go
regexp/backtrack.go
regexp/exec.go
Prog
コンパイル後の正規表現。
regexp/syntax/prog.go で定義。
Instという命令列、開始位置、キャプチャの数を 保持する構造体。
Instの種類
バイトコードは regexp/syntax/prog.go で定義。
InstAlt. InstAltMatch, InstCapture...など
バイトコードは大きく2種類に分けられる。
文字を消費するもの: InstRune, InstRuneAny...など
消費しないもの: InstAlt, InstCapture...など
/yes|no/をコンパイルすると‥
InstAlt -> 2, 5
InstRune 'y' -> 3
InstRune 'e' -> 4
InstRune 's' -> 7
InstRune 'n' -> 6
InstRune 'o' -> 7
InstMatch
Progの実行
現在実行しているInstの位置、
キャプチャの状態を保持した構造体をthreadと呼ぶ。
このthreadをリストとして持っておいて、
更新していくことで実行する。
Progの実行(疑似コード)
threads := make([]thread, 0)
threads = prog.add(threads, s, prog.Start, 0)
pos := 0
for len(threads) > 0 {
next := make([]thread, 0)
for _, t := range threads {
next, matched = prog.step(next, s, t.pc, pos)
if matched {
return true
}
}
threads = next
pos += 1
}
return false
prog.addとprog.add
prog.add: 文字列を消費しない命令は進めて、
threadを追加する。
prog.step: 文字列を消費する命令を進めて、 prog.addを呼び出す。
prog.add(疑似コード)
i := prog.Inst[pc]
switch i.Op {
case InstEmptyWidth:
if ... { // \wや\b、^、$の条件を満たしていれば
return prog.add(threads, s, i.Out, pos)
}
return threads
case InstAlt:
threads = prog.add(threads, s, i.Out, pos)
return prog.add(threads, s, i.Alt, pos)
// case ...: その他の文字列を消費しない命令の処理が入る
default:
// 文字列を消費する命令はstepで処理するのでthreadsに追加
return append(threads, thread{pc: pc})
}
prog.step(疑似コード)
i := prog.Inst[pc]
add := false
switch i.Op {
case InstMatch:
return threads, true
case InstRune:
add = i.Rune === s[pos]
// case ...: その他の文字列を消費する命令の処理が入る
}
if add {
threads = prog.add(threads, s, i.Out, pos)
}
return threads, false
InstRepeatAny: /.*/と同じ。
後読みが任意の位置から始まるようにするために必要。
InstMatchProc: n番目の後読みがマッチしたことを保存する。
InstCheckProc: n番目の後読みがマッチしていた場合、
次の命令に進む。
InstMatchProc/InstCheckProcは文字を消費しない命令
Progの実行(疑似コード)
threads := make([]thread, 0)
matched = make([]bool, len(prog.Fork))
for fork := range prog.Fork {
threads = prog.add(threads, s, fork, 0, matched)
}
threads = prog.add(threads, s, prog.Start, 0, matched)
pos := 0
for len(threads) > 0 {
next := make([]thread, 0)
matched = make([]bool, len(prog.Fork))
for _, t := range threads {
next, matched = prog.step(next, s, t.pc, pos, matched)
// 以下、変わらない
prog.add(疑似コード)
i := prog.Inst[pc]
switch i.Op {
case InstMatchProc:
matched[i.Arg] = true
return threads
case InstCheckProc:
if matched[i.Arg] {
threads = prog.add(threads, s, i.Out, pos)
}
return threads
// case ...: 他の命令は省略
default:
// 文字列を消費する命令はstepで処理するのでthreadsに追加
return append(threads, thread{pc: pc})
}
prog.step(疑似コード)
prog.addの引数にmatchedを追加するだけ
なので省略。
Brzozowski, J. A., & Leiss, E. (1980). On equations for regular languages, finite automata, and sequential networks. Theoretical Computer Science, 10(1), 19–35. https://doi.org/10.1016/0304-3975(80)90069-9
Automata DORON DRUSINSKY, F., & Harel, D. (1994). On the power of bounded concurrency I.