👼

神話のプログラム言語 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を作成して確認します。)

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を作成して確認します。)

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を作成して確認します。)

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