MathJaxをデバッグしてJavaScriptの理解を深める ページ2

提供:yonewiki
2022年12月1日 (木) 15:41時点におけるYo-net (トーク | 投稿記録)による版 (→‎s.combineDefaults)

VScodeで簡易WebServerの元でデバッグに戻る。

概要

 MathJaxをデバッグしてJavaScriptの理解を深めるの続きです。

[1] [2] [3]

t.defaultReady

 

e.MathJax.config.startup.ready() { t.defaultReady d() }

 d()は以下のような関数です。

function d() {
  var e, r;
  t.input && t.output && m();
  var n = t.output ? t.output.name.toLowerCase() : "";
  try {
    for(var i = o(t.input), s = i.next(); !s.done; s = i.next()) {
      var a = s.value,
        l = a.name.toLowerCase();
      g(l, a), 
      b(l, a), 
      t.output && y(l, n, a)
    }
  } catch(t) {
    e = {
      error: t
    }
  } finally {
    try {
      s && !s.done && (r = i.return) && r.call(i)
    } finally {
      if(e) throw e.error
    }
  }
}

 n = 'chtml'(CHTML) で l = 'tex'(TeX) → 'mathml'(MathML)という値。a は input の値。 output 系の l を使った g や b という関数。 output、input両方の l n を使った y という関数を実行する。いずれも a の input 値 を引数にしている。


 g はこんな関数。

function g(r, n) {
  var o = e.MathJax._.core.MathItem.STATE;

  e.MathJax[r + "2mml"] = function(e, r) {
    return void 0 === r && (r = {}), 
           r.end = o.CONVERT, 
           r.format = n.name, /* TeX → MathML */
           h(t.document.convert(e, r))
  }, 
  e.MathJax[r + "2mmlPromise"] = function(e, r) {
    return void 0 === r && (r = {}), 
           r.end = o.CONVERT, 
           r.format = n.name, /* TeX → MathML */ 
           l.handleRetriesFor((function() {
             return h(t.document.convert(e, r))
           }))
  }
}


 で、e.MathJax._.core.MathItem.STATE は、以下のような値。 o に取り込まれます。第一引数 r は Input の 'tex'、第二引数 n は Input 系のオブジェクト n.name は 'TeX' のような値です。

{
  UNPROCESSED: 0,
  FINDMATH: 10,
  COMPILED: 20,
  CONVERT: 100,
  METRICS: 110,
  RERENDER: 125,
  TYPESET: 150,
  INSERTED: 200,
  LAST: 10000,
  CONTEXT_MENU: 170,
  ASSISTIVEMML: 153,
}


 e.MathJax['tex2mml'] と e.MathJax['tex2mmlPromise'] に 関数を格納するのがこの g 関数の役割のようです。o.CONVERT: 100 が r.end に格納される部分がありますが、どのように使われるのかは、この時点ではあまりわかりません。


 次に b が呼ばれます。以下のような関数。


b(t, r) {
  e.MathJax[t + "Reset"] = function() {
    for(var t = [], e = 0; e < arguments.length; e++) 
      t[e] = arguments[e];
    return r.reset.apply(r, s([], i(t), !1))
  }
}


 e.MathJax['texReset'] に関数を格納するのがこの b 関数の役割です。格納した関数がどのように使われるかは、やはりこの時点ではわかりません。 引数の t と 取り込んだ関数の t は別物です。呼び出した時の引数を順次格納していくのに使われているようです。Reset とつくくらいですから、きっと呼び出されたら初期の表示状態にもどるんでしょう。おそらく。


 や、でも、やっぱり関数名が1文字だったり変数名が1文字なのは、理解に苦しむ。何がしたいのか、まったく全貌が見えてこない。リセットする関数が b で変換する関数っぽいのが g とな。何も想像できない。これメンテナンスできる人いるの?ってぐらいすごい。でも更新されてたりするってことはわかってる人いるんだろうな。難しい。この世界にこのプログラムを読み解ける人がいるってことが信じられない。スーパープログラマ中のスーパーだな。この優秀な頭脳が世界にはいくつも存在していて、回ってるのか。恐るべし。


 続いて y です。以下のような関数です。r が tex、n が r が chtml

