[javascript]正規表現を正規表現で表現できるかな

正規表現自体・・・

呪文

と呼ばれているので

下のテキストから正規表現の部分をパースしなさいとかは、難題

(* 文法   *)
        syntax      =   rule_list /\s*/ ;
        rule_list   =   { ( rule | special_effect | comment ) } + ;
        rule        =   identifier defining_symbol definitions ";" ;
        defining_symbol = "=" | "::=" ;                                     (* 終端記号 '=':EBNF, '::=':BNF *)
        (* 定義   *)
        definitions =   definition { "|" definition } ;                     (* 定義リスト *)
        definition  =   choice ;                                            (* 定義 *)
        (* リスト *)
        choice      =   sequence { "|" sequence } ;                         (* アレかコレかソレかのどれか *)
        sequence    =   exception { [ "," ] exception } ;          		    (* アレとコレとソレの一式。","は空白改行と同義  *)
        exception   =   words, { "-" words } ;          			        (* アレが良いが、コレとソレを除く。 *)
        (* 語彙   *)
        words       =   [ comments ] word [ comments ] ;
        special_effect =  (*$ NO SPACE SKIP $*) "(*$" /[^\\?]*/ "$*)" ;     (* 特殊効果 *)
        comments    =   ( special_effect | comment ) repeat_symbol;
        word        =   ( sq_string | wq_string | wraper | special_text | regexp | identifier ) ;
        comment     =   /(?:\(*))(?![*][/])(.*)(?:*\))/ ;                   (* 注意書き *)
        sq_string   =   /(?:')(?!\\\\)[^']*(?:')/ ;                         (* ' で括られたテキスト *)
        wq_string   =   /(?:")(?!\\\\)[^"]*(?:")/ ;                         (* " で括られたテキスト *)
        bq_string   =   /(?:\`)(?!\\\\)[^"]*(?:\`)/ ;                       (* " で括られたテキスト *)
        (* 囲み   *)
        wraper      =   ( group | option | repeate ) repeat_symbol ;
        group       =   "(" choice ")"  ;                                   (* choiceを明示的に括る表現 *)
        option      =   "[" choice "]"  ;                                   (* あれば尚良し *)
        repeate     =   "{" choice "}"  ;                                   (* 0回以上繰り返す *: 0回以上、+:1回以上 *)    (* TODO choiceを使うと exceptionで[undefined]が返る *)
        regexp      =   /.+/([a-z]*)  ;      
        identifier  =   /[A-Za-z][A-Z_a-z0-9\\x20]*[A-Z_a-z0-9]*/;          (* 識別子 *)
        special_text =  (*$ NO SPACE SKIP $*) "?" /[^\\?]*/ "?" ;           (* 特殊文字列 *)
        repeat_symbol=  [ "*" | "+" | '{' [ number ] ',' [ number ] '}'];   (* 繰返し記号 *)
        number      =   /\d+/ ;

なので

regexp  =  /.+/([a-z]*) ;

とか表記は誤魔化してたけど、

実際にはこれでパースするとテキストの終りの手前までヒットしてしまう。

とある記事で、エスケープ表現の文字を潜り抜けさせる方法が載ってた。

 (¥¥@)[^@]+   ※@は任意の文字

な感じで@のエスケープ表現の2文字@以外の文字のいづれかを繰り返す表現をしていた。

これを真似て

/ ( ( \ / ( ( \ \ / ) | ( [ \ / ) | [^\ / ] ) * \ / ) ( [ a – z ] * ) ) /

/ \ / ( ( \ / ( ( \ \ \ / ) | ( \ [ \ / ) | ( \ ( \ / ) | ( [ ^ / ] ) ) * \ / ) ( [ a – z ] * ) ) /  ※壊れてたので訂正 2025/5/6

※ps.2025/5/8 さっぱり通らなくなることはタマにある。が起きたので再訂正、正規表現デバッガもコピーボタンも2通りの書き方を出力するように変更(DevToolsを開きキャッシュクリア必須)。やはりコノPC何かが変なんだろうね?

const re1 = new RegExp("(\\/((\\\\\\/)|(\\[\\/)|(\\(\\/)|([^/]))*\\/)([a-z]*)", "gim");
const re2 = /(\/((\\\/)|(\[\/)|(\(\/)|([^/]))*\/)([a-z]*)/gim;

※崩れた顔文字ではありません、読みにくいので空白を挿入しました。

を作って試してみた結果は

うまくいってるみたいだが、ヒットした正規表現の半分は顔文字にしか見えない。

しかも、長い。長すぎる~。

ps.2025/5/9

MDNでreplace系で使う置換する関数に渡るパラメータは

replace(/(/{2}.*$|\/\*\/?([^/]|[^*]\/|\r|\n)*\*\/)/, replacer);
👇
function replacer(match, p1, p2, /* …, */ pN, offset, string ) {
  return replacement;
}

で、「名前付きのキャプチャグループ」がある場合はgroupsが付加されて

replace(/(?<COMMENT>/{2}.*$|\/\*\/?([^/]|[^*]\/|\r|\n)*\*\/)/, replacer);
👇
function replacer(match, p1, p2, /* …, */ pN, offset, string, groups) {
  return replacement;
}

となっていたので、名無しの正規表現でもそれなりに色付けできるように上書き修正

普通に

function replacer(match, p1, p2,  … pN, offset, string, groups) {
//  … pN 部分は全部pNに含まれる(だったらいいな的な
  return replacement;
}

みたいに…(残余引数)が使えたらいいけどね。

ps.2025/5/18

自身の表記が通らなかったので、

・・・|(\^\/\])|・・・

を追記したら改行が通ってしまうので

[^/] を
[^/\r\n]に変更

して

/(\/((\\\/)|(\^\/\])|(\[\/)|(\(\/)|([^/\r\n]))*\/)([dgimsuvy]*)/

になったけど、BNFのテキストの正規表現内に

\r\n

があるとダメなのは当然なので

\\r\\n

と表記しても通らない?

原因がよく解らないなぁと思ったら \が通らなくなっていた。

[^/\r\n]は、 / と¥とrとnを除外する意味と\r\nを除外する2つの意味を持ってしまっていた。

色々試してみた結果

x ([^/\r\n])
▲ ([^\r\n/])   \]っぽく処理されてダメだった
▲ ([^/]|[^\r\n]) 論理和だから条件が甘アマ
◎([^/\x0a\x0d]) 非推奨だけど

らしい(微妙

その後も「*/」のパターンが抜けてたり追記する度にエスケープさせるパターンが増える罠に遭遇し続けた結果がコレ

/(\/((\\\/)|(\^\/)|(\[\/)|(\(\/)|(\*\/)|([^/\x0a\x0d]))*\/)([dgimsuvy]*)/

自身はちゃんと判定できるものの、「*/」は末端の「/」と判定しないから、

「/.*/aaaaaaa/」は、「/.*/aaaaaaa/」な正規表現と判定される訳で

正規表現はパーサコンビネーションで解消するしかないっぽい。

そうなるとまた際限がないので、

正規表現を

  • / で始まり
  • 特殊な文字「( ) { } [] . + *等」か その他の文字 の繰り返し
  • / で終わる

の様な表現で括らないとダメな気がする。

/(\/)((\\/)|([^/\x0a\x0d]))+(\/)([dgimsuvy]*)/

のあたりで妥協して

EBNFで使うには文法上の利用方法を見直した方が良さそう。

でも、正規表現の文字クラスの中の文字クラスとかグループとか繰り返しとかを内包しないみたいなんで、[…]は/有無に関わらず通しても良さそう

/(\/)(((\\\/)|(\[.*?\])|([^/\r\n\s]))+)?(\/)([dgimsuvy]*)/gmuy

で、何とかならないかな?

2か所で使っている最短マッチング(x?y)が微妙で、chromeでもnode.jsでも

1つ目はグループを入れて見やすくしたり手を入れると、通常の最長マッチングになり

◎ (\[.*?\])
× ((\[.*)?(\])) xというか通常の最長マッチングになる場合がある

文末に近いコメントの中の ] までマッチングしてしまうが、

/(\\/)((\\\\/)|((\[.*)?(\]))|([^/]))+(\\/)(dgimsuvy*)/  ; (* 正規表現されたテキスト  + /.+/ "/" + { /[dgimsuvy]/ } *)

2つ目は(…)?(\/)とグループにして入れても支障は無い。

どうやら(…)で括ると \[ [ みたいに階層無しに思え最長マッチング(つまり通常のマッチング)してる気もするが([.*?])単体では最短マッチングするので、最短マッチングを階層の上と下で使う分には問題ないけど、その両方の?の両辺をグループにしてしまうとマッチングの位置(position)をうっかり共有してしまってる(VMの変数のネーミングが被ってるかVMのコンパイラには同じに見えている)気がする。

それに、VScodeのjsファイルのエディタで(\\\/)部分がダメで(\\\\/)で通る様にしたのが上の正規表現だったりするので、雑な正規表現は微妙な動きをするのは仕方が無いのかもしれない。




コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA