神話のプログラム言語 Odin 正規表現編
今回も小ネタですが、正規表現の操作について、簡単に解説します。
正規表現(regex)を行うには、text/regexライブラリをimportして使用する必要があります。
環境
まず、動作させている私のPC環境は以下の通りです。
- OS: Windows11
- Odin dev-2025-12a-nightly
- Microsoft visual C++ 2022
Odinでの正規表現
odinの正規表現は、match_iterator関数とmatch関数しかありません。
他の言語なら、indexやreplaceなどの関数があるかもしれませんが、odinにはありません。
(そもそも、検知出来たかの判定や、見つけた位置などは、戻り値などでわかる為、逆に不要です。replaceもstrings.replace関数を使う方が、パフォーマンス上、早いです。)
1.iteratorを使った例
regex.create_iterator関数は、グループ毎にループして、正規表現の値を返してくれる関数です。
(regex01ディレクトリの中に、以下のmain.odinを作成して確認します。)
package main
import "core:fmt"
import "core:text/regex"
// iteratorでループする場合
main :: proc() {
// create_iteratorで検索する文字列と正規化するパターンを設定
if it, err := regex.create_iterator("西暦2025年12月23日", `\d+`); err == nil {
defer regex.destroy_iterator( it )
// iteratorで数値を抽出
for capture in regex.match_iterator( &it ) {
fmt.printfln( "capture: %v", capture.groups[0] )
}
}
}
$> odin run .\regex01\
capture: Capture{pos = [[6, 10]], groups = ["2025"]}
capture: Capture{pos = [[13, 15]], groups = ["12"]}
capture: Capture{pos = [[18, 20]], groups = ["23"]}
Capture構造体は、上記の様に、見つけた位置と見つけた文字列を返します。
この場合の方法だと、年の前に数字が入っているのか、月の前に数字が入っているのかわかりません。
なので、下記のようにmatch関数を使って完全一致させます。
2.matchを使った例
regex.match関数を使って、正規表現パターンと完全一致する値を確認します。
(regex02ディレクトリの中に、以下のmain.odinを作成して確認します。)
package main
import "core:fmt"
import "core:text/regex"
import "core:text/match" // text/matchライブラリの追加
// 一括で取りに行く場合
main :: proc() {
text := "西暦2025年12月23日です"
// createで正規化するパターンを設定
if re, err := regex.create( `.+(\d{4})年(\d{2})月(\d{2})日`, {.Multiline, .Unicode} ); err == nil {
defer regex.destroy_regex( re )
// match関数で検索する文字(text)を設定
capture, success := regex.match( re, text )
defer regex.destroy_capture( capture )
fmt.printfln("group[0]:%v group[1]=%v group[2]=%v group[3]=%v", capture.groups[0], capture.groups[1], capture.groups[2], capture.groups[3])
// group[1]以上はパターン内の()がグループなのでそれを表示させている
}
}
create関数の{.Multiline}フラグは検索する文字列に改行が含まれた場合、{.Unicode}フラグはパターン文字内にUTF8文字が含まれる場合に使います。
$> odin run .\regex02\
group[0]:西暦2025年12月23日 group[1]=2025 group[2]=12 group[3]=23
上記の例だと、以前の例と違い、年の前の数字、月の前の数字、日の前の数字とそれぞれ正確な数字を得る事が出来ます。capture変数の最初のgroup[0]は完全一致を意味します。
二つ目以降は、パターン内のグループ項目(())毎の値が入っています。
3.色々な文字列を正規表現にしてみた結果
regex.match関数を使って、複数の正規表現パターンが一致するかを確認します。
(regex03ディレクトリの中に、以下のmain.odinを作成して確認します。)
package main
import "core:fmt"
import "core:text/regex"
import "core:text/match" // text/matchライブラリの追加
// 良く使われる正規表現の例
main :: proc() {
// ひらがなから始まりひらがなで終わる文字列の抽出
regex_proc("東京とっきょときゃ局", `([ぁ-ん]+)`) // c[0]:とっきょときゃ c[1]:とっきょときゃ
// 数字一文字+[-]+英数字文字列+[,]の文字列の抽出
regex_proc("they are all 205-based, even when compiling", `[0-9]-[0-9A-Fa-f].+,`) // c[0]:5-based,
// a+2文字+leの文字列の抽出
regex_proc("orange apple", `a..le`) // c[0]:apple
// 4桁数字+年+2桁数字+月+2桁数字+日の文字列の抽出
regex_proc("西暦2025年12月23日", `.+(\d{4})年(\d{2})月(\d{2})日`) // c[0]:西暦2025年12月23日 c[1]:2025 c[2]:12 c[3]:23
// 数字+[.]+数字+[%]の文字列の抽出
regex_proc("支持率75.8% JNN調査", `(\d+.\d+)%`) // c[0]:75.8% c[1]:75.8
// [-]+数字+[.]+数字の文字列の抽出
regex_proc("気温27.5度", `-?\d+.\d+`) // c[0]:27.5
// 天気は+何かの文字列+です。の文字列の抽出
regex_proc("今朝の天気は晴れです。明日は所々で雨が降ります。", `天気は(\W+)です。`) // c[0]:天気は晴れです。 c[1]:晴れ
// 3桁の数字+[-]+4桁の数字の文字列の抽出
regex_proc("〒265-3004", `[0-9]{3}-[0-9]{4}`) // c[0]:265-3004
}
// 正規表現をするだけの関数
regex_proc :: proc(text, pattern: string) {
if re, err := regex.create( pattern, {.Multiline, .Unicode} ); err == nil {
defer regex.destroy_regex( re )
capture, success := regex.match( re, text )
defer regex.destroy_capture( capture )
fmt.printfln("-- text:[%v] patten:[%v] rtn:[%v] --", text, pattern, success)
if !success do return
for c, i in capture.groups {
fmt.printf("c[%v]:%v ", i, c)
}
fmt.println("")
}
return
}
正規表現のパターン表は、どの言語も同じなので、記載は省きます。
ここでは使われるであろうパターンのみをプログラムで処理しています。
おわりに
今回はregexライブラリの使い方を説明しましたが、regex自体の内部ソースを見ればわかると思いますが、非常に複雑な事を行っています。正規表現を使うとパフォーマンスにも影響しますので、余り使わない方がいいかもしれません。odin作者のgingerBill氏も、そのような事を以前呟いていました。
Discussion