変奏現実

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

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

javascript

[javascript]クラスやメソッドの名前を表示する

処理中にクラス名を表示するのはコンストラクタのnameを表示するのが簡単です。

console.log(this.constructor.name);

クラスでは無いトコで使うと

Uncaught TypeError TypeError: Cannot read properties of undefined (reading 'constructor')

になります。

メソッド名が表示したい時はメソッドのnameプロパティが使えます。

class foo {
  xxxx() {
  const methodName = this.xxxx.name;
    console.log(methodName);  /* xxxx になる */
  }
}

ベタでmethodName = ‘xxxx’で良さそうですが、リファクタリングするとボロボロです。

厄介なのは派生クラスから基底クラスのメソッドを利用している時です。

class hoge extends foo {
  className = this.constructor.className;
  xxxx() {
    console.log(this.className); /* hoge になる */
    this.yyyy();
  }
}

class foo {
  className = this.constructor.className;
  yyyy() {
    console.log(this.className); /* hoge になる */
  }
}

大抵はこれで満足ですが、クラスfooのyyyならfooになって欲しい場合に

class hoge extends foo {
  /* className = new hoge().constructor.name; ではループするので、一旦staticなメンバーに入れます */
  static className = new hoge().constructor.name;
  className = hoge.className;
  xxxx() {
    console.log(this.className); /* hoge になる */
    this.yyyy();
  }
}

class foo {
  static className = new foo().constructor.name;
  className = foo.className;
  yyyy() {
    console.log(this.className); /* hoge になる */
  }
}

と書き込んでも、thisのコンストラクタのnameでは派生クラスの名前が出てきます。

そんな時はクラスのメンバーにclassName=クラス名を書けば解決しそうですが、

class hoge extends foo {
  static className = new hoge().constructor.name;
  className = hoge.className;
  xxxx() {
    console.log(this.className); /* hoge になる */
    this.yyyy();
  }
}

class foo {
  static className = new foo().constructor.name;
  className = foo.className;
  xxxx() {
    console.log(this.className); /* hoge になる */
  }
}

やはり、thisが強いので、派生クラスの名前が出てしまいますので、自分以外には見えない#classNameを使うと