y(r, n, o) {
  var i = r + "2" + n; /* tex2chtml */
  e.MathJax[i] = function(e, r) { /* e.MathJax['tex2chtml'] */
    return void 0 === r && (r = {}), 
           r.format = o.name, /* TeX → MathML */
           t.document.convert(e, r)
  }, 
  e.MathJax[i + "Promise"] = function(e, r) { /* e.MathJax['tex2chtmlPromise'] */
    return void 0 === r && (r = {}), 
           r.format = o.name, /* TeX → MathML */
           l.handleRetriesFor((function() {
             return t.document.convert(e, r)
           }))
  }, 
  e.MathJax[n + "Stylesheet"] = function() { /* e.MathJax['tex2chtmlStylesheet'] */
    return t.output.styleSheet(t.document)
  }, 
  "getMetricsFor" in t.output && (e.MathJax.getMetricsFor = function(e, r) {
    return t.output.getMetricsFor(e, r)
  })
}


 基本的に関数を定義することが多い関数だったなぁ。何がしたいのかが全くつかめず、テンションダウン。うまいな。作ってる人。おいかけてる人に対してのメッセージを感じる。いなされてるな。サッカーのイメージだ。凄すぎて、ぜんぜんボールが取れない。


e.MathJax.config.startup.ready() { t.defaultReady.then }

 非同期処理の処理順序登録をする。この関数がおわったら、e.CONFIG.pageReady()関数を実行して、うまくいったら、t.promiseResolve()、失敗したら t.promiseReject(e) を実行する。

then((function() {
  return e.CONFIG.pageReady()
})).then((function() {
  return t.promiseResolve()
})).catch((function(e) {
  return t.promiseReject(e)
}))

 

t.getRoot

 ここまで読んで気づいたのだけど小説ってだいたい3500行くらいらしい。5万行。14倍。14冊。おわんねぇ。無理過ぎる。どこかでやめないとな。数式だらけの参考書を14冊読んでるのと同じことやってることになる。


 どこでやめるかは、後で考えるとして、次のような関数です。

