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

提供:yonewiki

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

概要

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

[1] [2] [3] [4]

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'] 以下のオブジェクトの値が取り込まれるような処理になっていると思われます。この時の引数 o の値がdefault値だといっていてだいたいが空の値を保持している状況です。o は以下のような値です。

o = 
{
  paths: {
    mathjax: c.getRoot()
  },
  source: {},
  dependencies: {},
  provides: {},
  load: [],
  ready: c.defaultReady.bind(c),
  failed: function(t) {
    return console.log("MathJax(" . concat(t.package || "?", "): ") . concat(t.message))
  },
  require: null,
  pathFilters: [],
  versionWarnings: !0
}

 

s.combineWithMathJax

 以下のような関数です。

function(t) {
  return s(e.MathJax, t)
}


 この t という引数に c という値が渡されます。c は以下のような関数をふくむ連想配列です。キーは versions/ready/load/preLoad/defaultReady/getRoot/checkVersion/pathFilters/です。


{
  versions: {
  },
  ready: function() {
    for(var t, e, r = [], n = 0; n < arguments.length; n++) 
      r[n] = arguments[n];
    0 === r.length && (r = Array.from(a.Package.packages.keys()));
    var o = [];
    try {
    	for(var s = i(r), l = s.next(); !l.done; l = s.next()) {
    		var c = l.value;
    		var	u = a.Package.packages.get(c) || new a.Package(c, !0);
    		o.push(u.promise)
    	}
    } 
    catch(e) {
    	t = {	error: e }
    } 
    finally {
    	try {
    		l && !l.done && (e = s.return) && e.call(s)
    	} 
    	finally {
    		if(t) 
    		  throw t.error
    	}
    }
    return Promise.all(o)
  },
  load: function() {
    for(var r, n, o = [], s = 0; s < arguments.length; s++)
      o[s] = arguments[s];
    if(0 === o.length)
      return Promise.resolve();
    var l = [],
    c = function(r) {
    	var n = a.Package.packages.get(r);
    	n || 
    	(n = new a.Package(r)).provides(e.CONFIG.provides[r]), 
    		n.checkNoLoad(), 
    		l.push(n.promise.then(
    		  (
    				function() {
    			    e.CONFIG.versionWarnings && 
    				  n.isLoaded && 
    				  !t.versions.has(a.Package.resolvePath(r)) && 
    				  console.warn("No version information available for component ".concat(r))
    		    }
    		  )
    		)
    	)
    };
    try {
    	for(var u = i(o), p = u.next(); !p.done; p = u.next()) {
    		var h = p.value;
    		c(h)
    	}
    } 
    catch(t) {
    	r = {	error: t }
    } 
    finally {
    	try {
    		p && !p.done && (n = u.return) && n.call(u)
    	} 
    	finally {
    		if(r) 
    		  throw r.error
    	}
    }
    return a.Package.loadAll(), Promise.all(l)
  },
  preLoad: function() {
    for(var t, r, n = [], o = 0; o < arguments.length; o++) 
      n[o] = arguments[o];
    try {
      for(var s = i(n), l = s.next(); !l.done; l = s.next()) {
    	  var c = l.value,
    		  u = a.Package.packages.get(c);
    	    u || (u = new a.Package(c, !0)).provides(e.CONFIG.provides[c]), u.loaded()
      }
    } 
    catch(e) {
      t = { error: e }
    }
    finally {
      try {
    	  l && !l.done && (r = s.return) && r.call(s)
      } 
     finally {
    	  if(t) 
    	  throw t.error
    	}
    }
  },
  defaultReady: function() {
    void 0 !== e.MathJax.startup && e.MathJax.config.startup.ready()
  },
  getRoot: function() {
    var t = "//../../es5";
    if("undefined" != typeof document) {
    	var e = document.currentScript || document.getElementById("MathJax-script");
    	e && (t = e.src.replace(/\/[^\/]*$/, ""))
    }
    return t
  },
  checkVersion: function(n, o, i) {
    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)
  },
  pathFilters: {
    items: [
      {
        item: function(t) {
          return e.CONFIG.source.hasOwnProperty(t.name) && (t.name = e.CONFIG.source[t.name]), !0
        },
        priority: 0,
      },
      {
        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
        },
        priority: 10,
      },
      {
        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
        },
        priority: 20,
      },
    ],
  },
}


 s(e.MathJax, t /* c */) の s の関数は以下のようなものです。