class hoge extends foo {
  static className = new hoge().constructor.name;
  className = hoge.className;
  #className = hoge.className;
  xxxx() {
    console.log(this.className);  /* hoge になる */
    console.log(this.#className); /* hoge になる */
    this.yyyy();
  }
}

class foo {
  static className = new foo().constructor.name;
  className = foo.className;
  #className = foo.className;
  xxxx() {
    console.log(this.className);  /* hoge になる */
    console.log(this.#className); /* foo になる */
  }
}

やっと希望どおりになりますが、メンドクサイですし、new すると困る場合には向きません。

クラス名でコンストラクタの名前を取得すると・・・

class hoge {
   #className = hoge.constructor.name;
   xxxx() {
      console.log(this.#className); /* Function になる残念 */
   }
}

new した時にコンストラクタが名付けられるっぽい。



[spreadSheet7]デバッグ

ES-moduleにしたせいで、サーバに上げないと使えなくなってる。

index.htmlを開いて「Open with live server」でchromeでspreadSheet7を開き

chromeのDevToolでチマチマするのも面倒なので

VScodeの「chrome アタッチ」はchromeにデバッグオプションを付けないのでアタッチに失敗するので改造。urlはliveServerに合わせる。

{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "Launch Chrome",
            "url": "http://127.0.0.1:5500",
            "webRoot": "${workspaceFolder}",
            "runtimeArgs": [
                "--remote-debugging-port=9222"
            ]
        }
    ]
}

これで無事live serverで表示しながらVScodeのデバッガが使えるようになる。

問題点は、やはりspreadSheet7がローカルで動かないことかな



[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連の (.* ? を仕掛けるぞ(的な
で良いのかもしれない。
※とても十分な検証をする気にはならないががが

で済むのかな?

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



[spreadsheet6]組込関数の登録

functionsディレクトリィの組込関数の登録にはHTMLにscriptタグをいっぱい書かないと処理しないのでfunctionsディレクトリィのjsファイルの一覧をfunctions.jsonに手書きで作成し起動時に読込でimport() ×jsファイル数で関数を登録させてた

手書きしなくても

  • ファイル一覧を取得
  • jsonテキストに変換
  • ファイルに出力

をググってサンプルを探し

// 組込関数まとめ.js
//
// path変数が示すディレクトリィに存在する拡張子がjsのファイルの一覧を
// fname変数で指定したファイルにJSON形式で出力する
//
import { default as fs } from "fs";

const path = './js/spreadSheet/functions/';
// ディレクトリィのファイルリストを取得
const files = fs.readdirSync(path)
    .filter((fname) => {
        // 拡張子がjsのファイルのみ残す
        const [name, ext] = fname.split('.');
        return (ext === 'js');
    })
    ;

// ファイル一覧(型:配列)をJSON形式のテキストに変換する
const text = JSON.stringify(files, "", 2);

// 非同期にファイルに書き込む
const fname = "functions.json";
fs.writeFile(`${path}${fname}`, text, (err) => {
    if (err) {
        console.log(`'${path}${fname}' file write err\n${err.message}\n${err.stack.split('\\n')}`);
    } else {
        console.log(`'${path}${fname}' file write\n'${text}'\n`);
    }
});
// end of 関数集計.js/

繋げたら出来ました。

たまにはこんなこともあるんだなぁ~

ただし、実行してみると

[MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type ・・・

と長い注意書きがコンソールに出てきた。

package.jsonにtype=’model’を書いて無いから、common jsだと思って実行したけど、import文があるからES-moduleのハズだからやっといたよ?的な内容だった。

やっぱり、何事も無く無事に完了するワケがなかった。(合掌



[javascript]binaryen.js

binaryen.jsはbinaryenのjs版。延々とビルドせずに使えるのが嬉しい。

binaryenの説明文に「 used from JavaScript. 」って書いてあるけど、その先はパスにwikiがあるので説明だけ。ググり続け、npmのリポジトリィを見つけた。

npm install binaryen

元ネタのリポジトリィは、github.com/AssemblyScript/binaryen.jsのindex.jsだそうだ。

ちなみにそこをクローンすると6GB以上あるのでnpmで済ませる。

パッケージ本体はTypeScriptで書かれていて配布用のindex.jsは圧縮されほぼ解読不能。

npmのページのサンプルコードを入力して実行すると

Uncaught Error Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead. To see where the top-level await comes from, use --experimental-print-required-tla.

どうやらnpmのページのサンプルは5年前の書き方らしい。
var binaryen = require(“binaryen”);
を元ネタのページのサンプルの様に
import binaryen from “binaryen”;
に直すと動き出したが、直ぐエラってしまうので、※5年の間に色々変わり過ぎ
全部元ネタのページのサンプルに差し替えると最後まで動いた。
ちなみにこの記事を書いている時点でのモジュールのupdateは4時間前。※出来たてホヤホヤ

それにしても
jsのアロー関数とか、
wasmのテキストがS式とか、
大昔に卒論で(デバッグのために)いっぱい書いたから違和感はないけど
なんで大昔(半世紀くらい前)に流行ったフォーマットを使うんだろう?

謎(闇?)は深い。

binaryen.jsはTypeScriptベースのjavascriptソースで提供されている。

Node.jsでサンプルを実行する分にはnpmで作った設定ファイル等を参照してくれるのでパッケージ名だけ指定すればOK。

import binaryen from "binaryen";

ブラウザ(Chrome)で使用する場合はスクリプト・タグのタイプを”module”指定しimport文を使用可能にしてもnpmプロジェクトとか気に留めてないので直接node_modules/binaryenプロジェクトのpackage.jsonに”main”:”index.js”と書かれたファイルを相対パスで指定しないといけない。

chromeで表示するHTMLで<script type="module" src="./worker.js">とtypeでmodule指定しないとimportが使えない

import binaryen from "./node_modules/binaryen/index.js"; ※npm install binaryen したなら皆同じになるハズ

※ローカルな環境(file:///)ではimportがアクセスエラーで使えないっぽく、何かのWebサービス(apache等)上にHTML等を配置する必要がある。

index.html:1 Access to script at 'file:///・・・/test/worker.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-extension, chrome-untrusted, data, http, https, isolated-app.
worker.js:1 
Failed to load resource: net::ERR_FAILED

さらに厄介なのが、HTMLやtype=”text/script”(デフォルト設定)のjavascriptからtype=”module”を指定するモジュールが見えない(DevToolでモジュール欄すら出ない)のでpostMessageを経由してイベントを通知しないとダメらしい。※moduleと指定してるのでworker.jsをワーカーとして実行しなくてよい。

window.addEventListener("load", () => {
    document.querySelector("#btn").addEventListener("click", test);
});
const test = () => {
    postMessage("test", "http://localhost:xxxxx"とか);
}

それより全スクリプトをtype=”module”にすればいい、モジュールにはwindow.addEventListener(“load”, の通知が来ないが、 モジュールにはdefer属性が自動付与されるから、即DOMにアクセスできる。

import binaryen from "./node_modules/binaryen/index.js";

//# とりあえず、urlからドメインとポートを取り出してポストメッセージのチェックに使用する
//# function getDomainPort() {
//#     const url = import.meta.url;
//#     const result = /^(http[s]?:\/\/([.0-9a-z]+)(:\d+)?)/.exec(url);
//#     return result[0];
//# }
document.querySelector("#btn").addEventListener("click", workerTest);※これで解決

// アロー関数にするとworkerTest実行時にChromeのDevToolの範囲のモジュールに
// 自分の関数名が出ないので再帰する時は困るかもしれない。
function workerTest(event) {
    try {
        //# if (event.origin !== getDomainPort()) {
        //#     return;
        //# }
        // Create a module with a single function
        myModule.addFunction("add",                                 // 以下、ボクの理解範囲
            binaryen.createType([binaryen.i32, binaryen.i32]),      // parames: [引数[0]の型, 引数[1]の型]
            binaryen.i32,                                           // result: 戻り値の型
            [binaryen.i32],                                         // vars: スタックを型付き配列風に宣言
            myModule.block(null, [
                myModule.local.set(2,                               // 結果を スタック[2] に格納
                    myModule.i32.add(                               //   加算(スタックの2つの変数を加算)
                        myModule.local.get(0, binaryen.i32),        //      引数[0]をスタックに積む
                        myModule.local.get(1, binaryen.i32)         //      引数[1]をスタックに積む
                    )
                ),
                myModule.return(                                    // 戻り値は下記の様にスタックされる
                    myModule.local.get(2, binaryen.i32)             //   スタック[2]をスタックに積む
                )
            ])
        );
        myModule.addFunctionExport("add", "add");                   // 上の"add"関数を"add"の名前で外部参照を宣言

        // Optimize the module using default passes and levels
        myModule.optimize();                                        // 最適化

        // Validate the module
        if (!myModule.validate())                                   // 計算でエラったら
            throw new Error("validation error");                    // "validation error"とスローする

        // Generate text format and binary
        var textData = myModule.emitText();                         // 上記の設定からテキスト(S式)で得る
        var wasmData = myModule.emitBinary();                       // 上記の設定からWebAssemblyコードで得る

        // Example usage with the WebAssembly API
        var compiled = new WebAssembly.Module(wasmData);            // WebAssemblyコードを含むモジュールを生成
                                      //  importのリンク情報等を設定?
        var instance = new WebAssembly.Instance(compiled, {});      // 実行可能なモジュールのインスタンスを生成
        const result = (instance.exports.add(41, 1));               // インスタンスを実行させてみる

        document.querySelector("#result").innerHTML = `textData:${textData}<br/>result:${result}<br/>`;
};
window.addEventListener("message", workerTest);

うっかりすると、localってローカルな変数かと思うけどスタックマシンなのでlocal=stack。

myModule.local.set(2, よりも myModule.local.set(0, の方が良いと思うけど、最適化(Module.optimize())でS式はいづれも同じになる。

(module
   (type $0 (func (param i32 i32) (result i32)))
     (export "add" (func $add))
       (func $add (param $0 i32) (param $1 i32) (result i32)
         (i32.add (local.get $0) (local.get $1) ) ) )    ※明示的にパラメータをスタックに積む様になってる

ここまで面倒になってるものの、以前はmodule側からは一切DOMにアクセスできなかったと思うが

document.querySelector(“#result”)が可能になってるのは助かる。

とりあえず、podmanの未公開なapacheにソースを配置してVScodeでリモートデバッグを心掛ければ支障は無さそう。

VScodeの拡張機能でLive ServerLive Previewを使うのも手軽かも。いづれもVScode上でのデバッグはできないが、

Live Serverは、HTMLエディタ上で右クリックして「Open with Live Server」でブラウザで開けるしDevToolでデバッグが可能。

Live Previewも同様に「Show Preview」でスグ見れる、見れるダケだけど。

LiveServerがどのポートを使うのか判らないのでgetDomainPort()みたいにLiveServerが割り当てたurlからドメインとポートが得られるようにした方が気楽。



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



[javascript]XmlDocument?

XmlDocumentの構成とかjavascriptでの読み方とか

xmlData.getElementsByTagName("要素名")[0]
とか
xmlData.querySelect(セレクター)
とか

な感じで検索する記事はあるけど、ザーット表示する記事はなかった。

ま、ブラウザで表示すればいいからね。

でも自作のTreeViewもどきで作った

APIはまだ無いので自力でTreeViewにHTMLobjectを書き込まないといけない。

    <div class="tree_view">
        <!-- level 1 -->
        <div class="element open"><tag-name attr-name1="xxxxxx" attr-name2="xxxxxx" >
            <div class="contener">
                <!-- level 2 -->
                <div class="context">テキスト1</div>
                <div class="context">テキスト2</div>
                <div class="context">テキスト3</div>
                <div class="context">テキスト4</div>
                <div><tag-name2/></div>
            </div>
            <div class="element-end"></tag-name1></div>
        </div>
        <!-- level 1 -->

な感じでHTMLを作ると

な雰囲気で表示するところまで地味にcssを書いて

body {
    background-color: antiquewhite;
}
div.tree_view {
    background-color: burlywood;
    font-family: 'メイリオ', 'Meiryo', 'MS ゴシック', 'Hiragino Kaku Gothic ProN', 'ヒラギノ角ゴ ProN W3', sans-serif;
    font-size: 10pt;
    height: 100%; width: calc(100% - 20px);
    margin: 10px; border: solid black 1px; padding-left: 1em;    
}
div.marker {
    vertical-align: top;
    display: inline-block;
    width: 1em;
    border: solid blue 1px;
}
div.element {
    margin-right: 6px;
}
(省略)

点線も描いて

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
	<path d="M 0,0,0,9,14,9" fill="none" stroke="gray" stroke-dasharray="2" />
</svg>

後は、XMLファイルを読んで終了。

  ・・・
    /**
     * ファイルボタンのファイル選択イベント 
     * @param {*} event 
     */
    onSelectedFile = (event) => {
        // A file selection event occurred.
        console.log(`file selection event occurred. '${Array.from(event.target.files).map((a) => a.name).join(', ')}'`);
        // Get a file object.
        const fileObjects = event.target.files;
        // 過去歴をクリア
        this.xmlDocuments = [];
        clearnHtmlObjectTextContent(this.outputHtmlObject);
        // Load file 
        this.loadFiles(fileObjects, this.readXml, this.outputHtmlObject);
    };
    /**
     * load fileObject
     * @param {FileList} fileObjects 
     * @param {*} contextReader 
     * @param {*} outputHtmlObject
     */
    loadFiles = (fileObjects, contextReader, outputHtmlObject) => {
        Array.from(fileObjects).map((fileObject) => {
            const fr = new FileReader();
            fr.addEventListener('load', (event) => {
                // ファイルのテキストを取得
                const xmlContext = event.target.result;
                contextReader(xmlContext, outputHtmlObject);
            });
            fr.readAsText(fileObject);
        });
    }
  (省略)
// setup
window.addEventListener("load", () => {
    const xmlView = new TreeView(document.querySelector(`#file`), document.querySelector(`.tree_view`));
    // tree_view の▼と▶の切替
    window.addEventListener("click", (event) => {
        if (Array.from(event.target.classList).some((e) => e === 'element')) {
            const target = event.target; target.classList.toggle('open');
            const chLst = Array.from(target.childNodes).filter((ch) => {
                return ((!ch.classList) ? false : Array.from(ch.classList).some((cl) => cl === 'contener'));
            });
            (chLst).forEach(ch => ch.classList.toggle('hidden'));
        };
    });
});

のハズだったけど、

new DOMParser().parseFromString(xmlText, ‘application/xml’)で得られる

XmlDocumentのMDNの説明はDocument→Nodeクラスの派生型です。(説明終わり

仕方なく汎用XML解析ルーチンaddTreeListByXmlを作ることに

tagNameが無い場合はnodeNameが「#TEXT」なんで、テキストコンテキストノードっぽいのでtextContentのみ設定とかXMLを読んだ結果を見ながら行き当たりばったりで書いた。

配下のオブジェクトはchildListかなと思ったけど、childNodesの方だった。

属性はgetAttributeNames()で属性名を取得してgetAttribute(属性名)で値が判る。

そんな雰囲気なので作ってみた感想は目新しいメソッドも特に無いから

XmlDocumentクラスはDocument→Nodeクラスの派生です。

という説明で充分かもしれない。

TreeViewのAPIもWindowsのみたいに細かく作るよりJSON形式のデータを渡すとそれっぽく表示する程度で充分かな

ps.2025/4/22:カスタムエレメント化した。

<body>
    <label for="fileXmlDom">parseFromString(fileText)</label><input type="file" id="fileXmlDom" value="XmlDom"><br />
    <label for="fileXmlText">replaceAll(/x/g,xmlReplacer)</label><input type="file" id="fileXmlText" value="XmlText">
    <tree-view id="tree_view_sample" width="500px" height="600px" tabindex="1" resize="both" overflow="auto">
    </tree-view>
</body>

String.replaceAll({正規表現パターン、置換処理})の結果をinnerHTMLで展開

XMLParserのparseFromString({XMLテキスト})の結果(DOMツリー)を展開

いづれも長いコード書かないとツリービュー化できないでいる。

ReplaceAllの方はHTMLも読めなくもないが、末端に正しく(?)「/>」が入っていないと南京玉簾になる。

とりあえず、オブジェクトだったら配下のオブジェクトを調べる方法を画一化(childListとか)してみたい。



[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」失敗したら即throwしたい

try {
・・・
  const divSample1 = document.getElementById('sample1');
  if (!divSample1) {
    throw new Error('divタグ(id:sample1)が見つかりません');
  }
  const divSample2 = document.getElementById('sample2');
  if (!divSample2) {
    throw new Error('divタグ(id:sample2)が見つかりません');
  }
  ・・・
  const divSampleN = document.getElementById('sampleN');
  if (!divSampleN) {
    throw new Error('divタグ(id:sampleN)が見つかりません');
  }
・・・
} catch (ex) {
  console.error(ex.message);
}

と書きたくないのでNull 合体演算子 (??)を使って

try {
  ・・・
  const divSample = document.getElementById('sample1') ?? throw new Error('divタグ(id:sample1)が見つかりません');}
  const divSample = document.getElementById('sample2') ?? throw new Error('divタグ(id:sample2)が見つかりません');}
  ・・・
  const divSample = document.getElementById('sampleN') ?? throw new Error('divタグ(id:sampleN)が見つかりません');}
  ・・・
} catch (ex) {
  console.error(ex.message);
}

と書くと「throw」に赤い下線が付いて「式が必要です。ts(1109)」とエラるので・・・

/**
 * 投げだす
 * @param {string} msg 
 */
const throwMsg = (msg) => {
  throw new Error(msg);
}
・・・
try {
  ・・・
  const divSample = document.getElementById('sample1') ?? throwMsg ('divタグ(id:sample1)が見つかりません');}
  const divSample = document.getElementById('sample2') ?? throwMsg ('divタグ(id:sample2)が見つかりません');}
  ・・・
  const divSample = document.getElementById('sampleN') ?? throwMsg ('divタグ(id:sampleN)が見つかりません');}
  ・・・
} catch (ex) {
  console.error(ex.message);
}

で誤魔化せた。

式が必要な深い意味は思いつかないので、単に「throw」を式として認識してないダケだろう。

多用するとメンドクサイ事になるのは避けられないが・・・

※{美しくない}

とか言われそう。

心配なのはデバッグ中に式でExceptionすると

javascriptが処理を「本当」に投げ出す事がマレになるコトかな?(フッ




top