TinyJS(JavaScript)に正規表現を

投稿日:2019年04月15日 08時03分27秒

かねてからphpで運用していたアプリにpawfalikiがある。1ファイルで運用できるwikiアプリだ。何かと便利なので開発中のTinyJSを含んだサーバでも運用したいと思っていたが概ね目処がたったのでお知らせしたい。
正規表現は1から作るのは結構たいへんだ。なにせ普段あまり使わない方なので細かい問題が分からない。そこで日本語が使えないとか様々な問題はあるがポーティングに便利なC標準のregex.hをインクルードする方向で実装する。さらに今回の仕様ではlinux/windows共用としたいというのがあるので化石なソフトで申し訳ないがC++Builderに対応するようにした。結論からいうとwindowsではpcreposi.hをインクルードすることで解決できた。さすがにライブラリが古いだけあって色々苦労する。しかもC++Builderくらいしか動かないらしい。vc++とかに移植する場合にはまた悩もうと思う。
大きなバグとしてはregexec(&re, (char*), nbmat, mat, 0)というパターンマッチの関数で許容するマッチ数nbmatを1にするとパターン置換は検出するものの結果をmatに格納しないというものがあった。linuxで結構デバッグしてからポーティングしたので気がついたがwindowsオンリーだと使えないと諦めたかも。
あとREG_EXTENDEDとREG_NOSUBも使えない。wStringはstd::baseic_string互換の自作ライブラリ。概ね互換で動いているっぽいけど足りない関数もある。

使い方を参考にコピペ実装したがphpのpreg_replaceを念頭に実装してある。
参照元はここ
regular-expression

// ------------------------------------------------------------
// replace 本体
int dregex::replace(wString* result, const wString text, regex_t re, const wString replacement, const bool global)
{
    const size_t nbmat = 10;
    regmatch_t mat[nbmat];
    char h_work[1024]={0};
    char* sp = (char*)text.c_str();
    size_t cnt = 0;
    int shift = 0;
    *result = text;
    do {
        if (regexec(&re, (char*)(sp+cnt), nbmat, mat, 0)) {
            if (!cnt) return 1; // replace する事がなかった
                break;
            }
            // グループ文字列をホールドスペースに積む
            vector hs;
            for (int i=1; i<(int)nbmat; i++) {
            int s = (int)mat[i].rm_so;
            int t = (int)mat[i].rm_eo;
            if (s < 0 || t < 0) {
                hs.push_back("");
                continue;
            } else {
                wString tmp((char*)(sp+cnt));
                tmp.copy(h_work, t-s, s);
                *(h_work + t-s) = '\0';
                hs.push_back(h_work);
            }
        }
        // 置換文字列に対するグループ文字列での置換
        wString local_rep = replacement; for (unsigned int i=1; ireplace(mat[0].rm_so+cnt+shift, len, local_rep.c_str());
        shift += local_rep.size() - len;
        cnt += mat[0].rm_eo;
    } while ( global );
    return 0;
}

// relpace 事前コンパイル・インスタンス不要版
int dregex::replace(wString* result, const wString text, const vector pattern, const vector replacement)
{
    regex_t re;
    int global = 1;
    int cflags = REG_NEWLINE|REG_NOSUB;
    wString pat;
    wString temptext;
    if( pattern.size() != replacement.size() ){
        return 1;
    }
    temptext = text;
    int num=0;
        for( size_t i = 0 ; i < pattern.size() ; i++ ){
        //パターンから修飾子を取得
        wString tmp = pattern[i];
        if( tmp[0] == '/' ){
            while( tmp[tmp.length()-1] != '/' ){
                if ( tmp[tmp.length()-1] == 'i' ){
                    cflags |= REG_ICASE;
                    tmp = tmp.substr(0,tmp.length()-1);
                }else if ( tmp[tmp.length()-1] == 'x' ){
                    cflags |= REG_EXTENDED;
                    tmp = tmp.substr(0,tmp.length()-1);
                }else if ( tmp[tmp.length()-1] == 'm' ){
                    cflags &= ~REG_NEWLINE;
                    tmp = tmp.substr(0,tmp.length()-1);
                }else if ( tmp[tmp.length()-1] == 's' ){
                    cflags |= REG_NEWLINE;
                    tmp = tmp.substr(0,tmp.length()-1);
                }else{
                    //not pattern
                    return 1;
                }
            }
            tmp = tmp.substr(1,tmp.length()-2);
        }else{
            // not pattern
            return 1;
        }
        if (regcomp(&re, (char*)tmp.c_str(), cflags&~REG_NOSUB)){
            return 1;
            // syntax error.
        }
        num = dregex::replace(result, temptext, re, replacement[i], global);
        regfree(&re);
        if( num == 0 ){
            temptext = *result;
        }
    }
    return num;
}

あと大きな問題は文字コード。本来C++Builderではutf-8なんぞサポートしてないのだが、内部は無理やりutf-8コードにしている。"表示"などの"\"を扱う文字列をどうしても制御しきれなかった。面倒すぎる。しかしながら表示文字列=ファイル名なので表示と保存、一覧確認の段階でnkfライブラリをかましてutf-8->sjisのファイル名で保存するようにした。ファイル名がsjisで内容がutf-8なんだがまあ問題なく使えてるようなので良しとする。
これで残件はhttp upload、in memory data base、バッチ処理、tarコンテンツの実装くらいか。
http uploadは数百メガのファイルをアップロードしようとするのでその一時ファイル作成に悩む。in memory data baseはorder byやunion,join,SQL構文からSQL実行へのスケジューラなどまだまだ実装するものがてんこ盛り。しばらく先かな。バッチ処理はシステムのバッチスレッドから定期的にスクリプト等をキックする機構。現在はssdpだけ立ち上がっている。tarコンテンツはコンテンツをtarもどきに格納してアクセスするという機能でtarそのものだと高機能かつファイル量が結構あるので自作tarもどきで実装する予定。JavaScriptからはread onlyのフォルダに見えるように実装するのでセキュリティは格段に向上するはずだ。
とはいえ概ね見せられるレベルになってきたのでそろそろwindows版を公開する。STBや組み込み機器、いわゆるIoTでそれなりに有用だと考えている。

[<< JavaScriptのObject.keysを実装]

[え?不正なURI文字列? >>]