t.getRoot = function() {
  var t = "//../../es5";
  if("undefined" != typeof document) {
    var e = document.currentScript || document.getElementById("MathJax-script");
    /* e = "<script id="MathJax-script" async="" src="../es5/tex-mml-chtml.js"></script>" */
    e && (t = e.src.replace(/\/[^\/]*$/, "")) 
    /* e.src = 'file:///C:/MathJax/es5/tex-mml-chtml.js' */
    /* e.src.replace(/\/[^\/]*$/, "") = 'file:///C:/MathJax/es5'
  }
  return t /* 'file:///C:/MathJax/es5' */
}


  s.combineDefaults 関数の引数部で mathjax: c.getRoot() から呼び出されます。

 

t.checkVersion

 以下のような関数です。

t.checkVersion = function(n, o, i) { /* n = 'core', o = '3.2.2', i = 'core' */
  return t.versions.set(a.Package.resolvePath(n), r),
         !(!e.CONFIG.versionWarnings || o === r) && 
         (console.warn("Component ".concat(n, " uses ").concat(o, " of MathJax; version in use is ").concat(r)), !0)
}


 a.Package.resolevePath('core') = "file:///C:/MathJax/es5/core.js" で、 r = '3.2.2'、e.CONFIG.versionWarnings = true という感じでここでは、 バージョンチェックで、問題なければ、!(!e.CONFIG.versionWarnings || o === r) が false になって、最後の行にあるようなエラー警告が出ない仕組みです。


 最後の方にあるこんな関数から呼ばれます。MathJax.loader && MathJax.loader.checkVersion("core", e.VERSION, "core"), (0, t.combineWithMathJax)({ … }) のところです。

 

t.pathFileters

 次はこんな命令が処理されます。u.FunctionList は MathJax全体にわたって膨大な関数を管理しているオブジェクトです。それを t.pathFilters に格納されます。

t.pathFilters = new u.FunctionList, 
t.pathFilters.add(e.PathFilters.source, 0), 
t.pathFilters.add(e.PathFilters.normalize, 10), 
t.pathFilters.add(e.PathFilters.prefix, 20)

 u.FunctionList は以下のような構造になっています。全部書くとMathjaxのほぼすべてのコードを貼り付けるのと変わらないことになるので、だいたいこんな感じっていうのを示します。

u.FunctionList = 
{ ƒ :
  { prototype: t {constructor: ƒ, execute: ƒ, asyncExecute: ƒ},
    name: 'e',
    Scopes: [ 0 : { t: ƒ},
              1 : { n: ƒ, o: ƒ, i: ƒ, s: ƒ, a: ƒ},
              2 : { __webpack_exports__: ƒ,
                    __webpack_module_cache__: {235: {}, 265: {}, 3282: {} …
                    __webpack_modules__: {50: ƒ, 91: ƒ, 94: ƒ, 95: ƒ, …  
                    __webpack_require__: ƒ __webpack_require__(t){ … },
              },
              3 : { n: ƒ, o: ƒ, i: ƒ, s: ƒ, a: ƒ},
            ]
   }
}

 このような u を t.pathFilters.add をつかうと以下のような構造の t が作られるようです。

t.pathFilters = 
{ ƒ :
  { name: 'e',
    prototype: t {constructor: 
    { [ 0 : { t: ƒ},
        1 : { n: ƒ, o: ƒ, i: ƒ, s: ƒ, a: ƒ},
        2 : { __webpack_exports__: ƒ,
              __webpack_module_cache__: {235: {}, 265: {}, 3282: {} …
              __webpack_modules__: {50: ƒ, 91: ƒ, 94: ƒ, 95: ƒ, …  
              __webpack_require__: ƒ __webpack_require__(t){ … },
        },
              3 : { n: ƒ, o: ƒ, i: ƒ, s: ƒ, a: ƒ},
      ]
   }, 
   execute: ƒ, 
   asyncExecute: ƒ,
   items:{0: {item: function(t) {
 return e.CONFIG.source.hasOwnProperty(t.name) && (t.name = e.CONFIG.source[t.name]), !0
                    }
              priorty: 0
             }
         },
         {1: {item: function(t) {
 var e = t.name;
 return e.match(/^(?:[a-z]+:\\/)?\\/|[a-z]:\\\\|\\[/i) || 
        (t.name = "[mathjax]/" + e.replace(/^\\.\\//, "")), 
        t.addExtension && 
        !e.match(/\\.[^\\/]+$/) && 
        (t.name += ".js"), 
        !0
                     }
              priorty: 10
             }
         },
         {2: {item: function(t) {
 for(var r;
      (r = t.name.match(/^\\[([^\\]]*)\\]/)) && 
      e.CONFIG.paths.hasOwnProperty(r[1]);
 ) 
   t.name = e.CONFIG.paths[r[1]] + t.name.substr(r[0].length);
 return !0
                      }
              priorty: 20
             }
         },
   },
}

効率的なプログラミングをしているようで、関数ごとコピーしたりと、重たい処理が多い。これってこんなやり方でいいのか?って思うようなこともあったりする。

 

e.Loader , e.MathJax

(c = e.Loader || (e.Loader = {})), 
e.MathJax = s.MathJax,
void 0 === e.MathJax.loader /* if 判定対象 */

 e.Loader が 0 false なら、{} の空、連想配列に。


 e.Loader = {versions: Map(0), ready: ƒ, load: ƒ, preLoad: ƒ, defaultReady: ƒ, …} という内容で c にも格納。


 s.MathJax は以下のような値で e.MathJax にも格納されます。そして、e.MathJax.loader が undefined なら if 文にぶら下がる次の処理を実行。最初は以下のような値で _ と config しかメンバがないですので、次の s.combineDefaults の処理がされます。

s.MathJax = 
{
  version: "3.2.2",
  _: {
  },
  config: {
    loader: {
      load: [
        "input/tex-base",
        "[tex]/newcommand",
        "[tex]/action",
        "output/chtml",
      ],
    },
    tex: {
      inlineMath: [["$", "$",], ["\\(", "\\)",],],
      packages: ["base", "newcommand", "action",],
    },
  },
}

 

s.combineDefaults

 s.combineDefaults自体は関数で、以下のような関数です。

function t(e /* = e.MathJax.config */, r /* = "loader" */, o /* 大きな配列 */) {
  var s, a;
  e[r] || (e[r] = {}),  
  e = e[r];
  try {
  	for(var l = n(Object.keys(o)), c = l.next(); !c.done; c = l.next()) {
  		var u = c.value;
  		i(e[u]) && i(o[u]) ? 
          t(e, u, o[u]) 
        :
          null == e[u] && 
          null != o[u] && 
          (e[u] = o[u])
  	}
  }
  catch(t) { s = { error: t } }
  finally {	
    try     { c && !c.done && (a = l.return) && a.call(l) } 
    finally { if(s) throw s.error }
  }
  return e
}


 【 e[r] ⇔ e.MathJax.config['loader'] 】が 0(flase) なら e[r] を {} に設定して、e に e[r] を格納。そして第三引数の巨大な連想配列の各キー値を u に取り出し o[u] でその値を個別に処理をします。関数 i は引数がオブジェクト変数であるかどうかを確認し、結果を true Or flase で返す関数です。オブジェクト変数であるものを再帰的に t 関数自身を呼び出しつづけて細分的に処理をしていくものになっています。オブジェクト変数でない場合 e[u] が null で o[u] が null でないなら、e[u] に o[u] の値を格納する処理です。 e に e.MathJax.config['loader'] 以下のオブジェクトの値が取り込まれるような処理になっていると思われます。

 

s.combineWithMathJax

 

i(e.MathJax.config.loader.pathFilters)

 

ページ

 

関連情報

 MathJax

 

VScodeで簡易WebServerの元でデバッグに戻る。