mike-neckのブログ

JavaかJavaFXかJavaEE(なんかJava8が多め)

Swift から regex.h を呼び出してみる

Swift の正規表現はどうなっているのか以前気になって調べたところ、 Foundation には正規表現がないため、 NSRegularExpression を使うとのことで、実際に試しに使ってみたところ、使い勝手がかなり悪くて使うのを諦めました。そこで、 C 言語に regex.h という grep などで使われているらしい正規表現のヘッダーがあるようですので、こちらを Swift から呼び出してみるプログラムを書いてみました。

f:id:mike_neck:20180609045321p:plain

ディレクトリー構造はこんな感じ。

$ 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(&regMatch)
            return NoMatch()
        }
        let regMatchPtr = regMatch.match_ptr
        let regCode: RegCode = regex_exec(regexPtr, &target, regMatchPtr, size)
        if regCode.hasError() {
            regmatch_free(&regMatch)
            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