Swift の正規表現はどうなっているのか以前気になって調べたところ、 Foundation には正規表現がないため、 NSRegularExpression
を使うとのことで、実際に試しに使ってみたところ、使い勝手がかなり悪くて使うのを諦めました。そこで、 C 言語に regex.h
という grep などで使われているらしい正規表現のヘッダーがあるようですので、こちらを Swift から呼び出してみるプログラムを書いてみました。
ディレクトリー構造はこんな感じ。
$ tree Sources/ Sources/ ├── CRegexp │ ├── c_regexp.c │ └── include │ └── c_regexp.h └── Sample ├── CRegexp.swift └── main.swift
CRegexp
モジュールでは Swift から呼び出すのがややこしそうな部分のコードを C で書いている。 Swift から呼び出す関数は次の通り。
#ifndef C_REGEXP_H #define C_REGEXP_H #include <regex.h> #ifndef REG_OK #define REG_OK 0 #endif typedef struct { int value; } RegCode; typedef struct { regex_t *ptr; } RegPtr; typedef struct { regmatch_t *match_ptr; int size; } RegMatch; typedef struct { regmatch_t *ptr; } RegMatchElement; RegCode MakeRegexResult(int value); RegPtr NewRegex(); RegMatch NewRegMatch(int size); RegCode regex_compile(regex_t *regex_ptr, const char *pattern); RegCode regex_exec(const regex_t *ptr_regex_t, const char *target_string, regmatch_t *regmatch, int size); regoff_t regmatch_start_index(const regmatch_t *regmatch); regoff_t regmatch_end_index(const regmatch_t *regmatch); RegMatchElement regex_extract(const regmatch_t *regmatch, int index); void regex_free(RegPtr *ptr_regex); void regmatch_free(RegMatch *reg_match); #endif
CRegexp.swift
がこの C のコードを呼び出すためのラッパーのような位置づけになる。
import Foundation import CRegexp protocol RegexMatcher { func matches() -> Bool } class Regex { private var rawPointer: RegPtr = NewRegex() let pattern: String init?(pattern: String) { self.pattern = pattern let regexPtr = rawPointer.ptr guard var ptn = pattern.cString(using: .utf8) else { return nil } let regCode: RegCode = regex_compile(regexPtr, &ptn) if regCode.hasError() { return nil } } deinit { regex_free(&rawPointer) } func matcher(for targetString: String, expectedItemCount: Int = 32) -> RegexMatcher { let regexPtr = rawPointer.ptr let size: Int32 = Int32(expectedItemCount) var regMatch: RegMatch = NewRegMatch(size) guard var target = targetString.cString(using: .utf8) else { regmatch_free(®Match) return NoMatch() } let regMatchPtr = regMatch.match_ptr let regCode: RegCode = regex_exec(regexPtr, &target, regMatchPtr, size) if regCode.hasError() { regmatch_free(®Match) return NoMatch() } return RegexMatch(self.pattern, targetString, regMatch, self.rawPointer) } }
これをより汎用的な形を目指そうとするなら、 Regex
はクラスではなくインターフェースにするところだが、あくまでサンプルなのでこのままにする。 マッチさせた結果の RegexMatcher
については、マッチングさせてエラーが発生した場合に Optional
で返すのもちょっと使い勝手が悪かったので、何にもマッチしていない NoMatch
という struct のオブジェクトを作って返すことにした。なお、 C で宣言した構造体については、 Swift にて拡張している。
実際のコードは次のような感じになる。
let target = "foobarbaz" for pattern in ["ba", "^foo", "[0-9]+", "o+b", "ab", "[abr]{2}"] { guard let regex = Regex(pattern: pattern) else { continue } print(regex) let matcher = regex.matcher(for: target) print(target, " matches? :", matcher.matches()) }
動かすと次のようなログが出力される。
$ swift run Sample Compile CRegexp c_regexp.c Compile Swift Module 'Sample' (2 sources) Linking ./.build/x86_64-apple-macosx10.10/debug/Sample Regular Expression : ba foobarbaz matches? : true Regular Expression : ^foo foobarbaz matches? : true Regular Expression : [0-9]+ foobarbaz matches? : false Regular Expression : o+b foobarbaz matches? : true Regular Expression : ab foobarbaz matches? : false Regular Expression : [abr]{2} foobarbaz matches? : true