function s(t /* e.MathJax */ , e /* c = versions/ready/load/preLoad/defaultReady/getRoot/checkVersion/pathFilters */) {
  var r, o;
  try {
  	for(var a = n(Object.keys(e)), l = a.next(); !l.done; l = a.next()) {
  		var c = l.value;
  		"__esModule" !== c && 
        (!i(t[c]) || !i(e[c]) || 
        e[c] instanceof Promise ? 
          null !== e[c] && 
          void 0 !== e[c] && 
          (t[c] = e[c]) 
        :
          s(t[c], e[c]))
  	}
  } 
  catch(t) { r = { error: t } } 
  finally {
  	try { l && !l.done && (o = a.return) && o.call(a) } 
    finally { if(r) throw r.error }
  }
  return t
}


 c の大きな配列値の __esModule 以外のキー値を再帰的に e.MathJax に取り込むような処理になっている。

 

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

 i は以下のような関数です。


function(t) {
  var e = "function" == typeof Symbol && Symbol.iterator;
  var r = e && t[e];
  var n = 0;
  if(r) return r.call(t);
  if(t && "number" == typeof t.length) 
    return {
  	  next: function() {
  		return 
          t && 
          n >= t.length && 
          (t = void 0), 
          {
  		    value: t && t[n++],
  			done: !t
  	      }
  	  }
    };
  throw new TypeError(e ? "Object is not iterable." : "Symbol.iterator is not defined.")
}


 最初は e.MathJax.config.loader.pathFilters が 空の配列[] なので、e = Symbol.iterator になり、t[e] で[][Symbol.iterator]となります。空の配列ですが、r に格納され、反復処理プロトコルを保持します。if(r)は0でもfalseでもnullでもないため、成立し return r.call(t) が実行されます。[][Symbol.iterator].call([]) は第一引数が、この [] 自身で、this の対象ですから returnされるのは反復処理プロトコルを保持した空の配列です。[][Symbol.iterator] ですね。これに何の意味があるのか、真意はわかりませんが、next を 保持していて、next() を 呼ぶと value と done のメンバを持ったものが返ってくる。そういうものを反復処理プロトコルと呼んでいます。ようするに for in や for of の inやofの後ろにくるオブジェクトになっているということらしい。自分は初心者なのでまだ、これがどれくらいメリットのあるものなのか理解しきれていませんが、反復される処理への制限がかけれるためデータを保護できるのだとかいうイメージしかもっていません。その実用性は知りません。


 プログラムってのは安全に安全にというコードが余計なことをしないための工夫がされていくものですね。新しい仕組み。一人でやってるとそんなミスしませんけどねとか思っちゃいますけど、それを理解して利用していく人たち。やっぱ上を行ってる。とにかく t が [] だった場合はそんな動き。t が配列だったら、イテレータにしちゃうみたいな関数が、これの役割っぽい。es5(ECMAScriptVersion5)前提のプログラムだからes6の機能と同じことができるように補完してるのかな。

 

e.CONFIG = e.MathJax.config.loader

 e.MathJax.config.loader はこんな値で、メンバ CONFIG にコピーされます。

{
  load: (4) ['input/tex-base', '[tex]/newcommand', '[tex]/action', 'output/chtml'], 
  paths: {mathjax: 'file:///C:/MathJax/es5'}, 
  failed:function(t){return console.log("MathJax(" . concat(t.package || "?", "): ") . concat(t.message))}
  ready: {targetFunction: function(){void 0 !== e.MathJax.startup && e.MathJax.config.startup.ready()},
         name: 'bound',
         }
  source: {}, 
  dependencies: {}, 
  provides: {}, 
  pathFilters: [],
}

 

ページ

 

関連情報

 MathJax

 

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