変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

正規表現

[EBNF+]リテラルの記述

SpreadSheetでちょっと拡張したEBNFをベースに計算式等を評価させていたけど、

やはり顔文字にしか見えない正規表現とかメンドクサイ(ちょっと変えると拗ねるので)

特にリテラルの記述が面倒で、BNFっぽく

wq string   =   (*$ NO SPACE SKIP $*) '"', /(?!\\\\)[^"]*/, '"' ;

と苦し紛れだったので

wq string   =   '"' ・・・ '"' ;

BNFにこんな感じで書けると、とても楽そう

内部では

const encloseParser = parser.enclose(parser.token('"'), parser.search('"', true));

な感じで[ ” ]と[ ” ]で囲まれた文字リテラルを判定できる(ハズ

encloseはこんな感じで、囲まれたテキストを返す

/**
 * 記号で囲むリテラル用のパーサを作成する
 * @param {Function} leftParser   左囲みtoken
 * @param {Function} rightParser  右囲みsearch
 * @return {Function} 			  生成した連結パーサ
 */
enclose(leftParser, rightParser) {
  const methodName = 'enclose';
  const encloseParser = () => {
    const bkPosition = this.position;
    const leftParsed = leftParser();
    if (!leftParsed.status) {
      return new ParserResult(false, null, bkPosition);
    }
    const rightParsed = rightParser();
    if (!rightParsed.status) {
      return new ParserResult(false, null, bkPosition);
    }
    const encloseText = this.target.substring(leftParsed.position, rightParsed.position - rightParsed.result.length);
    return new ParserResult(true, encloseText, this.position);
  };
  encloseParser.type = methodName;
  return this.parserWraper(encloseParser;
};

見慣れないsearchパーサは、

/**
 * 一致するパターンを検索するパーサを生成する
 * yオプションを付けないregExpバージョン
 * @param {string} text       検索パターン
 * @param {boolean} fEscape   エスケープ処理
 * @return {Function}         生成したパーサ
 */
search(text, fEscape = undefined) {
  const methodName = 'token';
  const len = text.length;
  // エスケープ処理の指定が無い場合
  if (fEscape === undefined) { fEscape = this.fEscape; }
  const rePat = `(${(fEscape) ? '?<!\\\\)(' : ''}${regExpEscape(text)})`;
  const re = new RegExp(rePat, '');
  const searchParser = this.regexp(re, 'gm'); // yオプションを付けないregExpバージョン
  searchParser.type = methodName;
  return searchParser;  // regexpParserでparserWraper済
};

ほぼyオプションの無いパターンを探しにいってしまうregexp処理になっているので

regexpもちょっと変更

/**
  *  正規表現パーサを作成する
  * @param {RegExp} re  正規表現
  * @param {*} options  オプション  ※未設定の場合は、正規表現のオプション+gmyオプション
  * @return {Function}  生成したパーサ
  */
regexp(re, options) { // g:lastIndexから開始、m:複数行、y:lastIndexでのみ判定
  const methodName = 'regexp';
  const source = re.source;
  if (options === undefined) {
    options = (re.hasIndices ? 'd' : '')
      + (re.global ? 'g' : 'g')     /* lastIndex利用が必須なため、常時gオプションを付加 */
      + (re.ignoreCase ? 'i' : '')
      + (re.multiline ? 'm' : 'm')  /* 改行を跨いで処理すたい場合もあるので、常時mオプションを付加 */
      + (re.dotAll ? 's' : '')
      + (re.unicode ? 'u' : '')
      + (re.unicodeSets ? 'v' : '')
      + (re.sticky ? 'y' : 'y')     /* regexpは index=0 のみ判定する方が都合が良いので、常時yオプションを付加 */
      ;
  }
  try {
    re = new RegExp(source, options);
  } catch (ex) {
    const msg = `${rgis.className}.${methodName}: new RegExp fail, ex:'${ex}'`;
    console.error(msg);
    throw new Error(msg);
  }
  /**
   *  生成した正規表現パーサ
   * @return {ParserResult}       パースした結果
   */
  const regexpParser = () => {/* regexp */
    re.lastIndex = this.position;                                       // 再利用時のため
    const result = re.exec(this.target);                        // とりあえず正規表現で実行
    if (result) {
      // 読取りに成功した場合
      const foundText = result[0];
      // ログに追記
      this.mapLog.addSuccessToken(foundText);
      // 読取り位置を更新
      this.position = result.index + foundText.length;
      return new ParserResult(true, foundText, this.position);
    } else {
      // 読取りに失敗した場合
      // ログに追記
      this.mapLog.addFailToken(source);
      return new ParserResult(false, null, this.position);
    }
  };
  regexpParser.type = methodName;
  return this.parserWraper(regexpParser, true, false);
};

普通は、こんなBNF表記を挟まずに直接パーサコンビネーションを作りはじめるハズだが・・・

最終的には「湯出たてのスパゲッティー」にソースや香辛料をかけて、食べやすく「フォークで絡めて」食べる訳で、文法をちょっとイジるにも脳内のイメージを頼りにパーサコンビネーションをイジるから

バグると「脳内のイメージが間違ってる場合」と「勘違いしてコードしている場合」を切り分けるのが非常に難しい。

あと、パーサはまとめてクラス化してるので各パーサにテキストや読込位置をパラメータで渡すのを止めてみたから、SpreadSheetに組み込むのは完成した後になる。

今は、

    {
        title: "sequenceメソッド:正常パターン4",
        testCase: () => {
            const parser = new CoreParser('abc=defghijklmnopqrstuvwxyz')
            const sequenceParser1 = parser.sequence([parser.token('abc'), parser.token('='), parser.token('def')]);
            const rc = [];
            rc.push(sequenceParser1());
            return rc;
        },
        expectedResult: [
            { status: true, result: ['abc', '=', 'def'], position: 7, },
        ],
    },
test[4:sequenceメソッド:正常パターン4] success. => [ 0:{ "status": true, result:[ "0": "abc", "1": "=", "2": "def", ], "position": 7, }, ], 

単体テストを消化中。

これがいっぱい修正漏れが出るんだなぁ(笑

後になって、実行するパーサや結果がどのパーサが作ったのか判るようにtypeを追加してみたり

※用途未定

テスト結果の判定経過を出力させたり

[ 0:{ "status": true, result:[ "0": "abc", "1": "=", "2": "def", ], "position": 7, }, ], 

とか、出来てしまえば不要に思えるけど、バグったら必須な機能をパーサ本体の外においても

パーサ本体が600行にもなってしまう。

ps.2025/5/12

「?」最小マッチングで

/'.*?'/
👉 / ' .* ? ' /          ※見やすいように空白を挟んでみた

と’…..’をお手軽にマッチングできるけど、エスケープ(\’)が混ざるとどうして良いのか判らなかったけど、

'(.*?\\')*.*?'
👉 ' (.* ? \\' )* .* ? '   ※見やすいように空白を挟んでみた

の様に「エスケープ(\’)を0回以上繰り返す」を挟めばOKだった。

気づけば簡単すぎ。(大笑

だがどうやってうまく処理できてるのか?

.* ? \’ から .* ? ‘ に遷移するには

先回りして「\\’」や「’」の位置を把握し存在を確認し、「\\’」や「’」の直後に切り詰めてマッチングし、存在しなければ左部のマッチングをしないのかな?

※\\’が存在しない長いテキストを渡すと重そうだけど

正規表現も

/ .* /
こんなザックリとした表現は意味は読み取れるけど使い物にならないが
👉 / .* ? /
でちょっとは使えるので、
正規表現内で/が出現する状況を挟み込んで
👉 / ((.* ? \/)|(.* ? [/)|(.* ?(/)|(.* ?{/))* .* ? /  ※ / に 4連の (.* ? を仕掛けるぞ(的な
で良いのかもしれない。
※とても十分な検証をする気にはならないががが

で済むのかな?

顔文字表現より見やすい気がするけど、ジェットストリームっぽくドムだから処理が重いかも。



[javascript]コードハイライト(途中経過)

<body>
・・・
<input type="text" id="regexp_pattern" size="80" autocomplete="on" />
<input type="text" id="regexp_option" size="16" autocomplete="on" />
・・・
<input type="button" id="text_color_regexp_text" value="名前付きキャプチャグループ名で色付け" /></br>
結果:<div class="result" id="result"></div>
・・・
</body>
/**
 * サンプルコードのオブジェクトを作成
 */
const regExpSample = new class RegExpSample {
 ・・・
  /**
   * 初期化
   * @memberof RegExpSample
   */
  setup = () => {
    // イベントリスナー登録
    ・・・
    document.querySelector('#text_color_regexp_text').addEventListener("click", this.textColorFromClickButton);
  ・・・
  };
 ・・・
  /**
   * 【名前付きキャプチャグループ名で色付け】ボタンクリック処理
   * @memberof RegExpSample
   */
  textColorFromClickButton = () => {
    try {
      clearnHtmlObjectTextContent('result');
      // 結果を格納するdiv
      const divResult = document.querySelector('#result');
      // サンプル ※関数の引数の初期値に割り当てたら、画面初期表示時の空っぽだった件
      let text = document.querySelector('#regexp_text').value; //.replace('\r\n', '\n');
      // RegExpのパターンとオプションからRegExpクラスのオブジェクトを作成する
      const re = createRegexpPattern();
      // RegExpクラスのオブジェクトを渡しtextColorにお任せ
      let result = textColor(text, re);
      // 読み飛ばした箇所に背景色(黒)を付ける
      result = this.setBackColor(result);
      // textColorの結果を画面に出力
      result = result.replaceAll(/[\r\n]/g, '<br/>');
      divResult.innerHTML = result;
    } catch (ex) {
      console.error(`textColorFromClickButton : ${ex}`);
    }
  };
 ・・・
};
// 初期化
window.addEventListener("load", () => {
  regExpSample.setup();
});

ここまでは、そこそこ簡略化できてる(と思う

/**
 * 色名
 */
const colors = ['black', 'silver', 'gray', 'white', 'maroon', 'red', 'purple', 'fuchsia', 'green', 'lime', 'olive', 'yellow', 'navy', 'blue', 'teal', 'aqua', 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'transparent', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen',];
/**
 * textColorReplacer内で使用する初期色
 */
const defaultColor = new DefaultColor();
/**
 * テキストに色を付ける正規表現のリプレサー
 * replace関数のリプレサーのパラメータは以下の通り
 * @param {string} match   一致した部分文字列
 * @param {string} rs1     1番目のキャプチャグループの結果
 * @param {string} rs2     2番目のキャプチャグループの結果
 * ・・・
 * @param {string} rsN     N番目のキャプチャグループの結果
 * @param {integer} index  分析中の文字列の開始位置
 * @param {string} string  分析中の文字列全体
 * @param {object} groups  キャプチャグループ全体の抽出結果 {blue: undefined, red= 'false' }
 * @returns 置換テキスト
 */
const textColorReplacer = (...args) => {
  const match = args.shift(); // argsの先頭から取出す => 一致した部分文字列
  const groups = args.pop();  // argsの末尾から取出す => キャプチャグループ全体の抽出結果
  const string = args.pop();  // argsの末尾から取出す => 分析中の文字列全体
  const index = args.pop();  // argsの末尾から取出す => 分析中の文字列の開始位置
  // 残りのパラメータは、各キャプチャグループの結果(正規表現での記載順)
  const capGrpResList = args;  // キャプチャグループの結果(配列)
  /**
   * グループ名からテキストを色付け
   * @param {string} groupName 
   * @param {string} match 
   * @returns 色付けしたテキスト
   */
  const colorGroupText = (groupName, match) => {
    // グループ名を分割
    const re = groupName.split('_');
    let element_color = [], not_element_color = [], italic = undefined, underline = undefined, strikethrough = undefined, bold = undefined;
    // reを分析
    groupName = '';
    re.forEach((elm) => {
      if (elm === '') { // '' is undefined.
        element_color.push(undefined);
      } else if (isHex(elm)) { // hex is color code.
        element_color.push(`#${elm}`);
      } else if (colors.some(col => (col === elm))) {
        element_color.push(elm);
        if (element_color.length > 2) { element_color.shift(); /* 先頭を削除 */ }
      } else {
        switch (elm) {
          case 'italic': italic = true; break;
          case 'underline': underline = true; break;
          case 'strikethrough': strikethrough = true; break;
          case 'bold': bold = true; break;
          default:
            not_element_color.push(elm);
            break;
        }
      }
    });
    // 色情報抜きのグループ名からクラス名を作る
    const className = (not_element_color.length > 0) ? (not_element_color.join('_')) : element_color[0];
    // 設定内容からfcol,bcolを決定
    const fcol = (element_color.length > 0) ? (element_color[0] ? element_color[0] : defaultColor.get(match)) : defaultColor.get(match);
    const bcol = (element_color.length > 1) ? (element_color[1]) : 'burlywood';
    // fcol, bcol, italicからstyle属性を決定
    const style = `${(fcol) ? `color:${fcol}; ` : ''}${(bcol) ? `background-color:${bcol};` : ''}${(italic) ? "font-style:italic;" : ''}`;
    // pri, pro
    let pri = '', pro = '';
    // styleが有効ならspan化
    pri = `<span${(className) ? ` class="${className}"` : ''}${(style) ? ` style ="${style}"` : ''}>`; pro = '</span>';
    // 修飾名の場合、pri, proに修飾内容を追記
    if (underline) { pri += `<u>`; pro = '</u>' + pro; }
    if (strikethrough) { pri += `<s>`; pro = '</s>' + pro; }
    if (bold) { pri += `<b>`; pro = '</b>' + pro; };
    //
    return `${pri}${htmlEscape(match)}${pro}`;
  };
  // 内容が格納されているグループのみテキスト化
  const result = Object.keys(groups).reduce((o, groupName) => {
    if (groups[groupName]) { o += colorGroupText(groupName, groups[groupName]) }
    return o;
  }, '');
  return result;
}; // end of textColorReplacer.
/**
 * テキストに色を付ける
 * @param {any} text  テキスト(ハズ
 * @param {RegExp} regExp  色付け正規表現 ※未指定時は適当な正規表現を割り当てる
 * @return {string}      色付け後テキスト
 */
const textColor = (text, re = /(?<blue>true)|(?<red>false)|(?<green>\d+)/gi) => {
  text = `${text}`; // 一応テキスト化しておく
  let preTag = '', proTag = '';
  // 置換してみる
  const result = text.replace(re, textColorReplacer);
  return result;
}; // end of textColor.

colorGroupText関数正規表現をデバッグしやすくなると思った機能を入れすぎたかな



[javscript]RegExpよりリテラルの方がムズイ

あいかわらず、javascriptのソースコードをreplaceAll(/…/g, myReplacer)で色付けしているうちに

`・・・`の器用な使い方を発見

const date = () => {
    const date = new Date();
    return new Intl.DateTimeFormat("ja-JP-u-ca-japanese", {
        dateStyle: "full",        timeStyle: undefined,        timeZone: "Asia/Tokyo",
    }).format(date);
};
const time = () => {
    const date = new Date();
    return new Intl.DateTimeFormat("ja-JP-u-ca-japanese", {
        dateStyle: undefined,        timeStyle: "full",        timeZone: "Asia/Tokyo",
    }).format(date);
};
const test1 (type) => {
  let result = '';
    if(type==='date') { result = `${ date() }`;
    if(type==='time') { result = `${ time() }`};
  return result;
}
test1('date');
test1('time');

すこし粘ってみる

・・・
(type) => {
  let result = `
    ${(type==='date')? `${ date() }` : ''}
    ${(type==='time')? `${ time() }` : ''}
    `;
  return result;
}

で通るらしい。

つまり、

`...${...`.....`...}..${...`.....`...}....`

とバッククォータ・リテラルの中で${…}すると、その中で また ` …..` が作れてしまう。

これをRegExp.replaceAll(text)で字句解析するのは無理っぽいなぁ

RegExp.exec(text)で1トークンごとに読んで、(`)バッククォート来た!別のRegExpに差し替えて・・・末端までたどりついたら、元のRegExpに戻りindexを差替えて・・・かな。

spreadSheetのパーサコンビネーションみたいに、

複数のRegExpパターンでimports/exportsみたいなことをする方が無難かな

# exmport:BQLITERAL
BQLITERAL:  ( ` ( '${'  |  '}'  | '.*' )*  ` )
# exmport:.
# main
COMMENT:    (/{2}.*$|\/\*\/?([^/]|[^*]\/|\r|\n)*\*\/)
・・・
BQLITERAL:  (`) # import(BQLITERAL)
 👈多分この部分は RegExp.exec()の戻り値のgroupsにBQLITERALがあったら、サブルーチン呼び出しみたいな感じかな
# .

それもつらいなぁ

( ( ?<= $ { ] ) | ( ・・・ ) | (?= } ) )

で逃げ切れるかな?

とりあえず今はこの辺

ps.すこし粘ってみた

((\$\{)([^\}]*)(\}))

この${…}パターンを

BQLITERALパターンに

BQLITERAL:  (`(\\\\|\\`|[^`]|\r|\n)*`)

組み込んで

(new)BQLITERAL:  (`(((\${)([^\}]*)(\}))|(\\\\|\\`|[^`]|\r|\n))*`)

みた。

試してみる

---`aaa${222`333444555`666}aaaa$bbbb${222`333444555`666}`---------
---`aaa${222`333${222`333444555`666}444555`666}aaaa$bbbb${222`333444555`666}`---------

サンプル1では

・・・
[BQLITERAL] : '`aaa${222`'
[SYMBOLE] : '333444555'
[BQLITERAL] : '`666}aaaa$bbbb${222`'
[SYMBOLE] : '333444555'
[BQLITERAL] : '`666}`'
・・・
[BQLITERAL] : '`aaa${222`'
[SYMBOLE] : '333$'
[L_BRACE] : '{'
[SYMBOLE] : '222'
[BQLITERAL] : '`333444555`'
[SYMBOLE] : '666'
[R_BRACE] : '}'
[SYMBOLE] : '444555'
[BQLITERAL] : '`666}aaaa$bbbb${222`'
[SYMBOLE] : '333444555'
[BQLITERAL] : '`666}`'
・・・

から

・・・
[BQLITERAL] : '`aaa${222`333444555`666}aaaa$bbbb${222`333444555`666}`'
・・・
[BQLITERAL] : '`aaa${222`333${222`333444555`666}444555`'
・・・

と多少改善はできてる。

けど、サンプル2では

---`aaa${222`333444555`666}aaaa$bbbb${222`333444555`666}`---------
---`aaa${222`333${222`333${222`333444555`666}444555`666}444555`666}aaaa$bbbb${222`333444555`666}`---------
・・・
[BQLITERAL] : '`aaa${222`333444555`666}aaaa$bbbb${222`333444555`666}`'
・・・
[BQLITERAL] : '`aaa${222`333${222`333${222`333444555`666}444555`'
・・・
[BQLITERAL] : '`666}aaaa$bbbb${222`333444555`666}`'
・・・

つまり${…} のネストは3段目でかなり厳しい。

`...${...`...`...}...${...`...`...}...`
や
`...${...`...${...`...`...}...`...}...${...`...`...}...` ※旧よりはマシな結果
`...${...`...${...`...${...`...`...}...`...}...`...}...${...`...`...}...`

色付けで確認してみると

「BQLITERAL_white_black」と色指定の名前に変えて

【↓組み合わせ】ボタンで正規表現を更新して

【色付け】ボタンを押すと・・・

多少は、マシってだけ。



[javascript]正規表現

以前作ったJSのUTF-16をSJISに変換するサンプルが時代の波にモマれ動かなくなってたので作り直し。

それにつけても非推奨が増える一方な感じがするJS。

同様に正規表現のデバッガも動かなくなっていた。(【javascript】 RegExp 正規表現デバッガ

でも正規表現で名前付きキャプショングループを使える様になってきたみたいだから良しとしよう。

※2016年の記事(9年も前か!)だし、単に使いこなせなかったかもしれないけどね。

今回は名前付きキャプショングループのおかげで内容をとても単純になった。

正規表現の/…/の中のパターンとオプションを手入力して【チェック】ボタンを押すと

new RegExp({正規表現パターン}, {オプション})

が動き、RegExpオブジェクトのプロパティを画面下の結果欄に表示する。

だけのハズだったが、

実際 サンプルをRegExe.execメソッドに与えてみないと

文法上正しいだけの正規表現になってしまうので、

【RegExp.exec(サンプル)をループ実行】ボタン

whie(true) {
  if({正規表現パターン}.exec({サンプル}) {
    #結果に出力を追記
  } else {
    break;
  }
  {無限ループチェック}
}

を追加した。

空の正規表現パターンを無限ループ内のexecメソッドに与えると空振りし続け

CPUファンが最大回転数になる上にブラウザが「メモリが~メッセージ」を出すので

空回りしない様にしてる。(つもり

名前付きキャプチャグループ名をCSSの色名にすれば、replaceメソッドに正規表現とリプレッサーを組み込めば、簡単にテキストの色付けができる様になってた。(笑

※色指定方法:キャプチャーグループ名_文字色_背景色と(_)で区切る。

前回の苦労は何だったのかな?(只の徒労だな(昔は大変だったよね?(笑って忘れよう

【名前付きキャプチャグループ名で色付け】ボタン

{サンプル} .replace ( /{正規表現パターン}/{オプション} , {名前付きキャプチャグループ名で色付けする処理} )       

も追加した。

正規表現パターンを拡張しテキストの文字を全て色付けできたら「完成」なんだろうなぁ~

正規表現パターンが完璧なのにテキスト中に色指定無しがあれば、多分「シンタックスエラー」だから

replaceで「<span style=”color: xxx”>・・・</span>」部分を消去すれば、エラってる箇所を抽出できそうダケど、色指定をdarkgrayに差し替えるか色指定が無い部分に下線属性を付加した方が見つけやすそう。

正規表現の最後に何でもヒットするパターンに下線の名前を付ければ、

(?<blue>true)|(?<red>false)|(?<green>\d+)|(?<underline>.+)
で
aaaatruebbbbfalsecccc1234dddd
を色分けしてみると?

予想外の部分に下線が付くと思ったら、全部下線が付いてしまった(合掌

ま、気軽に進めよう(笑

以下、サンプルデータ。

(?<blue>true)|(?<red>false)|(?<green>\d+)
gi
aaaatruebbbbfalsecccc1234dddd

最後に画面が寂しいのでtitleとplaceholder属性を追記した。

今はtitle内容を改行して表示させるには\nと書いてもブラウザが”\n”と思ってしまうので、

テキストエディタ上で直にLFコードを入れる(Enterキー押下)しかなけど、気が付いたら\nって書けば改行してくれる様な気がする。

最近のテキストエディタは「Enterキー」入力をCR、LF、CRLFのいづれかに変更する機能を持つものが多いので何とかなるけどね。

ps.2025/4/16

画面はサンプルなデータを初期表示して試しやすく(具体的にはデバッグ)した。コードも少し変えてみた

結果にオブジェクトの内容を表示する処理を共通化してみた

JSONなオブジェクトでは十分だけど、

for(const propertyName in oobject)

クラスでは静的なプロパティしか見せたくないらしく出てこない。


/**
 * JSON.stringifyのreplacer
 * @param {*} key 
 * @param {*} value 
 */
const stringifyReplacer = (key, value) => {
  if (key !== '' && !value) {
    value = 'undefined';
  }
  return value;
};
/**
 * オブジェクトのプロパティを列挙
 * @param {object} object オブジェクト
 */
const objectPropertyStringify = (object) => {
  let ar = {};
  // for(..in object)
  for (const id in object) { ar[id] = object[id]; }
  // Object.keys(object)
  const keys = Object.keys(object);
  keys.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // Object.getOwnPropertyNames(object)
  const props = Object.getOwnPropertyNames(object);
  props.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // Object.getOwnPropertyNames(obj.__proto__)
  const prototypeProps = Object.getOwnPropertyNames(object.__proto__);
  prototypeProps.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // 纏め
  const txts = [];
  for (const id in ar) {
    // typeof value !== 'function' で メソッドを排除
    if (typeof object[id] !== 'function') {
      txts.push(`${id}:${textColor(ar[id])}`);
    }
  }
  //
  return `<ul><li>${txts.join('</li><li>')}</li></ul>`.replace('<li></li>', '');
};

アレレ?配色が効かない!

/**
 * JSON.stringifyのreplacer
 * @param {*} key 
 * @param {*} value 
 */
const stringifyReplacer = (key, value) => {
  if (key !== '' && !value) {
    value = 'undefined';
  }
  return value;
};
/**
 * オブジェクトのプロパティを列挙
 * @param {object} object オブジェクト
 */
const objectPropertyStringify = (object) => {
  let ar = {};
  // for(..in object)
  for (const id in object) { ar[id] = object[id]; }
  // Object.keys(object)
  const keys = Object.keys(object);
  keys.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // Object.getOwnPropertyNames(object)
  const props = Object.getOwnPropertyNames(object);
  props.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // Object.getOwnPropertyNames(obj.__proto__)
  const prototypeProps = Object.getOwnPropertyNames(object.__proto__);
  prototypeProps.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // 纏め
  const txts = [];
  for (const id in ar) {
    // typeof value !== 'function' で メソッドを排除
    if (typeof object[id] !== 'function') {
      txts.push(`${id}:${textColor(ar[id])}`);
    }
  }
  //
  return `<ul><li>${txts.join('</li><li>')}</li></ul>`.replace('<li></li>', '');
};

にしている。

後、文法解析用の字句解析にReExpの(…)|(…)|…|(…)な書き方用【↓組み合わせ】機能も追加。

ちゃんと字句解析できてるかと思いきや「初期色」が読み飛ばされていた。

キャプチャー名が名前以外の場合は適当に色を割り当ててます。(今は2色を交互に使ってる。

※2025/4/17 : 配色が赤・緑・赤・赤・緑・赤・赤・緑と変なので修正、空白にも配色してたので修正。読飛ばされた箇所は背景色を黒にしてみた。早速「:」忘れてたのに気が付く。

長いログを見ても判りにくいのでexecを実行させる際に「macthしたテキストのみ」結果に出るチェックボックスを追加してみた。

※2025/4/17 : 名無しキャプチャーグループのみの正規表現でも表示する様に修正

なるほど、BINOP単項演算子に見えたか

完成までの道程はまだ遠い

※適当なソースを貼って遊ぶとオモシロイかもしれないけど、ページを「保存」してローカルで遊びましょう。

/* ・・・ */の複数行コメントの字句解析が思わしくない。

  • Windowsから貼ると改行がCRLFになると正規表現のmオプションが暴走しやすい
    • 仕方がないからCRLFはLFに置換
  • /* コメント1 */ コード /* コメント2 */」と複数のコメントがあると
    • /* コメント1 */ コード /* コメント2 */」と一括りにしてしまう
  • /* コメント1 / コード / コメント2 /コード / コメント3 /」と複数のコメントがあると
    • / コメント1 / コード / コメント2 /コード / コメント3 */」と一括りにしてしまう
  • パターンを先読みアサーション((?=))、([\s\S]*)、後読みアサーション((?<=))
    • (?=ここから/*) ([\s\S]*) (?=*/ここまで)」も
    • ここから/* * */ここまで * ここから/* * */ここまで」と一括りにしてしまう
  • 初期表示のサンプルは/* … */は1つだけなので2度コピペすると↑を際限できます
  • とりあえず良さげなパターンが見つかった。
    • COMMENT: (/{2}.$|\/\*\/?([^/]|[^*]\/|\r|\n)*\*\/)
      • \/\*:つまり / *
      • [ \ s | \S ]* を使うと 最後に見つけた * / までブチ抜けくので
      • \/?([^/]|[^*]\/|\r|\n)* で頑張る
        • 先頭の\/?:/*直後に/があった場合のお守り
        • ([^/]|[^*]\/|\r|\n)*: */ 以外のパターンのつもり
          • ( ) * で中のパターンを0回以上繰り返し
            • [^/] | [^*] \/ | \r | \n
            • 非 / or 非 * and / or CR or LF  であるから
          • 非 / 非 * and / 改行のパターンを0回以上繰り返すパターンの意味
      • \*\/:つまり * /
    • CR: ([\r|\n]+)
    • 記事にペーストなんか違う?勝手に斜体になってる???
    • まさかWordPressの記事入力のエスケープシーケンスまで絡んでくるとは思わなんだ

結果に改行がそのまま出力してたので<br/>に変えた。

ついでに空白も&nbsp;や&emsp;に変えたらなぜか色設定が消えた!(あ、タグの中に空白がいっぱい

font-familyの設定もイジることにした見やすくなった。(結果オーライ

ps,2025/4/18

’ ・・・ ’の字句解析が思わしくない。

  • \’ ・・・・ ‘ も判定してしまう。
    • LITERAL:    (‘[^’]*’) では不完全らしいから
      • WQLITERAL:  (“[^”]*”) や QLITERAL:   (‘[^’]*’) もダメだろう。
  • でも普通はいきなり \’ なんて出てこない

コードの中の正規表現の後がズタズタだった・・・(涙

spreadSheetを作ってた時もBNF記述でハマってた。パーサーコンビネーターで作ってたから厳密な字句解析をすっ飛ばして token === ‘/.*/{flags}’ でいいや的な誤魔化しができた。

正規表現でコードハイライトのラスボスは正規表現

嫌すぎ

ps.2025/4/18:多少改善できたので更新。

TODO:/\\/な正規表現が苦手で、以下ボロボロになる。

text = text
  .replaceAll(/\\/g, '\\\\') 👈今この辺で滞っている
  .replaceAll(/\"/g, '\\"')
  ;
copyClipboardText(`new RegExp("${text}", "${flags}");`);

やっと片付いた(かもしれない

COMMENT:    (\/{2}.*$|\\/\\*\\/?([^/]|[^*]\\/|\\r|\\n)*\\*\\/)
CR:         ([\\r|\\n]+)
SPACE:      ([\\s]+)
BQLITERAL:  (\`(\\\\\\\\|\\\\\`|[^\`]|\\r|\\n)*\`)
LITERAL:    (\'(\\\\\\\\|\\\\\'|[^'])*\')
WQLITERAL:  ("(\\\\\\\\|\\\\"|[^"])*")
REGEXP:     (\\/(\\\\\\\\|\\\\/|[^\\/])*\\/[a-z]*)
REGION_B:   (s*#regions+[a-zA-Z0-9]+.*$)
REGION_E:   (s*#endregion$)
L_BRACE:    ({)
R_BRACE:    (})
L_PAREN:    (\\()
R_PAREN:    (\\))
L_BRACKET:  (\\[)
R_BRACKET:  (\\])
COMMMA:     (,)
COLON:      (:)
DOT:        (\\.)
BINOP:      (\\|\\||&&|===|!==|!=|==|\\+|<|>|\\?|<=|>=|-|\\*|\\/|%|=|!)
DELIMITER:  (;)
NULL:       (null)
VALUE_TYPE: (void|integer|bool|string)
SCOPE:      (public|private|friend|static)
THIS:       (this)
NEW:        (new)
BOOL:       (true|false)
SYMBOLE:    ([_a-zA-Z0-9]+)
NUMBER:     ([1-9][0-9]*[.]?[0-9]*)
※オプションは「gim」指定時のみ有効。

から【↓組合せ】ボタンで論理和で結合したパターンを貼り付ける。

このパターンとオプションからVScodeに貼ると、自動的にフォーマッタが働きグチャグチャになるので

"・・・"

とペーストする部分をクリックした後にそ~っとペーストしなければいけない。

※【コピー】ボタンでエスケープ処理済みのテキスト(↓参照)をクリップボードにコピーさせてからファイルにペースト。

const re = new RegExp(
  "/(?<COMMENT>\\/{2}.*$|\\/\\*\\/?([^/]|[^*]\\/|\\r|\\n)*\\*\\/)|(?<CR>[\\r|\\n]+)|(?<SPACE>[\\s]+)|(?<BQLITERAL>`(\\\\\\\\|\\\\`|[^`]|\\r|\\n)*`)|(?<LITERAL>'(\\\\\\\\|\\\\'|[^'])*')|(?<WQLITERAL>\"(\\\\\\\\|\\\\\"|[^\"])*\")|(?<REGEXP>\\/(\\\\\\\\|\\\\\\/|[^\\/])*\\/[a-z]*)|(?<REGION_B>s*#regions+[a-zA-Z0-9]+.*$)|(?<REGION_E>s*#endregion$)|(?<L_BRACE>{)|(?<R_BRACE>})|(?<L_PAREN>\\()|(?<R_PAREN>\\))|(?<L_BRACKET>\\[)|(?<R_BRACKET>\\])|(?<COMMMA>,)|(?<COLON>:)|(?<DOT>\\.)|(?<BINOP>\\|\\||&&|===|!==|!=|==|\\+|<|>|\\?|<=|>=|-|\\*|\\/|%|=|!)|(?<DELIMITER>;)|(?<NULL>null)|(?<VALUE_TYPE>void|integer|bool|string)|(?<SCOPE>public|private|friend|static)|(?<THIS>this)|(?<NEW>new)|(?<BOOL>true|false)|(?<SYMBOLE>[_a-zA-Z0-9]+)|(?<NUMBER>[1-9][0-9]*[.]?[0-9]*)",
  "gim");

な感じなら、使用しているjavascriptファイルは大体OK(だと思う

リテラル系の「 \ \ \ \ \ \ \ \ 」:「\」8キャラが酷い。

  • javascriptファイル上の「 \ \ \ \ \ \ \ \ 」:「\」8キャラ
    • をjavascriptエンジンが読み込んで
    • 「\\」を「\」に変換するので
    • \ \ \ \ 」:「\」4キャラのデータになる
      • これを渡されたRegExpクラスも
      • 「\\」を「\」に変換するので
      • \ \ 」:「\」2キャラのデータになる。

なのでRegExpに「 \ 」を2キャラ渡すために

javascriptのソースコードには「 \ \ \ \ \ \ \ \ 」と8キャラ書くことになる

なんてこったい。(知ってたけど

ついでにココ(段落)に貼るとWordPressのエスケープシーケンスが即反応するので、専用のプラグインに貼る方がいい。

ps.2025/4/19:少し調整

・テキストエリアでタブキーを入力できる様にした

・正規表現のキャプチャーグループ名に文字色と背景色を指定できるようした

COMMENT_RED_000000 な感じに書くと

色付け処理で、

グループ名を(_)で区切って「グループ名_文字色_背景色」な感じで色付けできる。

色番号の「#」はキャプチャーグループ名に使えないので「#」を除いた16進表記のみ書く。

これで、ちょっと「(」が読みにくいので色を変えるとかが簡単になった。

ps.2025/4/23

XMLのツリービュー表示で使う正規表現のデバッグをしていたら

正規表現
(?<attr_name>[-:a-z]+)(?<attr_sep>=)(?<attr_value>"[^"]*")|(?<attr_name>[-:a-z]+)|(?<SP>[\t\s\r\n]+)
サンプル
<style:style style:family="paragraph" style:name="a213">

一度のexec実行で複数のグループがヒットする正規表現の場合には・・・

[attr_name, attr_sep, attr_value] : '{attr_nameの抽出結果のみ}'

と最初の抽出結果しか表示してないコトに気付いたので

[キャプチャ1名]:'{キャプチャ1抽出結果}'、・・・[キャプチャn名]:'{キャプチャn抽出結果}'

の様な感じに変更した。

隣の「色付け」ボタンを押すとキャプチャ対象外の文字は赤で背景色が黒で表示するので、

あ、「<」と「>」を無視してるじゃん!

とか判りやすいと思う。



【JavaScript】正規表現の\sではまる

空白でテキストを区切る場合

text.split(” “)で十分だけど

タブや改行でも区切りたかったので

text.split(/\s/g)にすると

ECMAScript 準拠なら、\s: 空白文字 ([ \t\f\r\n\v])だからいいかな?と思った。

同様に

text.match(/:[^\+…\’\s]+/g)で、SQLのパラメータっぽい事を処理しようと

「select * from table1 where aaa =’:コード[20]’」は予定通り「:コード[20]」が取れた。

「select * from table1 where aaa =’:コード{20}’」は予定通り「:コード{20}」が取れた。

しかし、

「select * from table1 where aaa =’:コード(20)’」は予定外の「:コード」が取れた。

これでは困るので\sのかわりに\u0020を使って

text.match(/:[^\+…\’\u0020]+/g)とすると

「select * from table1 where aaa =’:コード(20)’」は予定通り「:コード(20)」が取れた。

ところが、サンプルを作ってみると異常は起きない。

特定の環境か使い方でしか起きない様だ。

そうだったら恐ろしいけど、よくよく見ればコードミスかな?



構文解析と云えば、BNF か ABNF か EBNFかな?作れたのは字句解析まで・・・orz

JavaScriptでRegExpだけで ソースコードの構文を構文解析しようとすれば、すぐに無理が来る。
昔から構文の説明する時にはBNF(バッカス・ナウア記法)が使われてきた。
ABC ::= A B C
な感じの書き方なので、本当に難しそうな文法の教科書向き。
しかし、繰り返し表現が無く
数字 ::= 0|1|2|3 … |9
数値 ::= 数字, 数値 | 数字
の様に再起表現に頼らざるを得ないので、どうしても説明が長くなるが、右寄せあるいは左寄せのいづれか一方のみの再起表現を使用すると制限を加えればパーサーは作りやすい仕様になっている。
EBNFは、
ABC = “A” , “B”,  “C” ;
な感じに書き方で、{ …} な繰り返し表現もあり、簡単な表現でパーサが書けそう。
本のBNFの記述でも {…} は0回以上の繰り返し可能なブロック、[…]は省略可能なブロック な表現を見かけたが、そう云うよく見かける拡張機能を組み込んだのがEBNFなのだろう。
XMLの文法を表現するために作られたという話もあるし、
誰もが心の中でBNFを勝手に拡張しているのは間違いない。
ただ、拡張の仕方は皆バラバラで方向性もマチマチなので、BNFを規約という形で拡張する様なことはC++の規約化と同様に「厳密にイミフな表現が可能」になり、愚の骨頂と云える様な結末に至るだろう。
※UTF-8で多国語のテキストを1つのHTMLにまとめることが出来るようになった、だからと云って万人がそれを読みこなせるハズも無く、多国語テキストの方向性に使い倒せるの人は極わずか人ダケということだ。
ABNFは、
ABC = %65  %66  %67 ;  コメント
な感じに書き方で、主に通信プロトコルの説明に向いているそうだ。
確かにアナライザーのログとにらめっこをするなら A や 漢 の文字表現より、 コード表現の方が便利そうだ。
・・・
まじめにパーサを作るならいづれかを選択するのが筋だろう。
でも、JavaScriptのRegExpのexecを字句解析に使う場合には
一般的なパーサの文法に縛られる必要な無いので、
“%%”
事前に定義したいJavaScriptのソースのコード
“%%”
トークン名=    (?: (“/” RegExpの字句解析のパターン “/”)  | ( トークン名) ){1,99}   “;”  “%%” returnでパーサーに答えを返す処理 “%%”

※RegExpの字句解析のパターンを複数書く場合はスペースで区切る。
※トークンの構文に対応するJavaScriptのソース;では字句解析のパターン中の(…)に対応する変数$1…$99 や $_や$@なども使える。
※少し工夫すれば、xxx: ([A-Z0-9_]+) ;  シンボル { return $1; } と書いても何とかなるハズ。
※処理を”%%”で括るのは、RegExpの/mでやっつけで処理したいから・・・(笑
・・・で、十分な気がする。
鋭意作成中・・・
謎の現象が発生。
1.{ 処理・・・処理 }
正規表現 (\{)((?:\\\{|\\\}|[^{}])+)(\}) が
1個目の{ ~最後の} までを処理してしまい、
“{“、その他、”}”、?EOT? と、解釈してしまった。
処理の結果は不可解だけど、
このパターンなら、途中の}に目もくれずできる限り遠くの}まで探索してくれることを覚えておいて損は無いだろう。
とりあえず、 (\{)([^{]+)(\}) で済ませたが、(\\{)((?:\\\\}|[^\\}])*)(\\}) が正解のようだ。
$1…$nに割り当てない文字のグループ表現(?: … )を使い、
}のエスケープシーケンス または }以外の文字を (?:\\\\}|[^\\}]) と表現すれば * で繰り返せばOK
2./RegExpのパターン/
(\/((?:\\\/|[^\/])+)\/) が1と同様に、
1個目の{ ~最後の} までを処理してしまい、
“/”、その他、”/”、?EOT? と、解釈してしまった。
とりあえず、 (\/([^\/]+)\/) で済ませるがが、やはり、(\\/)((?:\\\\/|[^\\/])*)(\\/) が正解のようだ。
3.字句解析の2周目の結果が・・・
NULL・・・NULL、””となって、無限ループ。
とりあえず、
最初にNULL以外で見つかった字句が空文字列の場合は
無視してlastIndexを1文字進める。
・・・
※正規表現の\\が多いのはnew RegExpのパラメータの文字列として組み込むため、\単体ではjavascriptのテキストのエスケープシーケンス扱いになるので\\と書いて\の文字扱いしてもらうため。
4.改行が空白として処理される
改行より手前に空白のトークンを宣言すると、空白扱いになっていたので、順を入れ替える。
正規表現を1つにまとめexecの結果で最初に文字列が入っている位置でトークンを分類する場合、トークンの順番を入れ替えるとexecの結果も入れ替わるので、各トークンの正規表現のテストパターンをトークン登録前のチェックでexecの結果の長さから、推定できるようにメモを取っておく。
やっと変な挙動の症状は落ち着いてきた。
最初は、{ 処理リスト } としていたが、処理リストの中のの{や}を¥{、¥}で書くのはやはり面倒。
{{処理リスト}}に変えてみると、”{”演算子が反応してしまったので・・・
%%処理リスト%%に変えてたのが・・・
想定外の文字列が混入していてもRegExp.exec( ) は、ガン無視するので
トークン:「その他」を追加。
RegExpの説明を読んでいると、

/<.*?>/ は “<foo> <bar>” の “<foo>” にマッチしますが、/<.*>/ は “<foo> <bar>” にマッチします。

というのを発見。.*の使用は要注意らしい。
もっとも、パターンの末尾以外に .* を使うと最後まで突っ走りそう、しかも今回はregExpのマルチラインモード(m)なので実質的に .* の使い道がないと思ってたので[^ストッパー文字]*[ストッパー文字]な感じに書いているので .*?[.*を止めるストッパー文字] で済むならストッパー文字を2度書きせずに済む。
但し、”*****************” は、'(“.*?”)’ と書けそうだが ”********\”*********” で破綻してしまうので、'(\\”(?:\\\\”|[^\\”])*\\”)’になっている。
各トークンの情報は格納できるようになった。
しかし、各トークンにパーサーの処理を組み込もうとするとやはり難しい。
トークンの名前が = の左側か右側かで、やるべきことが違うからだ。
なので、
1.RegExp.execでトークンに分解。
2.各トークンの処理はセルフチェックに抑える。
3.下記のEBNFの文法を・・・

%%処理リスト%%

トークン名宣言= (引用符文字列 | 正規表現 | トークン名参照) { 演算子   (引用符文字列 | 正規表現 | トークン名参照) }  “;” %%処理リスト%%

・・・

トークン名宣言= (引用符文字列 | 正規表現 | トークン名参照) { 演算子   (引用符文字列 | 正規表現 | トークン名参照) }  “;” %%処理リスト%%

サブルーチンの呼び出しで、なぞっていくことにする。
そうすることでトークン名を宣言版と参照版に事前に区別して呼び出すことができるからだ。
と大きく路線変更を決めた様な気がしたけど
トークン情報のリストに【メタな表現】のトークンが増えるだけなんだなぁ~(笑
4. 連想配列を多用していると、急にJavascriptが無効化される
とりあえず、jQUeryの初期化処理に $(‘#result’).val(“- – -“); を入れてJavascriptが無効化されたら何も出ないので判るようにした。
よーくみると、

{

‘name’:   null,

‘pattern’:     /RegExp/g

}

のハズが

{

‘name’:   null;

‘pattern’:     /RegExp/g;

}

と区切り方を間違えていた。
ブラウザのコンソールにエラーが出ないので、原因を掴むのが結構面倒。
根本的な原因はJavaScriptのどこかにtry…catch…を書くと、開発ツールのコンソール・タグに文法エラーすら出てこなくなる様だ。
なので、出来るだけtry…catch…をあてにしない様にしないと、括弧の付け忘れすら見つけるのが大変だ。(大笑
もっとも、それが原因で文法チェックしかできなくていいから自作のパーサーが欲しいなぁと思ってしまったんだけどね。
こうなると、行数がどんどん増えてくると、ケアレスミスすら捕まえるのが難しい。
トークンのパターンの括弧の扱いをどうしようか・・・のあたりで諦めた。
try…catch 無しで考え直すしかなさそう。
だが、他にも
コピペしたコードの消し忘れで
var msg = ‘message’ : “…”;
と 妙なところに : を書いてしまったり、
EBnf.prototype.parseEbnf = function(grammer) {
の最後の { を付け忘れても、ダンマリ(JavaScriptが動かない)ケースがあった。
また、とりあえず一時的にtry…catch を無効にしたい場合は

//    try {

・・・

/*   }  catch (ex) {

・・・

      }  */

が手っ取り早いが、

/*%%    try {   %%*/

・・・

/*%%   }  catch (ex) {

・・・

      }  %%*/

と*を*%%などあまり使わない表現を混ぜるとテキストエディタの正規表現検索で探しやすく、戻し忘れも防ぎやすいでお勧め。
そんな感じなので、ケアレスミスのチェックに手間取り、パーサーの進捗ははかどっていない。
まだまだ完成には程遠いが、ソース中の正規表現だけはすぐに使い回しが利きそうな気がしているので、EBNF書式のテキストを色付けするだけのサンプルを残す。字句解析しかないけど・・・
字句解析しかないから、
しっかりとRegExpのパターンを作り、適切な登録順で組み合わせれば、
JavaScript用も作れたので残しておく。(字句解析しかしてないけどね。
selfCheckの呼び出しもRegExpのパターン情報をコピーしたtknを呼び出し、
rc = tkn.selfCheck( this, tkns[offset], tkns[offset+1], tkns[offset+2] );
と変えたので、特定の用語だけ色を変えることも可能なハズ。
想定外のパターンに出会えば【その他】が反応し、背景色:赤、文字色:黄で派手にしてあるのでデバッグも容易。(大笑
/* … */ なコメントも追加。予想通り /RegExpパターン/が先に反応したので それより先に配置。コメント解析がいくつもあるのも嫌なので、( //…  |  /*…*/  |  (*…*)  )を1つにまとめてみると
(/{2}.*$|\\/\\*(?:\\*(?!\\))|[^\\*])+\\*\\/|\\(\\*(?:\\*(?!\\))|[^\\*])+\\*\\))
コメントを判定するダケの正規表現なのに・・・明らかにイミフだ。(大笑

ソース・コードのコメントに色付けしたいダケ

なら、

ソースコード.replace(

/(/{2}.*$|\\/\\*(?:\\*(?!\\))|[^\\*])+\\*\\/|\\(\\*(?:\\*(?!\\))|[^\\*])+\\*\\))/mg ,

function ($0,$1) {

  return “<span style=\”color=#00ff00\”>” + $1 + “</span>”;

});

とでも書けばいいのかな?
コメントの種類ごとに色訳けしたいなら

ソースコード.replace(/(/{2}.*$)/mg ,function ($0,$1) {

  return “<span style=\”color=#00ff00\”>” + $1 + “</span>”;

}) . replace(/(\\/\\*(?:\\*(?!\\))|[^\\*])+\\*\\/)/mg ,function ($0,$1) {

  return “<span style=\”color=#0000ff\”>” + $1 + “</span>”;

}) . replace(/(\\(\\*(?:\\*(?!\\))|[^\\*])+\\*\\))/mg ,function ($0,$1) {

  return “<span style=\”color=#ff0000\”>” + $1 + “</span>”;

});

とreplaceを結合すればいいのだろう。
※コメントはよく行をまたぐので、最後のは必須。
これでもいいのかもしれないが
こんな風にRegExpで頑張るよりも
パーサー・コンビネーターで、
ノソノソと解析させた方が可読性は格段に高いハズ。
字句解析を連想配列化しているが、構文解析はついついハードコードしてしまい1000行オーバーな雰囲気から抜け出ないので連想配列化した方がいいのかな

{

name:  式,

pattern:    “項 [ 演算子 式 ] | ‘(‘ 式 ‘) ‘”,

testpattern: “1+2”,

proc: function(p1,p2) {

return  p1+p2;

}

}

な雰囲気で並べて動かせばゴソっと行数は減るような気もするが、構文パターンの中の演算子” | “やグループやオプション表現はやはりハードコードになるのかな。勿論、このpattern用の字句解析の連想配列も作っておこう。



【javascript】 RegExp 正規表現

よく ”20161216010203″.replace(/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})/g,”$1/$2/$3 $4:$5:$6″);
などどダラダラな使い方をしてしまうと
もう正規表現とはいいがたい。
var reg = new RegExp(”\(\[0-9\]\{4\}\)\(\[0-9\]\{2\}\)\(\[0-9\]\{2\}\)\(\[0-9\]\{2\}\)\(\[0-9\]\{2\}\)\(\[0-9\]\{2\}\)”, “g”);
var mat = reg.exec(“20161216010203”);
しかもパターンをjavascriptのリテラルとして指定するとエスケープシーケンスで更に長くなって訳が分からなくなる。
字句解析した部分:mat[0]、年:mat[1]、月:mat[2]、時:mat[3]、分:mat[4]、秒:mat[5]
など・・・となるはずなんだけどなぁ・・・
その度にスクラッチするのも面倒なのでデバッガを作ってみた。

ちまちましたパターンのデバッグに丁度いい。

複数のパターンを書けば、 | で繋いで、字句解析する様になっているものの・・・
パターンの長いものを先に書かないと、短いパターンが優先してマッチングしてしまい不調。
各パターンが括弧で括る(・・・)なパターンを想定しているので、上の様な場合は、複数のパターンを書くと、うまくいかないので、
デバッグ・ログ出力のチェックボックスを追加した。
これで、$0,$1,$2・・・$n の出力の雰囲気も判るハズ。

で、VisualStudioのC#のForm1.Designer.csを読取り
1つづつトークンと正規表現を増やしていく・・・
COMMENT:    (/{2}.*$|/\*.*\*/)
REGION_B:   (\s*#region\s+[a-zA-Z0-9]+.*$)
REGION_E:   (\s*#endregion$)
SPACE:      ([\s]+)
L-BRACE:    (\{)
R-BRACE:    (\})
L-PAREN:    (\()
R-PAREN:    (\))
COMMMA:     (\,)
DOT:        (\.)
BINOP:      (\|\||&&|!=|==|\+|<=|>=|-|\*|\/|%|=)
DELIMITER:  (;)
NULL:       (null)
VALUE_TYPE: (void|integer|bool|string)
SCOPE:      (public|private|friend|static)
THIS:       (this)
NEW:        (new)
BOOL:       (true|false)
SYMBOLE:    ([a-zA-Z0-9]+)
NUMBER:     ([1-9]\.?[0-9]+)
この辺から急に重くなりだす。
暫くすると
メモリーガーとか言い出す。
LITERAL:    (‘[^’]*’)
WQLITERAL:  (“[^”]*”)
まで来ると、
字句解析させてみるとタイムアウト。
matchならあっという間などど思ってたら、
実は,解析結果の出力方法が・・・
for ( var idx in tokens ) {
log( idx + ” type:” + tokens[idx].type + “, token:” + tokens[idx].token );
}
だったせい。
このlog(msg)は間に合わせに作ったログの書き出し処理で
中身は・・・
function log(msg) {
$(‘#result’).text($(‘#result’).text() + msg + “\n”);
}
Form1.Designer.csを字句解析すると7000個以上のトークンに分解できるが
そこまではexecもmatchに瞬時。
しかし、DOM要素を7000回も読み書きしていたので
とても時間がかかっていたのた。 orz
ついでだから50%増速モードに変更。
var msg=””;
for (var idx = 0; idx < tokens.length; idx++) {
msg += idx + ” type:” + tokens[idx].type + “, token:” + tokens[idx].token +”\n”;
}
log(msg);
これで、劇的に早くなりデバッグも容易になった。(大笑
ま、トークンが22種類になり$1~$22となってしまうと、$9までの制限があるreplace( ) はそのままでは使えなくなるけどね。
とても、がっがりしたけど、
Form1.Designer.csの字句解析まではサクっと終わるから、
後はちゃんと文法にまとめ、HTMLのフォームに変換すれば、VisualStudioのC#のWindowsFormフォームをブラウザに写し取れるハズ。
勿論、その逆も可能なハズ。
※実はここが重要だったりする。
と云うのも、VisualStudioのフォーム・エディタで編集するとプロパティをちょっと追加しただけでテキストの順序が結構移動するので、svnの差分エディタを見るとウンザリする差分ができあがるのだ。
ここだけちょっと何とかしたい。
華麗でなくていい、綺麗にそれなりに自力でソートできるだけでも十分実用的じゃない?
でも、出来上がった正規表現パターンは訳が判らないなぁ~(爆笑
to be continue…




top