/**
 * @name CeL function for native (built-in) objects.
 * @fileoverview 本檔案包含了 native objects 的擴充功能。
 * 
 * http://www.hunlock.com/blogs/Ten_Javascript_Tools_Everyone_Should_Have
 * 
 * @see https://github.com/andrewplummer/Sugar
 * @since
 */

'use strict';
// 'use asm';

// --------------------------------------------------------------------------------------------

typeof CeL === 'function' && CeL.run({
	// module name
	name : 'data.native',

	// require : '',

	// 設定不匯出的子函式。
	// no_extend : '*',

	// 為了方便格式化程式碼，因此將 module 函式主體另外抽出。
	code : module_code
});

function module_code(library_namespace) {

	var
	/** {Number}未發現之index。 const: 基本上與程式碼設計合一，僅表示名義，不可更改。(=== -1) */
	NOT_FOUND = ''.indexOf('_');

	/**
	 * null module constructor
	 * 
	 * @class native objects 的 functions
	 */
	var _// JSDT:_module_
	= function() {
		// null module constructor
	};

	/**
	 * for JSDT: 有 prototype 才會將之當作 Class
	 */
	_// JSDT:_module_
	.prototype = {};

	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//

	// cache
	var set_method = library_namespace.set_method,
	// https://en.wikipedia.org/wiki/Unicode_subscripts_and_superscripts
	SUPERSCRIPT_NUMBER = superscript_integer.map = (superscript_integer.digits = '⁰¹²³⁴⁵⁶⁷⁸⁹')
			.split(''),
	//
	SUBSCRIPT_NUMBER = subscript_integer.map = (subscript_integer.digits = '₀₁₂₃₄₅₆₇₈₉')
			.split('');

	SUPERSCRIPT_NUMBER['+'] = '⁺';
	SUPERSCRIPT_NUMBER['-'] = '⁻';
	SUBSCRIPT_NUMBER['+'] = '₊';
	SUBSCRIPT_NUMBER['-'] = '₋';

	function superscript_integer() {
		var v = [];
		this.digits().forEach(function(i) {
			v.push(SUPERSCRIPT_NUMBER[i]);
		});
		return v.join('');
	}

	function subscript_integer() {
		var v = [];
		this.digits().forEach(function(i) {
			v.push(SUBSCRIPT_NUMBER[i]);
		});
		return v.join('');
	}

	/**
	 * padding / fill. 將 string 以 character 補滿至長 length。
	 * 
	 * @see Number.prototype.toLocaleString()
	 *      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
	 * 
	 * TODO: 效能測試:與 "return n > 9 ? n : '0' + n;" 相較。
	 * 
	 * @example <code>

	 // More examples: see /_test suite/test.js

	 * </code>
	 * 
	 * @param {String}string
	 *            基底 string。
	 * @param {Integer}length
	 *            補滿至長 length (maxLength)。
	 * @param {String}fillString
	 *            以 fillString 補滿。
	 * @param {Boolean}from_start
	 *            補滿方向。基本為 5 → ' 5'，設定 from_start 時，為 5 → '5 '。
	 * 
	 * @since 2012/3/25 19:46:42
	 * 
	 * @returns {String} padding 過後之 string
	 */
	function pad(string, length, fillString, from_start) {
		// 為負數作特殊處理。
		// e.g., pad(-9, 3) === '-09'
		if (typeof string === 'number' && string < 0
		//
		&& !from_start && (!fillString || fillString === 0))
			return '-' + pad(-string, length - 1, '0');

		string = String(string);

		// 差距。
		var gap = length - string.length;
		if (gap > 0) {
			// library_namespace.debug(gap + ' [' + fillString + ']');
			if (!fillString || typeof fillString !== 'string') {
				fillString = typeof fillString === 'number' ? String(fillString)
						: string === '' || isNaN(string) ? ' ' : '0';
			}
			// assert: {String}fillString

			var l = fillString.length,
			/**
			 * TODO: binary extend.<br />
			 * .join() is too slow.
			 */
			fill = new Array(l > 1 ? Math.ceil(gap / l) : gap);
			// library_namespace.debug('fill.length = ' + fill.length);

			if (from_start) {
				fill[0] = string;
				fill.length++;
				string = fill.join(fillString);
				if (string.length > length)
					string = string.slice(0, length);
			} else if (l > 1) {
				fill.length++;
				string = fill.join(fillString).slice(0, gap) + string;
			} else {
				fill.push(string);
				string = fill.join(fillString);
			}
		}
		return string;
	}

	_.pad = pad;

	/**
	 * <code>
	function經ScriptEngine會轉成/取用'function'開始到'}'為止的字串

	用[var thisFuncName=parse_function().funcName]可得本身之函數名
	if(_detect_)alert('double run '+parse_function().funcName+'() by '+parse_function(arguments.callee.caller).funcName+'()!');

	You may use this.constructor


	TODO:
	to call: parse_function(this,arguments)
	e.g., parent_func.child_func=function(){var name=parse_function(this,arguments);}

	bug:
	函數定義 .toString() 時無法使用。
	</code>
	 */

	_// JSDT:_module_
	.
	/**
	 * 函數的文字解譯/取得函數的語法
	 * 
	 * @param {Function|String}
	 *            function_name function name or function structure
	 * @param flags
	 *            =1: reduce
	 * @return
	 * @example parsed_data = new parse_function(function_name);
	 * @see http://www.interq.or.jp/student/exeal/dss/ref/jscript/object/function.html,
	 *      Syntax error:
	 *      http://msdn.microsoft.com/library/en-us/script56/html/js56jserrsyntaxerror.asp
	 * @_memberOf _module_
	 * @since 2010/5/16 23:04:54
	 */
	parse_function = function parse_function(function_name, flags) {
		if (!function_name)
			try {
				function_name = parse_function.caller;
				if (typeof function_name !== 'function')
					return;
			} catch (e) {
				return;
			}
		if (typeof function_name === 'string'
				&& !(function_name = library_namespace
						.get_various(function_name)))
			return;

		var fs = String(function_name), m = fs
				.match(library_namespace.PATTERN_function);
		library_namespace.debug(typeof function_name + '\n' + fs + '\n' + m, 6);

		// detect error, 包含引數
		// 原先：functionRegExp=/^\s*function\s+(\w+) ..
		// 因為有function(~){~}這種的，所以改變。
		if (!m) {
			// JScript5 不能用 throw!
			// http://www.oldversion.com/Internet-Explorer.html
			// Syntax error!
			// gettext_config:{"id":"syntax-error"}
			throw new Error(1002, '語法錯誤！');
		}

		if (function_name != m[1]) {
			// Function name unmatched.
			library_namespace.warn({
				// gettext_config:{"id":"function-name-unmatched"}
				T : '函數名稱不相符，可能是用了 reference？'
			});
		}

		library_namespace.debug('function ' + m[1] + '(' + m[2] + '){\n' + m[3]
				+ '\n}', 9);

		return {
			string : fs,
			name : m[1],
			// 去除前後空白
			arguments : m[2].replace(/[\s\n]+/g, '').split(','),
			code : m[3]
		};
	};

	// 補強 String.fromCharCode()
	function fromCharCode(c) {
		if (!isNaN(c))
			return String.fromCharCode(c);
		try {
			// 直接最快
			return eval('String.fromCharCode(' + c + ');');
		} catch (e) {
		}

		// comments
		if (typeof c == 'string') {
			// c=c.split(','); 後者可以通過審查
			return eval('String.fromCharCode(' + n + ')');
		}
		if (typeof c == 'object') {
			var t = '', d, i, a, n = [];
			if (c.length)
				a = c;
			else {
				a = [];
				for (i in c)
					a.push(c[i]);
			}
			for (i = 0; i < a.length; i++)
				if (!isNaN(c = a[i]) || !isNaN(c = ('' + a[i]).charCodeAt(0)))
					// 跳過無法判讀的值
					n.push(c);
			// n.join(',') 這樣較快
			return eval('String.fromCharCode(' + n + ')');
		}
	}

	_// JSDT:_module_
	.
	/**
	 * ASCII_code_at, 對付有時 charCodeAt 會傳回 >256 的數值。 若確定編碼是 ASCII (char code 是
	 * 0~255) 即可使用此函數替代 charCodeAt。
	 * 
	 * @param text
	 *            string
	 * @param position
	 *            at what position
	 * @return
	 * @since 2008/8/2 10:10:49
	 * @see http://www.alanwood.net/demos/charsetdiffs.html
	 * @_memberOf _module_
	 */
	toASCIIcode = function(text, position) {
		var _f = arguments.callee, c;

		if (!_f.t) {
			// initialize
			var i = 129, t = _f.t = [], l = {
				8364 : 128,
				8218 : 130,
				402 : 131,
				8222 : 132,
				8230 : 133,
				8224 : 134,
				8225 : 135,
				710 : 136,
				8240 : 137,
				352 : 138,
				8249 : 139,
				338 : 140,
				381 : 142,
				8216 : 145,
				8217 : 146,
				8220 : 147,
				8221 : 148,
				8226 : 149,
				8211 : 150,
				8212 : 151,
				732 : 152,
				8482 : 153,
				353 : 154,
				8250 : 155,
				339 : 156,
				382 : 158,
				376 : 159
			};
			for (; i < 256; i += 2)
				t[i] = i;
			for (i in l) {
				library_namespace.debug(i + ' = ' + l[i], 6);
				t[i | 0] = l[i];
			}
		}

		if (position < 0 && !isNaN(text))
			c = text;
		else
			c = text.charCodeAt(position || 0);

		return c < 128 ? c : (_f.t[c] || c);
	};

	/**
	 * <code>	2008/8/2 9:9:16
	encodeURI, encodeURIComponent 僅能編成 utf-8，對於其他 local 編碼可使用本函數。

	e.g.,
	f.src='http://www.map.com.tw/search_engine/searchBar.asp?search_class=address&SearchWord='+encodeUC(q[0],'big5')


	perl
	#use Encode qw(from_to);
	use Encode;

	my $tEnc='utf-8';

	$t="金";

	$t=Encode::decode($t,'big5');

	Encode::from_to($t,$lEnc,$outEnc);

	Encode::from_to

	@b=split(//,$a);

	for($i=0;$i<scalar(@b);$i++){
	$r.=sprintf('%%%X',ord($b[$i]));
	};


	</code>
	 */
	// encodeUC[generateCode.dLK]='toASCIIcode';
	function encodeUC(u, enc) {
		if (!enc || enc == 'utf8')
			return encodeURI(u);

		var i = 0, c = new ActiveXObject("ADODB.Stream"), r = [];
		// adTypeText;
		c.Type = 2;
		c.Charset = enc;
		c.Open();
		c.WriteText(u);
		c.Position = 0;
		c.Charset = 'iso-8859-1';
		u = c.ReadText();
		c.Close();

		for (; i < u.length; i++)
			r.push((c = u.charCodeAt(i)) < 0x80 ? u.charAt(i) : '%'
					+ toASCIIcode(c, -1).toString(0x10).toUpperCase());

		return r.join('').replace(/ /g, '+');
	}

	/**
	 * String pattern (e.g., "/a+/g") to RegExp pattern.<br />
	 * escape RegExp pattern，以利作為 RegExp source 使用。<br />
	 * cf. qq// in perl.
	 * 
	 * <code>
	 * String.prototype.to_RegExp_pattern = function(f) { return to_RegExp_pattern(this.valueOf(), f); };
	 * </code>
	 * 
	 * @param {String}pattern
	 *            pattern text.
	 * @param {RegExp}[escape_pattern]
	 *            char pattern need to escape.
	 * @param {Boolean|String}[RegExp_flags]
	 *            flags when need to return RegExp object.
	 * 
	 * @return {String|RegExp} escaped RegExp pattern or RegExp object.
	 */
	function to_RegExp_pattern(pattern, escape_pattern, RegExp_flags) {
		pattern = pattern
		// 不能用 $0。
		.replace(escape_pattern || /([.*?+^$|()\[\]\\{}])/g, '\\$1')
		// 這種方法不完全，例如對 /^\s+|\s+$/g
		.replace(/^([\^])/, '\\^').replace(/(\$)$/, '\\$');

		return RegExp_flags === undefined ? pattern : new RegExp(pattern,
				_.PATTERN_RegExp_flags.test(RegExp_flags) ? RegExp_flags : '');
	}
	_// JSDT:_module_
	.to_RegExp_pattern = to_RegExp_pattern;

	// CeL.ignore_first_char_case('abc') === '[Aa]bc'
	function ignore_first_char_case(pattern) {
		// pattern 無特殊字元！否則應該出警告。
		var lower_case = pattern.charAt(0),
		//
		upper_case = lower_case.toUpperCase();
		if (upper_case !== lower_case
		//
		|| upper_case !== (lower_case = upper_case.toLowerCase()))
			pattern = '[' + upper_case + lower_case + ']' + pattern.slice(1);
		return pattern;
	}
	_// JSDT:_module_
	.ignore_first_char_case = ignore_first_char_case;

	// CeL.ignore_case_pattern('abc') === '[Aa][Bb][Cc]'
	function ignore_case_pattern(pattern, only_first_char) {
		pattern = pattern.split('');
		// pattern 無特殊字元！否則應該出警告。
		pattern.forEach(function(lower_case, index) {
			var upper_case = lower_case.toUpperCase();
			if (upper_case !== lower_case
			//
			|| upper_case !== (lower_case = upper_case.toLowerCase()))
				pattern[index] = '[' + upper_case + lower_case + ']';
		})
		return pattern.join('');
	}
	_// JSDT:_module_
	.ignore_case_pattern = ignore_case_pattern;

	// pattern.replace(string)
	// 警告: 必須自行檢查 string! 否則會出現 pattern.replace(undefined) === 'undefined'
	function pattern_replace(string) {
		// assert: {RegExp}this pattern
		// assert: pattern has .replace_to
		return String(string).replace(this, this.replace_to);
	}

	if (false) {
		pattern = '/move from/g'.to_RegExp();
		pattern = '/move from/replace to/g'.to_RegExp({
			allow_replacement : true
		});
		pattern.replace('*move from*') === '*replace to*';
	}

	/**
	 * 將 String pattern (e.g., "/a+/g") 轉成 RegExp。<br />
	 * TODO:<br />
	 * and, or, not.<br />
	 * (?:(^|\s*\|)\s*(!)?(\/(?:[^\/]+|\\\/)(\/([a-z]*))?|\\(\S+)|\S+))+<br />
	 * {Object|Array}preprocessor<br />
	 * 
	 * cf. CeL.to_RegExp_pattern()
	 * 
	 * @param {String}pattern
	 *            欲轉換成 RegExp 的 pattern text。
	 * @param {Object}[options]
	 *            附加參數/設定特殊功能與選項 options = {<br />
	 *            {String}flags : RegExp 的 flags。<br />
	 *            {Function|String}error_handler : 當遇到不明 pattern 時的處理程序。<br /> }
	 * 
	 * @returns {RegExp} RegExp object。
	 * 
	 * @since 2012/10/13 10:22:20
	 */
	function String_to_RegExp(pattern, options) {
		// 前置作業。
		if (typeof options === 'string' && _.PATTERN_RegExp_flags.test(options)) {
			options = {
				flags : options
			};
		} else if (typeof options === 'function') {
			options = {
				error_handler : options
			};
		} else {
			options = library_namespace.setup_options(options);
		}

		if (typeof pattern === 'string') {
			if (typeof String_to_RegExp.preprocessor === 'function')
				pattern = String_to_RegExp.preprocessor(pattern);

			if (typeof pattern === 'string' && pattern.length > 1)
				// pattern.trim()
				if (pattern.charAt(0) === '/') {
					library_namespace.debug({
						// gettext_config:{"id":"treat-$1-as-regexp"}
						T : [ 'Treat [%1] as RegExp.', pattern ]
					}, 3, 'String_to_RegExp');
					var matched = pattern.match(_.PATTERN_RegExp), replace_to;
					if (!matched && options.allow_replacement
					//
					&& (matched = pattern.match(_.PATTERN_RegExp_replacement))) {
						replace_to = matched[2];
						// matched[2] = matched[3];
						matched.splice(2, 1);
					}
					// 設定 flags。
					var flags = _.PATTERN_RegExp_flags.test(options.flags)
							&& options.flags
							|| (matched ? matched[2]
									: String_to_RegExp.default_flags);

					try {
						try {
							pattern = new RegExp(matched ? matched[1] : pattern
									.slice(1), flags);
						} catch (e) {
							try {
								if (matched) {
									// 設定絕對可接受的 flags，或完全不設定。
									pattern = new RegExp(matched[1]);
									library_namespace.warn([
									//
									'String_to_RegExp: ', {
										// gettext_config:{"id":"invalid-flags-$1"}
										T : [ 'Invalid flags: [%1]', flags ]
									} ]);
								} else
									throw true;
							} catch (e) {
								library_namespace.warn([
								// Illegal pattern: /%1/
								'String_to_RegExp: ', {
									// gettext_config:{"id":"illegal-pattern-$1"}
									T : [ 'Illegal pattern: [%1]', matched[1] ]
								} ]);
							}
						}
					} catch (e) {
						library_namespace.debug({
							// gettext_config:{"id":"conversion-mode-$1-error-invalid-regexp?-$2"}
							T : [ '轉換模式 [%1] 出錯：並非 RegExp？ %2', pattern,
									e.message ]
						}, 2, 'String_to_RegExp');
					}

					if (replace_to) {
						pattern.replace_to = replace_to;
						if (false) {
							pattern.replace = function replace(string) {
								return string.replace(pattern, replace_to);
							};
						}
						pattern.replace = pattern_replace;
					}

				} else if (pattern.charAt(0) === '\\'
						&& typeof library_namespace.wildcard_to_RegExp === 'function') {
					library_namespace.debug({
						T : [
						// gettext_config:{"id":"treat-pattern-$1-as-windows-wildcard-search-string"}
						'Treat pattern [%1] as Windows wildcard search string.'
						//
						, pattern ]
					}, 3, 'String_to_RegExp');
					pattern = new RegExp(library_namespace
							.wildcard_to_RegExp(pattern));
				}

			if (typeof pattern === 'string')
				try {
					pattern = typeof options.error_handler === 'function'
					// default unknown handler.
					? options.error_handler(pattern) : new RegExp(pattern
					// .replace(/,/g, '|')
					);
				} catch (e) {
					library_namespace.debug({
						// gettext_config:{"id":"unable-to-convert-mode-$1"}
						T : [ '無法轉換模式 [%1]！', pattern ]
					}, 3, 'String_to_RegExp');
				}
		}

		return pattern;
	}

	String_to_RegExp.default_flags = 'i';

	// 前置處理。
	String_to_RegExp.preprocessor = function(pattern) {
		var m;
		if (pattern.length < 800
				&& (m = pattern
						.match(/^／((?:＼／|[^\\\/|?*":<>／\0-\x1f]+)+)／([a-z]*)(?:\.[^.]+)?$/)))
			try {
				/**
				 * @see application.net.to_file_name()
				 */
				library_namespace.debug('因為 pattern [' + pattern
						+ '] 以 "／" 起首，可能是以 directory name / file name'
						+ ' 充當 pattern，嘗試將之還原為 regular pattern。', 2,
						'String_to_RegExp.preprocessor');

				pattern = new RegExp(m[1]
				// functional characters
				.replace(/＼/g, '\\').replace(/／/g, '/').replace(/｜/g, '|')
				//
				.replace(/？/g, '?').replace(/＊/g, '*')
				//
				.replace(/((?:^|[^\\])(?:\\\\)*)\\([\\\/|?*])/g,
				//
				function($0, $1, $2) {
					return $1 + '[\\$2' + {
						'\\' : '＼',
						'/' : '／',
						'|' : '｜',
						'?' : '？',
						'*' : '＊'
					}[$2] + ']';
				})

				// normal characters
				.replace(/＂/g, '["＂]').replace(/：/g, '[:：]').replace(/＜/g,
						'[<＜]').replace(/＞/g, '[>＞]')

				// control characters
				.replace(/_/g, '[_\\r\\n\\t\\f\\v]'), m[2]);

			} catch (e) {
			}

		return pattern;
	};

	/**
	 * 將 string 轉成 search pattern，並回傳是否 matched。
	 * 
	 * @param {String}pattern
	 *            欲轉換成 RegExp 的 pattern。
	 * @param {String}[text]
	 *            欲測試的 text。
	 * @param {Function}[unknown_handler]
	 *            當遇到不明 pattern 時的處理程序。
	 * @returns 是否 matched。
	 * 
	 * @since 2012/10/13 10:22:20
	 * 
	 * @see CeL.data.fit_filter()
	 */
	function is_matched(pattern, text, unknown_handler) {
		pattern = String_to_RegExp(pattern, unknown_handler);

		if (typeof text !== 'string')
			if (typeof text === 'undefined' || text === null)
				return pattern;
			else
				text = String(text);

		return library_namespace.is_RegExp(pattern) ? text.match(pattern)
				: text.indexOf(String(pattern)) !== NOT_FOUND;
	}

	_.is_matched = is_matched;

	var RegExp_flags = /./g.flags === 'g'
	// get RegExp.prototype.flags
	? function(regexp) {
		return regexp.flags;
	} : function(regexp) {
		// regexp = RegExp.prototype.toString.call(regexp);
		// return ('' + regexp).match(/[^\/]*$/)[0];
		regexp = '' + regexp;
		return regexp.slice(regexp.lastIndexOf('/') + 1);

		var flags = [];
		for ( var flag in RegExp_flags.flags)
			if (regexp[flag])
				flags.push(RegExp_flags.flags[flag]);
		return flags.join('');
	};

	library_namespace.RegExp_flags = RegExp_flags;

	// RegExp.prototype.flags
	// 注意: 本 shim 實際上應放置於 data.code.compatibility。惟其可能會被省略執行，因此放置於此。
	if (!('flags' in RegExp.prototype)
	//
	&& !Object.defineProperty[library_namespace.env.not_native_keyword])
		Object.defineProperty(RegExp.prototype, 'flags', {
			get : function() {
				return RegExp_flags(this);
			}
		});

	// https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags
	RegExp_flags.flags = {
		// Proposed for ES6
		// extended : 'x',
		global : 'g',
		ignoreCase : 'i',
		multiline : 'm',
		dotAll : 's',
		unicode : 'u',
		sticky : 'y',
		// https://github.com/tc39/proposal-regexp-match-indices
		indices : 'd'
	};

	if (Object.values) {
		_.PATTERN_RegExp_flags = Object.values(RegExp_flags.flags);
	} else {
		// e.g., @ WSH (Windows Script Host)
		_.PATTERN_RegExp_flags = [];
		(function() {
			for ( var flag in RegExp_flags.flags)
				_.PATTERN_RegExp_flags.push(RegExp_flags.flags[flag]);
		})();
	}
	// TODO: flags 只能出現一次!
	_.PATTERN_RegExp_flags = _.PATTERN_RegExp_flags.join('');
	// CeL.PATTERN_RegExp
	// [ all, pattern source, flags ]
	_.PATTERN_RegExp = new RegExp(
	// /^\/(.+)\/([iugms]*)$/
	// /^\/((?:\\[\s\S]|[^\/])+)\/([gimsuy]*)$/
	/^\/((?:\\[\s\S]|[^\/])+)\/([flags]*)$/.source.replace('flags',
			_.PATTERN_RegExp_flags));

	// CeL.PATTERN_RegExp_replacement
	// e.g., '/只/隻/i'
	_.PATTERN_RegExp_replacement = new RegExp(
	// [ all, pattern source, replace to, flags ]
	/^\/((?:\\[\s\S]|[^\/])+)\/((?:\\[\s\S]|[^\/])*)\/([flags]*)$/.source
			.replace('flags', _.PATTERN_RegExp_flags));

	// CeL.PATTERN_RegExp_flags
	_.PATTERN_RegExp_flags = new RegExp(/^[flags]+$/.source.replace('flags',
			_.PATTERN_RegExp_flags));

	/**
	 * <code>

	use (new RegExp(regexp.source, flags)) instead.
	or even (new RegExp(regexp, flags)):
	RexExp constructor no longer throws when the first argument is a RegExp and the second argument is present. Instead it creates a new RegExp using the same patterns as the first arguments and the flags supplied by the second argument.

	</code>
	 */

	/**
	 * 重新設定 RegExp object 之 flags. change the flags of a RegExp instances.
	 * 
	 * @param {RegExp}regexp
	 *            RegExp object to set
	 * @param {String}flags
	 *            flags of RegExp
	 * @return {RegExp}
	 * @example <code>

	// 附帶 'g' flag 的 RegExp 對相同字串作 .test() 時，第二次並不會重設。
	// 因此像下面的 expression 兩次並不會得到相同結果。
	var r = /,/g, t = 'a,b';
	WScript.Echo(r.test(t) + ',' + r.test(t));

	// 改成這樣就可以了：
	var r = /,/g, t = 'a,b', s = renew_RegExp_flags(r, '-g');
	WScript.Echo(s.test(t) + ',' + s.test(t));

	// 這倒沒問題：
	r = /,/g, a = 'a,b';
	if (r.test(a))
		library_namespace.debug(a.replace(r, '_'));

	// delete r.lastIndex; 無效，得用 r.lastIndex = 0; 因此下面的亦可：
	if (r.global)
		r.lastIndex = 0;
	if (r.test(a)) {
		// ...
	}

	</code>
	 * 
	 * @see http://msdn.microsoft.com/zh-tw/library/x9h97e00(VS.80).aspx,
	 *      如果規則運算式已經設定了全域旗標，test 將會從 lastIndex 值表示的位置開始搜尋字串。如果未設定全域旗標，則 test
	 *      會略過 lastIndex 值，並從字串之首開始搜尋。
	 *      http://www.aptana.com/reference/html/api/RegExp.html
	 * @_memberOf _module_
	 */
	function renew_RegExp_flags(regexp, flags) {
		// 未指定 flags: get flags
		if (!flags) {
			flags = '';
			for ( var i in RegExp_flags.flags)
				if (regexp[i])
					flags += RegExp_flags.flags[i];
			return flags;
		}

		var a = flags.charAt(0), F = '', m;
		a = a === '+' ? 1 : a === '-' ? 0 : (F = 1);

		if (F) {
			// 無 [+-]
			F = flags;
		} else {
			// f: [+-]~ 的情況，parse flags
			for ( var i in RegExp_flags.flags)
				if ((m = flags.indexOf(RegExp_flags.flags[i], 1) !== NOT_FOUND)
						&& a || !m && regexp[i])
					F += RegExp_flags.flags[i];
		}

		// for JScript<=5
		try {
			return new RegExp(regexp.source, F);
		} catch (e) {
			// TODO: handle exception
		}
	}

	_// JSDT:_module_
	.renew_RegExp_flags = renew_RegExp_flags;

	// ---------------------------------------------------------------------//

	// Unicode category

	// 使用例之說明：
	// @see CeL.data.native for Unicode category (e.g., \p{Cf})

	if (false) {
		var
		/**
		 * 振り仮名 / 読み仮名 の正規表現。
		 * 
		 * @type {RegExp}
		 * @see [[d:Property:P1814|假名]]
		 */
		PATTERN_読み仮名 = CeL.RegExp(/^[\p{Hiragana}\p{Katakana}ー・ 　]+$/, 'u');
	}

	// https://github.com/slevithan/xregexp/blob/master/tools/output/categories.js
	// http://stackoverflow.com/questions/11598786/how-to-replace-non-printable-unicode-characters-javascript
	var Unicode_category = {
		// Control
		Cc : '\0-\x1F\x7F-\x9F',
		// Format
		Cf : '\xAD\u0600-\u0605\u061C\u06DD\u070F\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB',
		// Unassigned
		Cn : '\u0378\u0379\u0380-\u0383\u038B\u038D\u03A2\u0530\u0557\u0558\u0560\u0588\u058B\u058C\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u05FF\u061D\u070E\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08B5-\u08E2\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0AF8\u0AFA-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0BFF\u0C04\u0C0D\u0C11\u0C29\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D00\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5E\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F6\u13F7\u13FE\u13FF\u169D-\u169F\u16F9-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE\u1AAF\u1ABF-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7\u1CFA-\u1CFF\u1DF6-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u2065\u2072\u2073\u208F\u209D-\u209F\u20BF-\u20CF\u20F1-\u20FF\u218C-\u218F\u23FB-\u23FF\u2427-\u243F\u244B-\u245F\u2B74\u2B75\u2B96\u2B97\u2BBA-\u2BBC\u2BC9\u2BD2-\u2BEB\u2BF0-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E43-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FD6-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA6F8-\uA6FF\uA7AE\uA7AF\uA7B8-\uA7F6\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FE\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB66-\uAB6F\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD\uFEFE\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFF8\uFFFE\uFFFF',
		// Private_Use
		Co : '\uE000-\uF8FF',
		// Surrogate
		Cs : '\uD800-\uDFFF',
		// Other
		C : '\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u0380-\u0383\u038B\u038D\u03A2\u0530\u0557\u0558\u0560\u0588\u058B\u058C\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08B5-\u08E2\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0AF8\u0AFA-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0BFF\u0C04\u0C0D\u0C11\u0C29\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D00\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5E\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F6\u13F7\u13FE\u13FF\u169D-\u169F\u16F9-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180E\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE\u1AAF\u1ABF-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7\u1CFA-\u1CFF\u1DF6-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BF-\u20CF\u20F1-\u20FF\u218C-\u218F\u23FB-\u23FF\u2427-\u243F\u244B-\u245F\u2B74\u2B75\u2B96\u2B97\u2BBA-\u2BBC\u2BC9\u2BD2-\u2BEB\u2BF0-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E43-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FD6-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA6F8-\uA6FF\uA7AE\uA7AF\uA7B8-\uA7F6\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FE\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB66-\uAB6F\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF',

		// 振り仮名 / 読み仮名
		// http://www.unicode.org/charts/PDF/U3040.pdf
		Hiragana : '\u3041-\u3096\u309D-\u309F',
		Katakana : '\u30A1-\u30FA\u30FD-\u30FF\u31F0-\u31FF\u32D0-\u32FE\u3300-\u3357\uFF66-\uFF6F\uFF71-\uFF9D'
	};

	if (!('unicode' in RegExp.prototype)) {
		Unicode_category.C = Unicode_category.C.replace('\uD7FC',
		// exclude surrogate pair control characters
		// (surrogate code point, \uD800-\uDFFF)
		'\uD7FC-\uD7FF\uE000');
	}

	// invalid characters @ wikitext, XML.
	Unicode_category.invalid = Unicode_category.C.replace('\0',
	// 去除 \t\n\r
	'\0-\x08\x0B\x0C\x0E');

	/**
	 * 可以使用 /\p{C}/u 之類的 RegExp。
	 * 
	 * @param {String|RegExp}source
	 *            source of RegExp instance.
	 * @param {String}[flags]
	 *            flags of RegExp instance.
	 * 
	 * @returns {RegExp}RegExp instance.
	 */
	function new_RegExp(source, flags) {
		if (has_Unicode_flag) {
			try {
				return new RegExp(source, flags);
			} catch (e) {
				// e.g., 自行設定了 Unicode_category
			}
		}

		if (library_namespace.is_RegExp(source)) {
			if (flags === undefined)
				flags = source.flags;
			source = source.source;
		}

		if (typeof flags === 'string' && flags.includes('u')) {
			if (!has_Unicode_flag)
				flags = flags.replace(/u/g, '');

			// 後處理 Unicode category。
			source = source.replace(/\\p{([A-Z][A-Za-z_]*)}/g, function(all,
					category) {
				return Unicode_category[category] || all;
			});
		}

		return new RegExp(source, flags);
	}

	new_RegExp.category = Unicode_category;

	var has_Unicode_flag;
	try {
		if (has_Unicode_flag = ('unicode' in RegExp.prototype)
				&& new RegExp(/\p{C}/, 'u')) {
			has_Unicode_flag = has_Unicode_flag.test('\u200E')
					&& has_Unicode_flag.unicode
					&& has_Unicode_flag.flags === 'u';
		}
	} catch (e) {
		has_Unicode_flag = false;
	}
	_.RegExp = new_RegExp;

	// ---------------------------------------------------------------------//

	/**
	 * <code>	2004/5/27 16:08
	將 MS-DOS 萬用字元(wildcard characters)轉成 RegExp, 回傳 pattern
	for search

	usage:
	p=new RegExp(wildcard_to_RegExp('*.*'))


	flags&1	有變化的時候才 return RegExp
	flags&2	add ^$


	萬用字元經常用在檔名的置換。
	 * 代表任意檔案名稱
	如：ls * 表示列出所有檔案名稱。
	? 則代表一個字元
	如: ls index.??? 表示列出所有 index.三個字元 的檔案名稱
	[ ] 代表選擇其中一個字元
	[Ab] 則代表 A 或 b 二者之中的一個字元
	如: ls [Ab]same 為 Asame 或 bsame
	[! ] 代表除外的一個字元
	[!Ab] 則代表 不是 A 且 不是 b 的一個字元
	如: [!0-9] 表不是數字字元
	如: *[!E] 表末尾不是 E 的檔名

	memo:
	檔案名稱不可包含字元	** 不包含目錄分隔字元 [\\/]:
	/:*?"<>|/

	</code>
	 */

	// 萬用字元 RegExp source, ReadOnly
	// wildcard_to_RegExp.w_chars = '*?\\[\\]';
	wildcard_to_RegExp.w_chars = '*?';

	function wildcard_to_RegExp(pattern, flags) {

		if (library_namespace.is_RegExp(pattern))
			return pattern;
		if (!pattern || typeof pattern !== 'string')
			return;

		var ic = wildcard_to_RegExp.w_chars, r;
		if ((flags & 1) && !new RegExp('[' + ic + ']').test(pattern))
			return pattern;

		ic = '[^' + ic + ']';
		if (false) {
			// old: 考慮 \
			r = pattern.replace(/(\\*)(\*+|\?+|\.)/g, function($0, $1, $2) {
				var c = $2.charAt(0);
				return $1.length % 2 ? $0 : $1
						+ (c === '*' ? ic + '*' : c === '?' ? ic + '{'
								+ $2.length + '}' : '\\' + $2);
			})
		}

		r = pattern

		// 處理目錄分隔字元：多轉一，'/' → '\\' 或相反
		.replace(/[\\\/]+/g, library_namespace.env.path_separator)

		// 在 RegExp 中有作用，但非萬用字元，在檔名中無特殊作用的
		.replace(/([().^$\-])/g, '\\$1')

		// * 代表任意檔案字元
		.replace(/\*+/g, '\0*')

		// ? 代表一個檔案字元
		.replace(/\?+/g, function($0) {
			return '\0{' + $0.length + '}';
		})

		// [ ] 代表選擇其中一個字元
		// pass
		.replace(/([\[\]])/g, '\\$1')

		// [! ] 代表除外的一個字元
		// pass
		// .replace(/\[!([^\]]*)\]/g, '[^$1]')

		// translate wildcard characters
		.replace(/\0+/g, ic)

		;

		// console.trace(r);

		// 有變化的時候才 return RegExp
		if (!(flags & 1) || pattern !== r) {
			try {
				pattern = new RegExp(flags & 2 ? '^' + r + '$' : r, 'i');
			} catch (e) {
				// 輸入了不正確的 RegExp：未預期的次數符號等
			}
		}

		return pattern;
	}

	_.wildcard_to_RegExp = wildcard_to_RegExp;

	function remove_Object_value(object, value) {
		for ( var i in object)
			if (object[i] === value)
				delete object[i];
	}

	_// JSDT:_module_
	.remove_Object_value = remove_Object_value;

	// string & Number 處理 -----------------------------------------------

	var PATTERN_SPACES = /[ _]+/g,
	// Punctuation marks 無實際意義的標點符號
	PUNCTUATION_MARKS = /[ _,.;:?'"`~!@#$%^&*()\/\-\[\]<>]+/g;

	// String.covers(string_1, string_2, options)
	// string_1.covers(string_2, options)
	// @see Knuth–Morris–Pratt algorithm
	/**
	 * @return true: 兩者相同, false: 兩者等長但不相同,<br />
	 *         1: str2為str1之擴展 (str2涵蓋str1), -1: str1為str2之擴展, 2: 兩者等價, 0: 皆非
	 */
	function String_covers(string_1, string_2, options) {
		// 前置作業。
		options = library_namespace.setup_options(options);

		// 預先處理函數. e.g., 是否忽略大小寫
		if (options && typeof options.preprocessor === 'function') {
			string_1 = options.preprocessor(string_1);
			string_2 = options.preprocessor(string_2);
		}

		// ignore punctuation marks
		if (options.ignore_marks) {
			string_1 = string_1.replace(PATTERN_PUNCTUATION_MARKS, '');
			string_2 = string_2.replace(PATTERN_PUNCTUATION_MARKS, '');
		} else if (options.ignore_spaces) {
			// assert: PATTERN_PUNCTUATION_MARKS including PATTERN_SPACES
			string_1 = string_1.replace(PATTERN_SPACES, '');
			string_2 = string_2.replace(PATTERN_SPACES, '');
		}

		if (string_1.length === string_2.length) {
			if (string_1 === string_2)
				return true;
			if (!options || !options.force
					|| typeof options.equals !== 'function')
				// 就算兩者等長但不相同，還是有可能等價。
				return false;
		}

		var result = 1;
		// swap: string_2 轉成長的。 (短,長)
		if (string_1.length > string_2.length) {
			result = string_2, string_2 = string_1, string_1 = result;
			result = -1;
		}

		// string_1 = string_1.replace(/\s+/g, ' ');

		string_1 = string_1.chars(true);
		string_2 = string_2.chars(true);

		var string_1_index = 0, string_2_index = 0, character_1 = string_1[0],
		// comparer
		equals = options && typeof options.equals === 'function' ? options.equals
				: String_covers.equals;

		string_2.some(function(character_2, index) {
			if (equals(character_1, character_2))
				if (++string_1_index === string_1.length) {
					string_2_index = index;
					return true;
				} else
					character_1 = string_1[string_1_index];
		});

		return string_1_index === string_1.length ? result : 0;
	}

	String_covers.equals = function(a, b) {
		return a === b;
	};

	// compare file name. 比較檔名是否相同。str2 為 str1 添加字元後的擴展？表示兩檔名等價
	String_covers.file_name_equals = function(a, b) {
		return a === b || /^[ ・.]+$/.test(a + b) || /^[-～]+$/.test(a + b)
				|| /^[［\[]+$/.test(a + b) || /^[］\]]+$/.test(a + b);
	};

	set_method(String, {
		covers : String_covers,
		similarity : similarity_coefficient
	});

	function split_String_by_length_(s, l, m) {
		var
		// less than
		lt, lt2,
		// great than
		gt,
		// index
		i = 0,
		// left count index(left length now)
		c = l,
		// text now
		t = '',
		// text index
		I = 0;
		while (I < s.length) {
			// 將lt,gt定在下一label之首尾,i為下一次搜尋起點.label定義:/<.+?>/
			if (i !== NOT_FOUND)
				if ((lt = s.indexOf('<', i)) !== NOT_FOUND) {
					if ((gt = s.indexOf('>', lt + 1)) === NOT_FOUND)
						i = lt = NOT_FOUND;
					else {
						i = gt + 1;
						while (lt !== NOT_FOUND
								&& (lt2 = s.indexOf('<', lt + 1)) !== NOT_FOUND
								&& lt2 < gt)
							lt = lt2;
					}
				} else
					i = lt = NOT_FOUND;
			if (false && s.indexOf('') !== NOT_FOUND)
				alert(i + ',' + lt + ',' + gt + ';' + l + ',' + c + '\n' + t);
			if (lt === NOT_FOUND)
				gt = lt = s.length;
			// 未來:考慮中英文大小，不分隔英文字。前提:'A'<'z'..或許不用
			while (I + c <= lt) {
				t += s.substr(I, c) + (m ? '\n' : '<br />');
				I += c;
				c = l;
			}
			t += s.slice(I, gt + 1);
			c -= lt - I;
			I = gt + 1;
		}
		return t;
	}
	/*
	 * 將字串以長l分隔, split String by fixed length. m==0: html用, 1:text.
	 */
	// split_String_by_length[generateCode.dLK]='split_String_by_length_';
	function split_String_by_length(l, m) {
		var s = this.valueOf(), t = [], sp = '<br />';
		if (!s || !l || l < 1
		// ||!String.charCodeAt: for v5.5
		|| !String.fromCharCode)
			return m ? s.gText() : s;
		// (m):這樣就不用再費心思了.不過既然都作好了,就留著吧..不,還是需要
		s = s.turnU(m);
		if (s.length <= l)
			return s;
		if (!m)
			s = s.replace(/<w?br([^>]*)>/gi, sp);

		// deal with line
		s = s.split(sp = m ? '\n' : sp);
		try {
			// 預防JS5不能push
			for (var i = 0; i < s.length; i++)
				t.push(split_String_by_length_(s[i], l, m));
		} catch (e) {
			return this.valueOf();
		}
		return t.join(sp);
	}

	/**
	 * 將字串以長 size 切割。
	 * 
	 * @param {Integer}size
	 *            切割大小。可以 ((.length / count) | 0 ) 取得。
	 * @returns {Array} chunks
	 * 
	 * @see <a
	 *      href="http://stackoverflow.com/questions/7033639/javascript-split-large-string-in-n-size-chunks"
	 *      accessdate="2015/3/2 23:27">regex - javascript: Split large string
	 *      in n-size chunks - Stack Overflow</a>
	 */
	function chunk(size) {
		if ((size |= 0) < 1)
			return [ this ];

		var index = 0, length = this.length, result = [];
		for (; index < length; index += size)
			result.push(this.substr(index, size));

		return result;
	}

	// ---------------------------------------------------------------------//

	var no_string_index;
	// for IE 6. Or use .chars(), .split(''), .charAt()
	try {
		no_string_index = '01';
		no_string_index = !(no_string_index[1] === '1');
	} catch (e) {
		// e.g., IE 6
		no_string_index = true;
	}

	// To test if RegExp.prototype has unicode flag:
	// if ('unicode' in RegExp.prototype) {}
	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp

	var has_spread_syntax;
	try {
		has_spread_syntax = eval("[...'ab'].join(',')==='a,b'");
	} catch (e) {
	}

	var PATTERN_char, PATTERN_char_with_combined, split_by_code_point;
	try {
		// using [\s\S] or [^] or /./s
		// @see https://github.com/tc39/proposal-regexp-dotall-flag
		// tested @ Edge/12.10240
		PATTERN_char = new RegExp(/[\s\S]/.source, 'ug');
		// 注意:因為/./u會切分[[en:Combining character#Unicode ranges]]，
		// 因此對組合字符，得要另外處理。
		PATTERN_char_with_combined = new RegExp(
				/[\s\S][\u0300-\u036F\uFE20-\uFE2F\u20D0-\u20FF\u1DC0-\u1DFF\u1AB0-\u1AFF]*/.source,
				'ug');

		/**
		 * 對於可能出現 surrogate pairs 的字串，應當以此來取代 .split('')！<br />
		 * handling of surrogate pairs / code points
		 * 
		 * TODO: 利用.split('')增進效率。
		 * 
		 * @see https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B10000_to_U.2B10FFFF
		 *      http://teppeis.hatenablog.com/entry/2014/01/surrogate-pair-in-javascript
		 */
		split_by_code_point = function(with_combined) {
			// if (has_spread_syntax && !with_combined) return [...this];
			return this.match(with_combined ? PATTERN_char_with_combined
					: PATTERN_char)
					|| [];
			// show HEX:
			// .map(function(char){return
			// char.codePointAt(0).toString(0x10).toUpperCase();});
		};
	} catch (e) {
		// 舊版。
		var PATTERN_surrogate = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
		PATTERN_char = /[\uD800-\uDBFF][\uDC00-\uDFFF]|[\s\S]/g;
		PATTERN_char_with_combined = /(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[\s\S])[\u0300-\u036F\uFE20-\uFE2F\u20D0-\u20FF\u1DC0-\u1DFF\u1AB0-\u1AFF]*/g;
		split_by_code_point = function(with_combined) {
			return with_combined // || !PATTERN_surrogate
					|| PATTERN_surrogate.test(this) ? this
					.match(with_combined ? PATTERN_char_with_combined
							: PATTERN_char)
					|| [] : this.split('');
		};
	}

	// String.prototype.codePoints()
	// http://docs.oracle.com/javase/8/docs/api/java/lang/CharSequence.html#codePoints--
	function codePoints() {
		return split_by_code_point.call(this)
		// need data.code.compatibility!
		.map(function(char) {
			return char.codePointAt(0);
		});
	}

	/**
	 * get string between head and foot.<br />
	 * 取得 text 中，head 與 foot 之間的字串。不包括 head 與 foot。<br />
	 * 可以 [3] last index 是否回傳 NOT_FOUND (-1) 檢測到底是有找到，只是回傳空字串，或是沒找到。
	 * 
	 * TODO: {RegExp}head, {RegExp}foot
	 * 
	 * @example <code>

	 // More examples: see /_test suite/test.js

	 * </code>
	 * 
	 * @param {Array}data [
	 *            text 欲篩選字串, head 首字串, foot 尾字串, start index ]
	 * 
	 * @returns [ 0: text 欲篩選字串, 1: head 首字串, 2: foot 尾字串, 3: last index, 4:
	 *          head 與 foot 之間的字串 ]
	 * 
	 * @since 2014/8/4 21:34:31
	 */
	function get_intermediate_Array(data) {
		if (data && data[0]
		// = String(data[0])
		) {
			// start index of intermediate.
			var index;
			if (!data[1])
				index = 0;
			else if ((index = data[0].indexOf(data[1], data[3])) !== NOT_FOUND)
				index += data[1].length;
			// library_namespace.debug('head index: ' + index, 4);

			if ((data[3] = index) !== NOT_FOUND
					&& (!data[2] || (data[3] = data[0].indexOf(data[2], index)) !== NOT_FOUND))
				data[4] = data[2] ? data[0].slice(index, data[3])
				//
				: index ? data[0].slice(index)
				//
				: data[0];
		}
		return data;
	}

	/**
	 * get string between head and foot.<br />
	 * 取得 text 中，head 與 foot 之間的字串。不包括 head 與 foot。<br />
	 * 回傳 undefined 表示沒找到。只是回傳空字串表示其間為空字串。
	 * 
	 * TODO: lastIndexOf()
	 * 
	 * @example <code>

	 // More examples: see /_test suite/test.js

	 * </code>
	 * 
	 * @param {String}text
	 *            欲篩選字串。
	 * @param {String}[head]
	 *            首字串。 TODO: RegExp
	 * @param {String}[foot]
	 *            尾字串。 TODO: RegExp
	 * 
	 * @returns head 與 foot 之間的字串。undefined 表示沒找到。
	 * 
	 * @since 2014/7/26 11:28:18
	 */
	function get_intermediate(text, head, foot, index, return_data) {
		if (text
		// = String(text)
		) {
			// start index of intermediate.
			if (!head) {
				index = 0;
			} else if ((index = text.indexOf(head, index | 0)) !== NOT_FOUND) {
				index += head.length;
			}
			library_namespace.debug('head index: ' + index, 4);

			if (index !== NOT_FOUND && (!foot || (foot = foot.tail
			// 可以用 {tail:'foot'} 來從結尾搜尋。from tail
			? text.lastIndexOf(foot.tail)
			// 正常:從頭搜尋。
			: text.indexOf(foot, index)) !== NOT_FOUND)) {
				head = foot ? text.slice(index, foot) : index > 0 ? text
						.slice(index) : text;
				return return_data ?
				// [ last index, head 與 foot 之間的字串 ]
				[ foot || text.length, head ] : head;
			}
		}

		if (return_data) {
			return [ NOT_FOUND, ];
		}
	}

	_.get_intermediate = get_intermediate;

	/**
	 * <code>

	// 推薦 use .find_between(), 見下一段例
	var data = html.find_between('>', '<'), text;

	text = data.search().search().search().toString();

	while (data.next()) {
		text = data.toString();
	}

	// method 1:
	while (term = terms.next()) {
		term.toString();
	}

	// method 2:
	while (typeof (text = data.search()) === 'string') {
		;
	}

	</code>
	 */
	function next_intermediate(index) {
		if (this[3] !== NOT_FOUND) {
			var data = get_intermediate(this[0], this[1], this[2], this[3],
					true);
			library_namespace.debug('Get [' + data + ']', 4);
			if ((this[3] = data[0]) !== NOT_FOUND) {
				this[4] = data[1];
				return this;
			}
		}
	}
	// find out next
	// 為避免 overwrite Array.prototype.find()，因此改名為 search。
	function search_intermediate(index) {
		this.next();
		return this;
	}
	function intermediate_result() {
		return this[3] !== NOT_FOUND && this[4] || '';
	}
	function intermediate_between() {
		return String.prototype.between.apply(this.toString(), arguments);
	}
	// 2017/1/3 13:48:21 API change:
	// set_intermediate()→find_between()
	// WARNING: 請盡可能採用find_between()，勿使用deprecated_find_between()。
	// 要用此函數，不如直接採用RegExp.prototype.exec()比較快。
	function deprecated_find_between(head, foot, index) {
		var data = [ this, head, foot, index | 0 ];
		data.next = next_intermediate;
		data.search = search_intermediate;
		data.toString = intermediate_result;
		// data.between = intermediate_between;
		return data;
	}

	/**
	 * 2017/2/15 16:5:0 API change: rename to .find_between<br />
	 * all_between()→find_between()
	 * 
	 * TODO:<br />
	 * return {Iterator} of all between
	 * 
	 * @see http://jsrocks.org/cn/2015/09/javascript-iterables-and-iterators/
	 *      http://es6.ruanyifeng.com/#docs/symbol
	 * 
	 * <code>

	// 推薦用法
	var get_next_between = html.find_between('>', '<'), text;

	while ((text = get_next_between()) !== undefined) {
		text;
	}

	</code>
	 */

	// 採用String.prototype.indexOf()以增進速度，超越RegExp.prototype.exec()。
	// @see /_test suite/test.js
	function find_between(head, foot, index) {
		// start index
		index |= 0;
		// assert: !!head && !!foot
		// && typeof head === 'string' && typeof foot === 'string'
		var text = this, head_length = head.length, foot_length = foot.length;

		function get_next_between() {
			if (index !== NOT_FOUND
					&& (index = text.indexOf(head, index)) !== NOT_FOUND) {
				var foot_index = text.indexOf(foot, index += head_length);
				if (foot_index !== NOT_FOUND) {
					var token = text.slice(index, foot_index);
					// +foot_length: search next starts from end of foot
					index = foot_index + foot_length;
					return token;
				}
				// 接下來皆無foot，則即使再存有head亦無效。
				index = NOT_FOUND;
			}
			// return undefined;
		}

		return get_next_between;
	}

	// return {Array}all matched
	function all_between(head, foot, index) {
		// start index
		index |= 0;
		// assert: !!head && !!foot
		// && typeof head === 'string' && typeof foot === 'string'
		var matched = [], head_length = head.length, foot_length = foot.length;

		while (index !== NOT_FOUND
				&& (index = this.indexOf(head, index)) !== NOT_FOUND) {
			var foot_index = this.indexOf(foot, index += head_length);
			if (foot_index === NOT_FOUND) {
				// 接下來皆無foot，則即使再存有head亦無效。
				break;
			}
			matched.push(this.slice(index, foot_index));
			// +foot_length: search next starts from end of foot
			index = foot_index + foot_length;
		}

		return matched;
	}

	// callback(token, index, foot_index);
	// 沒有輸入foot的話，則會把head拿來當作foot。
	// TODO: {RegExp}head, foot
	function each_between(head, foot, callback, thisArg, index) {
		if (Array.isArray(head) && typeof foot === 'function') {
			// shift arguments.
			index = thisArg;
			thisArg = callback;
			callback = foot;
			// for head: [head, foot]
			if (Array.isArray(head) && head.length === 2) {
				foot = head[1];
				head = head[0];
			} else {
				if (typeof head === 'string') {
					// 每個head切一段?
					library_namespace
							.error('If you needs cut string into small pieces, please using string.split().slice(1).forEach() !');
				}
				throw new TypeError('Invalid head type');
			}
		}

		// this.all_between(head, foot, index).forEach(callback, thisArg);

		// start index
		index |= 0;
		// assert: !!head && !!foot
		// && typeof head === 'string' && typeof foot === 'string'
		var head_length = head ? head.length : 0, foot_length = foot ? foot.length
				: 0, foot_index;

		if (!thisArg) {
			thisArg = this;
		}

		while (foot || !(foot_index > 0) ? index !== NOT_FOUND
		// allow null header
		&& (!head || (index = this.indexOf(head, index)) !== NOT_FOUND)
				: (index = foot_index) < this.length) {
			foot_index = this.indexOf(foot || head, index += head_length);
			if (foot_index === NOT_FOUND) {
				// 接下來皆無(foot||head)，則即使再存有head亦無效。
				if (foot) {
					break;
				}
				foot_index = this.length;
			}
			callback.call(thisArg, this.slice(index, foot_index), index,
					foot_index);
			// +foot_length: search next starts from end of foot
			index = foot_index + foot_length;
		}
	}

	// =====================================================================================================================

	function set_bind(handler, need_meny_arg) {
		if (typeof need_meny_arg !== 'boolean')
			need_meny_arg = handler.length > 1;

		return need_meny_arg ? function(args) {
			if (arguments.length < 2)
				return handler(this, args);

			// Array.from()
			args = Array.prototype.slice.call(arguments);
			args.unshift(this);
			return handler.apply(handler, args);
		} : function(args) {
			return handler(this, args);
		};
	}

	function set_bind_valueOf(handler, need_meny_arg) {
		var ReturnIfAbrupt = function(v) {
			// 尚有未竟之處。
			switch (library_namespace.is_type(v)) {
			case 'Boolean':
			case 'Number':
			case 'String':
				v = v.valueOf();
			}
			return v;
		};
		return need_meny_arg ? function(args) {
			if (arguments.length < 2)
				return handler(ReturnIfAbrupt(this), args);

			// Array.from()
			args = Array.prototype.slice.call(arguments);
			args.unshift(ReturnIfAbrupt(this));
			return handler.apply(handler, args);
		} : function(args) {
			return handler(ReturnIfAbrupt(this), args);
		};
	}

	// ReturnIfAbrupt
	var need_valueOf = false;
	String.prototype.test_valueOf = (function() {
		return function() {
			if (typeof this !== 'string')
				if (this && typeof this.valueOf() === 'string')
					need_valueOf = true;
				else
					library_namespace.error('set_bind: 無法判別是否該使用 .valueOf()！');
		};
	})();
	'.'.test_valueOf();
	try {
		delete String.prototype.test_valueOf;
	} catch (e) {
		String.prototype.test_valueOf = undefined;
	}

	_.set_bind = need_valueOf ? set_bind_valueOf : set_bind;

	/**
	 * 
	 * @param {Array}array
	 * @param {Function}[comparator]
	 * @returns
	 */
	function unique_and_sort_Array(array, comparator) {
		if (comparator) {
			array.sort(comparator);
		} else {
			array.sort();
		}

		var i = 1, j = -1;
		for (; i < array.length; i++)
			if (array[i] === array[i - 1]) {
				if (j < 0)
					j = i;
			} else if (j >= 0)
				array.splice(j, i - j), i = j, j = -1;

		if (j >= 0)
			array.splice(j, i - j);
		return array;
	}

	// -------------------------------------------
	var type_index = {
		string : 0,
		number : 1,
		boolean : 2,
		'undefined' : 3
	};
	function deprecated_unique_Array(sorted) {
		var array = [];

		if (sorted) {
			var last;
			this.forEach(function(element) {
				if (last !== element)
					array.push(element);
				last = element;
			});

		} else {
			// 以 hash 純量 index 加速判別是否重複。
			var hash = Object.create(null);
			this.forEach(function(element) {
				var type = typeof element;
				// 能確保順序不變。
				if (type in type_index) {
					// TODO: -0
					if (!(element in hash)
							|| !(type_index[type] in hash[element])) {
						array.push(element);
						(hash[element] = [])[type_index[type]] = null;
					}
				} else if (array.indexOf(element) === NOT_FOUND)
					array.push(element);
			});
		}

		return array;
	}
	// -------------------------------------------

	// @see cardinal_1()
	function unique_sorted_Array(get_key) {
		var latest_key, configured;
		var unique_array = this.filter(function(element) {
			var key = get_key ? get_key(element) : element;
			var is_different = configured ? !Object.is(latest_key, key)
					: (configured = true);
			latest_key = key;
			return is_different;
		});

		return unique_array;
	}

	/**
	 * 取交集 array_1 ∩ array_2
	 * 
	 * @param {Array}array_1
	 *            array 1
	 * @param {Array}array_2
	 *            array 2
	 * @param {Function}[key_of_item]
	 *            Function to get key of item.
	 * @param {Boolean}[sorted]
	 *            true: array_1, array_2 are sorted.
	 * 
	 * @returns {Array}intersection of array_1 and array_2
	 */
	function Array_intersection(array_1, array_2, key_of_item, sorted) {
		if (key_of_item === true) {
			sorted = true;
			key_of_item = null;
		}
		if (!sorted) {
			var sort_function = typeof key_of_item === 'function' ? function(
					_1, _2) {
				return general_ascending(key_of_item(_1), key_of_item(_2));
			} : general_ascending;
			array_1 = array_1.clone().sort(sort_function);
			array_2 = array_2.clone().sort(sort_function);
		}
		// console.log([array_1, array_2]);

		var index_of_array_1 = 0, index_of_array_2 = 0,
		// Object.create(array_1)
		result = [];
		for (; index_of_array_1 < array_1.length
				&& index_of_array_2 < array_2.length; index_of_array_1++) {
			var item = array_1[index_of_array_1];
			if (key_of_item)
				item = key_of_item(item);

			do {
				var item_2 = array_2[index_of_array_2];
				if (key_of_item)
					item_2 = key_of_item(item_2);
				if (item_2 < item) {
					index_of_array_2++;
					continue;
				}
				if (item_2 === item) {
					// 相同元素最多取 array_1, array_2 之最小個數。
					index_of_array_2++;
					result.push(item);
				}
				break;
			} while (index_of_array_2 < array_2.length);
		}
		return result;
	}

	var has_native_Map = !Map[library_namespace.env.not_native_keyword];
	// 警告: 相同的 key 只會留下一個 item！
	function Array_intersection_Map(array_1, array_2, key_of_item, sorted) {
		if (key_of_item === true) {
			sorted = true;
			key_of_item = null;
		}
		if (sorted)
			return Array_intersection(array_1, array_2, key_of_item, sorted);

		// @see function unique_Array()
		var map = new Map;
		function set_item(item) {
			var key = key_of_item ? key_of_item(item) : item;
			if (!map['has'](key))
				map['set'](key, /* item */null);
		}
		array_1.forEach(set_item);
		var result = array_2.filter(function(item) {
			var key = key_of_item ? key_of_item(item) : item;
			return map['has'](key);
		});
		return result;
	}

	/**
	 * Count occurrence of $search in string.<br />
	 * 計算 string 中出現 search 之次數。<br />
	 * 
	 * 用 s/// 亦可 @ perl
	 * 
	 * @param {String}string
	 *            在 string 中搜尋。
	 * @param {String|RegExp}search
	 *            搜尋對象。
	 * @param {Integer}[position]
	 *            開始搜尋的位置(start index)。
	 * 
	 * @returns {Integer} string 中出現 search 之次數。
	 * 
	 * @see http://jsperf.com/count-string-occurrence-in-string,
	 *      http://jsperf.com/count-the-number-of-occurances-in-string
	 *      http://stackoverflow.com/questions/881085/count-the-number-of-occurences-of-a-character-in-a-string-in-javascript
	 * 
	 * @since 2013/2/13 11:12:38 重構
	 * @since 2014/8/11 12:54:34 重構
	 */
	function count_occurrence(string, search, position) {
		// 正規化 position 成 index (0, 1, ..)。
		// 注意:過大的 position 在 |0 時會變成負數!
		if (isNaN(position) || (position |= 0) < 0)
			position = 0;

		if (position > 0)
			string = string.slice(position);

		return string.split(search).length - 1;

		// 以下放棄。

		if (library_namespace.is_RegExp(search))
			return (string = (position > 0 ? string.slice(position) : string)
					.match(search)) ? string.length : 0;

		// 正規化 search。
		if (!search || !(search = String(search)))
			return 0;

		// 使用 String.prototype.indexOf (searchString, position)
		var count = 0, length = search.length;

		while ((position = string.indexOf(search, position)) !== NOT_FOUND)
			count++, position += length;

		return count;
	}

	function determine_line_separator(text) {
		var matched, PATTERN = /\r?\n|\r/g, rn = 0, r = 0, n = 0;
		while (matched = PATTERN.exec(text)) {
			if (matched[0] === '\r\n')
				rn++;
			else if (matched[0] === '\n')
				n++;
			else
				r++;
		}

		if (rn > n && rn > r) {
			return '\r\n';
		}
		if (n > r && n > rn) {
			return '\n';
		}
		if (r > n && r > rn) {
			return '\r';
		}

		return library_namespace.env.line_separator;
	}

	_.determine_line_separator = determine_line_separator;

	/**
	 * 取至小數 digits 位， 肇因： JScript即使在做加減運算時，有時還是會出現 3*1.6=4.800000000000001,
	 * 2.4/3=0.7999999999999999 等數值。此函數可取至 1.4 與 0.1。 c.f., round()
	 * 
	 * @see Number.prototype.toLocaleString()
	 *      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
	 * 
	 * @param {Number}[decimals]
	 *            1,2,...: number of decimal places shown
	 * @param {Number}[max]
	 *            maximum decimals. max===0:round() else floor()
	 * 
	 * @return {Number}取至小數 digits 位後之數字。
	 * 
	 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=5856
	 *      IEEE754の丸め演算は最も報告されるES3「バグ」である。 http://www.jibbering.com/faq/#FAQ4_6
	 *      http://en.wikipedia.org/wiki/Rounding
	 * 
	 * @example <code>
	 * var d=new Date,v=0.09999998,i=0,a;
	 * for(;i<100000;i++)a=v.to_fixed(2);
	 * alert(v+'\n→'+a+'\ntime:'+format_date(new Date-d));
	 * </code>
	 * 
	 * @_memberOf _module_
	 */
	function slow_to_fixed(decimals, max) {
		var value = this.valueOf(), i, negative;

		if (isNaN(value))
			return value;

		if (isNaN(decimals) || (decimals = Math.floor(decimals)) < 0) {
			// TODO: using Number.EPSILON

			// 內定：10位
			decimals = 10;
		} else if (decimals > 20)
			// 16: Math.ceil(Math.abs(Math.log10(Number.EPSILON)))
			decimals = 16;

		if (!max && Number.prototype.toFixed) {
			// 去掉末尾的0。必須預防 `(49.5).to_fixed(0)`。
			return parseFloat(value.toFixed(decimals).replace(/\.0+$/, ''));
		}

		if (value < 0)
			// 負數
			negative = true, value = -value;

		value = value.toString(10);
		i = value.indexOf('e');
		if (i !== NOT_FOUND) {
			// e-\d: 數字太小.
			return value.charAt(i + 1) === '-' ? 0 : value;
		}

		library_namespace.debug(value, 2);
		// TODO: using +.5 的方法
		// http://clip.artchiu.org/2009/06/26/%E4%BB%A5%E6%95%B8%E5%AD%B8%E7%9A%84%E5%8E%9F%E7%90%86%E8%99%95%E7%90%86%E3%80%8C%E5%9B%9B%E6%8D%A8%E4%BA%94%E5%85%A5%E3%80%8D/
		i = value.indexOf('.');
		if (i !== NOT_FOUND && i + 1 + decimals < value.length) {
			if (max) {
				value = '00000000000000000000' + Math.round(
				//
				value.slice(0, i++) + value.substr(i, decimals)
				//
				+ '.' + value.charAt(i + decimals)).toString(10);
				if (value != 0)
					library_namespace.debug(value + ',' + value.length + ','
							+ decimals + ','
							+ value.substr(0, value.length - decimals) + ','
							+ value.substr(max), 2);
				max = value.length - decimals;
				value = value.slice(0, max) + '.' + value.substr(max);
			} else
				value = value.slice(0, i + 1 + decimals);
		}

		return value ? parseFloat((negative ? '-' : '') + value) : 0;
	}

	// (15*1.33).to_fixed()===19.95

	// old:very slow
	function deprecated_to_fixed(d, m) {
		var v = this.valueOf(), i;
		if (isNaN(v))
			return v;
		if (isNaN(d) || d < 0)
			d = 8; // 內定：8位
		if (!m) {
			v = Math.round(Math.pow(10, d) * v);
			v = v < 0 ? '-' + '0'.repeat(d) + (-v) : '0'.repeat(d) + v;
			v = v.slice(0, i = v.length - d) + '.' + v.substr(i);
		} else if (i = (v = '' + v).indexOf('.') + 1)
			v = v.slice(0, i + (d ? d : d - 1));
		return parseFloat(v || 0);
	}

	if (false) {
		var addDenominationSet = {
			a : ',,,,'.split(',')
		},
		// 增添單位
		addDenomination = function addDenomination(a, b) {
			TODO;
		};
	}

	/**
	 * 取至小數 digits 位，<br />
	 * 肇因： JScript即使在做加減運算時，有時還是會出現 3*1.6=4.800000000000001,<br />
	 * 2.4/3=0.7999999999999999 等數值。此函數可取至 1.4 與 0.1，避免 <a
	 * href="http://en.wikipedia.org/wiki/Round-off_error" accessdate="2012/9/19
	 * 22:21" title="Round-off error">round-off error</a>。<br />
	 * c.f., Math.round()
	 * 
	 * @param {Number}[decimals]
	 *            1,2,..: number of decimal places shown.
	 * @return 取至小數 digits 位後之數字。
	 * 
	 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=5856
	 *      IEEE754の丸め演算は最も報告されるES3「バグ」である。 http://www.jibbering.com/faq/#FAQ4_6
	 *      http://en.wikipedia.org/wiki/Rounding
	 * 
	 * @example <code>
	var d = new Date, v = 0.09999998, i = 0, a;
	for (; i < 100000; i++)
		a = v.to_fixed(2);
	alert(v + '\n→' + a + '\ntime:' + format_date(new Date - d));
	 * </code>
	 * 
	 * @_memberOf _module_
	 */
	function native_to_fixed(decimals) {
		// (21.1*2006).toFixed(11) === "42326.60000000001"
		// (21.1*200006).toFixed(9)==="4220126.600000001"
		// 256738996346789.1.toFixed(10)==="256738996346789.0937500000"
		return decimals <= 0 ? Math.round(this) : parseFloat(this
				.toFixed(decimals || 12));
	}

	var to_fixed = slow_to_fixed;
	_.to_fixed = slow_to_fixed;

	/**
	 * non-negative modulo, positive modulo. 保證 modulo 結果 >=0。 轉成最接近 0 之正 index。
	 * 
	 * @param {Number}dividend
	 *            被除數。
	 * @param {Number}divisor
	 *            除數。
	 * 
	 * @returns {Number}remainder 餘數
	 */
	function non_negative_modulo(dividend, divisor) {
		if (false)
			return ((dividend % divisor) + divisor) % divisor;

		if ((dividend %= divisor) < 0)
			dividend += divisor;
		return dividend;
	}

	/**
	 * <code>
	var sourceF=WScript.ScriptName,targetF='test.js';simpleWrite('tmp.js',alert+'\n'+simpleRead+'\n'+simpleWrite+'\nvar t="",ForReading=1,ForWriting=2,ForAppending=8\n,TristateUseDefault=-2,TristateTrue=-1,TristateFalse=0\n,WshShell=WScript.CreateObject("WScript.Shell"),fso=WScript.CreateObject("Scripting.FileSystemObject");\nt='+'data.native'(simpleRead(sourceF),80)+';\nsimpleWrite("'+targetF+'",t);//eval(t);\nalert(simpleRead("'+sourceF+'")==simpleRead("'+targetF+'")?"The same (test dQuote OK!)":"Different!");');//WshShell.Run('"'+getFolder(WScript.ScriptFullName)+targetF+'"');

	//	determine quotation mark:輸入字串，傳回已加'或"之字串。
	dQuote.qc=function(c,C){
	return c<32?'\\'+c:C;
	};

	TODO:
	use JSON.stringify()

	</code>
	 * 
	 * @see JSON.stringify()
	 */
	// string,分割長度(會採用'~'+"~"的方式),separator(去除末尾用)
	function dQuote(s, len, sp) {
		var q;
		s = String(s);
		if (sp)
			// 去除末尾之sp
			s = s.replace(new RegExp('[' + sp + ']+$'), '');
		if (isNaN(len) || len < 0)
			len = 0;
		if (len) {
			var t = '';
			for (; s;)
				t += '+' + dQuote(s.slice(0, len))
				// '\n':line_separator
				+ '\n', s = s.substr(len);
			return t.substr(1);
		}

		// test用
		if (false && len) {
			var t = '';
			for (; s;)
				t += 't+=' + dQuote(s.slice(0, len)) + '\n', s = s.substr(len);
			return t.substr(3);
		}

		s = s.replace(/\\/g, '\\\\').replace(/\r/g, '\\r')
		//
		.replace(/\n/g, '\\n')
		// \b,\t,\f

		// 轉換控制字符
		.replace(/([\0-\37\x7f\xff])/g, function($0, $1) {
			var c = $1.charCodeAt(0);
			return c < 8 * 8 ? '\\' + c.toString(8) : '\\x'
			//
			+ (c < 0x10 ? '0' : '') + c.toString(0x10);
		})
		// .replace(/([\u00000100-\uffffffff])/g, function($0, $1) {})
		;
		if (false) {
			q = s.length;
			while (s.charAt(--q) == sp)
				;
			s = s.slice(0, q + 1);
		}
		if (s.indexOf(q = "'") !== NOT_FOUND)
			q = '"';
		if (s.indexOf(q) !== NOT_FOUND) {
			library_namespace.debug(
			//
			"Can't determine quotation mark, the resource may cause error.\n"
					+ s);
			s = s.replace(new RegExp(q = "'", 'g'), "\\'");
		}
		return q + s + q;
	}

	_.dQuote = dQuote;

	// ----------------------------------------------------
	// 可以處理 circular 的 JSON.stringify()，以及可以復原的 JSON.parse()。

	// 盡量找不會用到，又不包含特殊字元的字串作為識別碼。
	var default_KEY_reference = 'REF|';

	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
	// https://github.com/WebReflection/circular-json/blob/master/src/circular-json.js

	// https://stackoverflow.com/questions/11616630/json-stringify-avoid-typeerror-converting-circular-structure-to-json
	function create_reference_map(object, recover_value, KEY_reference) {
		if (KEY_reference === undefined) {
			KEY_reference = default_KEY_reference;
		}

		// e.g., reference_map[{k:0}] = 'REF|0'
		// reference_list = [ {k:0}, ... ]
		var reference_map = new Map, reference_hash = Object.create(null), index = 0;

		return JSON.stringify(object, function(key, value) {
			// console.log([ this, key, value ]);

			// only objects may circular
			if (typeof value === 'object') {
				// return a reference to the value if it had beed processed
				if (reference_map.has(value))
					return reference_map.get(value);

				// record the value

				// find a key that is not in reference_hash or we will be
				// confused if there are duplicate keys
				while ((key = KEY_reference + index++) in reference_hash) {
					if (recover_value) {
						throw new Error('create_reference_map: Invalid index '
								+ key + '. It should not happen.');
					}
				}
				// assert: typeof key!=='object', or will be traversed by
				// JSON.stringify()

				reference_hash[key] = value;
				reference_map.set(value, key);

			} else if (typeof value !== 'object' && recover_value
					&& (value in reference_hash)) {
				// recover value
				this[key] = reference_hash[value];

			} else if (typeof value === 'string' && !recover_value
					&& value.startsWith(KEY_reference)) {
				throw new Error('create_reference_map: '
				//
				+ 'You should specify another KEY_reference instead of '
						+ JSON.stringify(KEY_reference) + '. Confused value: '
						+ value);
			}

			return value;
		});
	}

	// KEY_reference: Any value of object wont starts with KEY_reference
	// 若是有任何一個value包含了 default_KEY_reference + 數字的格式，則需要另外指定 KEY_reference。
	function stringify_circular(object, KEY_reference) {
		return create_reference_map(object, false, KEY_reference);
	}

	function parse_circular(json_string, KEY_reference) {
		var parsed = JSON.parse(json_string);
		// stringify again, using the same algorithm as JSON.stringify() in
		// JSON.stringify_circular().
		create_reference_map(parsed, true, KEY_reference);

		return parsed;
	}

	// old JScript engine do not have JSON
	if (typeof JSON === 'object' && typeof JSON.parse === 'function') {
		set_method(JSON, {
			stringify_circular : stringify_circular,
			parse_circular : parse_circular
		});
	}

	// ----------------------------------------------------

	_// JSDT:_module_
	.
	/**
	 * check input string send to SQL server
	 * 
	 * @param {String}
	 *            string input string
	 * @return {String} 轉換過的 string
	 * @since 2006/10/27 16:36
	 * @see from lib/perl/BaseF.pm (or program/database/BaseF.pm)
	 * @_memberOf _module_
	 */
	checkSQLInput = function(string) {
		if (!string)
			return '';

		// 限制長度 maximum input length
		if (maxInput && string.length > maxInput)
			string = string.slice(0, maxInput);

		return string
		// for \uxxxx
.replace(/\\u([\da-f]{4})/g, function($0, $1) {
			return String.fromCharCode($1);
		}).replace(/\\/g, '\\\\')

		// .replace(/[\x00-\x31]/g,'')
		.replace(/\x00/g, '\\0')

		// .replace(/\x09/g,'\\t')
		// .replace(/\x1a/g,'\\Z')

		// .replace(/\r\n/g,' ')
		.replace(/\r/g, '\\r').replace(/\n/g, '\\n')

		// .replace(/"/g,'\\"')
		.replace(/'/g, "''");
	};

	_// JSDT:_module_
	.
	/**
	 * 轉換字串成數值，包括分數等。分數亦將轉為分數。
	 * 
	 * @param {String}
	 *            number 欲轉換之值。
	 * @return
	 * @_memberOf _module_
	 */
	parse_number = function(number) {
		var m = typeof number;
		if (m === 'number')
			return number;
		if (!number || m !== 'string')
			return NaN;

		number = number.replace(/(\d),(\d)/g, '$1$2');
		if (m = number.match(/(-?[\d.]+)\s+([\d.]+)\/([\d.]+)/)) {
			var p = parseFloat(m[1]), q = parseFloat(m[2]) / parseFloat(m[3]);
			return p + (m[1].charAt(0) === '-' ? -q : q);
		}
		if (m = number.match(/(-?[\d.]+)\/([\d.]+)/))
			// new quotient(m[1],m[2])
			return parseFloat(m[1]) / parseFloat(m[2]);

		if (false) {
			try {
				return isNaN(m = parseFloat(number)) ?
				// TODO: security hole
				eval(number) : m;
			} catch (e) {
				return m;
			}
		}
	};

	/**
	 * filter object. .map() of {Object}<br />
	 * for Object.filter()
	 * 
	 * @param {Object}object
	 *            object to filter
	 * @param {Function}filter
	 *            callback/receiver to filter the value. <br />
	 *            filter(value, key, object) is true: will be preserved.
	 * 
	 * @returns filtered object
	 */
	function Object_filter(object, filter) {
		if (typeof filter !== 'function' || typeof object !== 'object')
			return object;

		var key, delete_keys = [];
		for (key in object) {
			if (!filter(object[key], key, object))
				// 在這邊 delete object[key] 怕會因執行環境之不同實作方法影響到 text 的結構。
				delete_keys.push(key);
		}

		if (delete_keys.length > 0)
			delete_keys.forEach(function(key) {
				delete object[key];
			});
	}

	var has_spread_operator, clone_Object;
	try {
		has_spread_operator = eval('clone_Object=function(object){return {...object};};');
	} catch (e) {
		// TODO: handle exception
	}

	/**
	 * clone object.<br />
	 * for Object.clone()
	 * 
	 * @param {Object}object
	 *            object to clone
	 * @param {Boolean}deep
	 *            deep clone / with trivial
	 * 
	 * @returns {Object}cloned object
	 * 
	 * @see clone() @ CeL.data
	 * @see https://www.bram.us/2018/01/10/javascript-removing-a-property-from-an-object-immutably-by-destructuring-it/
	 * @see `return {...object}` :
	 *      https://juejin.im/post/5b2a186cf265da596d04a648
	 */
	var Object_clone = function clone(object, deep, copy_to) {
		if (!object || typeof object !== 'object') {
			// assert: object is 純量。
			return object;
		}

		// for read-only??
		// return Object.create(object);

		if (deep) {
			// @see
			// http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-an-object
			return JSON.parse(JSON.stringify(object));
		}

		if (!copy_to) {
			if (Array.isArray(object)) {
				// for {Array}
				return object.clone();
			}

			if (clone_Object) {
				return clone_Object(object);
			}
		}

		// shallow clone Object.
		return Object.assign(copy_to || Object.create(
		// copy prototype
		Object.getPrototypeOf(object)), object);
	};

	/**
	 * Test if no property in the object.<br />
	 * for Object.is_empty(), Object.isEmpty()
	 * 
	 * for ArrayLike, use .length instead. This method includes non-numeric
	 * property.
	 * 
	 * @param {Object}object
	 *            object to test
	 * 
	 * @returns {Boolean}the object is empty.
	 * 
	 * @see CeL.is_empty_object()
	 * @see http://stackoverflow.com/questions/3426979/javascript-checking-if-an-object-has-no-properties-or-if-a-map-associative-arra
	 */
	function Object_is_empty(object) {
		if (object !== null)
			for ( var key in object) {
				if (!Object.hasOwn || Object.hasOwn(object, key)) {
					return false;
				}
			}
		return true;
	}

	// Object.clean() 從 object 中移除所有項目。
	// 通常用於釋放記憶體。 Usually used to release memory.
	// cf. array.truncate()
	// {Boolean|Natural}deepth=1: 清理到 object 底下底幾層。
	function Object_clean(object, deepth) {
		if (!object || typeof object !== 'object')
			return;

		if (deepth > 0 && deepth !== true)
			deepth--;

		Object.keys(object).forEach(function(key) {
			// 當有引用重要物件時，不應該採用 deep。
			if (deepth)
				Object_clean(object[key], deepth);
			delete object[key];
		});
	}

	// Object.reverse_key_value({a:b}) → {b:a}
	function Object_reverse_key_value(object) {
		var new_object = Object.create(null);
		if (!object)
			return new_object;

		for ( var key in object) {
			if (!Object.hasOwn || Object.hasOwn(object, key)) {
				new_object[object[key]] = key;
			}
		}
		return new_object;
	}

	/**
	 * Count properties of the object.<br />
	 * for Object.size()
	 * 
	 * for ArrayLike, use .length instead. This method will count non-numeric
	 * properties.
	 * 
	 * @param {Object}object
	 *            object to count properties
	 * 
	 * @returns {Boolean}properties count
	 * 
	 * @see http://stackoverflow.com/questions/5223/length-of-a-javascript-object-that-is-associative-array
	 */
	function Object_size(object) {
		if (object === null)
			return 0;
		var count = 0;
		for ( var key in object) {
			if (!Object.hasOwn || Object.hasOwn(object, key)) {
				count++;
			}
		}
		return count;
	}

	function the_same_content(value_1, value_2) {
		if (value_1 === value_2)
			return true;
		// @see Object.is()
		// check NaN. May use Number.isNaN() as well.
		if (value_1 !== value_1 && value_2 !== value_2)
			return true;
		if (typeof value_1 !== 'object' || typeof value_2 !== 'object')
			return false;

		// TODO: adapt for Map, Set, ...

		var keys_1 = Object.keys(value_1), keys_2 = Object.keys(value_2);
		if (keys_1.length !== keys_2.length)
			return false;

		for (var index = 0; index < keys_1.length; index++) {
			var key = keys_1[index];
			if (!(key in value_2)
					|| !the_same_content(value_1[key], value_2[key]))
				return false;
		}

		return true;
	}

	set_method(Object, {
		filter : Object_filter,
		clone : Object_clone,
		is_empty : Object_is_empty,
		the_same_content : the_same_content,
		clean : Object_clean,
		reverse_key_value : Object_reverse_key_value,
		size : Object_size
	});

	// 非 deep, 淺層/表面 clone/copy: using Array.from().
	var Array_clone = Array.prototype.slice;
	Array_clone = Array_clone.call.bind(Array_clone);
	(function() {
		var a = [ 2, 3 ], b = Array_clone(a);
		if (b.join(',') !== '2,3')
			Array_clone = function clone() {
				// Array.prototype.slice.call(array);
				// library_namespace.get_tag_list():
				// Array.prototype.slice.call(document.getElementsByTagName(tagName));
				return this.slice(0);
			};
	})();
	// or use Array.from(): https://kknews.cc/zh-tw/code/x625ppg.html
	// Array.prototype.clone = function() { return Array.from(this); };

	// 將 element_toPush 加入 array_pushTo 並篩選重複的（本來已經加入的並不會變更）
	// array_reverse[value of element_toPush]=index of element_toPush
	function pushUnique(array_pushTo, element_toPush, array_reverse) {
		if (!array_pushTo || !element_toPush)
			return array_pushTo;
		var i;
		if (!array_reverse)
			for (array_reverse = new Array, i = 0; i < array_pushTo; i++)
				array_reverse[array_pushTo[i]] = i;

		if (typeof element_toPush != 'object')
			i = element_toPush, element_toPush = new Array, element_toPush
					.push(i);

		var l;
		for (i in element_toPush)
			if (!array_reverse[element_toPush])
				// array_pushTo.push(element_toPush),array_reverse[element_toPush]=array_pushTo.length;
				array_reverse[array_pushTo[l = array_pushTo.length] = element_toPush[i]] = l;

		return array_pushTo;
	}

	/**
	 * append/merge to original Array.<br />
	 * Array.prototype.concat does not change the existing arrays, it only
	 * returns a copy of the joined arrays.
	 * 
	 * @param {Array}array
	 *            添加至此 Array list.
	 * @param {Array}list
	 *            欲添加的 Array list. TODO: 若非Array，則會當作單一元素 .push()。
	 * @param {Integer}index
	 *            從 list[index] 開始 append。
	 * 
	 * @returns this
	 */
	function append_to_Array(list, index) {
		if (Array.isArray(list) && (index ? 0 < (index = parseInt(index))
		// TODO: for index < 0
		&& index < list.length : list.length > 0)) {
			if (index > 0) {
				list = Array.prototype.slice.call(list, index);
			}
			// 警告: 當 list 太大時可能產生 RangeError: Maximum call stack size exceeded
			try {
				Array.prototype.push.apply(this, list);
			} catch (e) {
				while (index < list.length)
					Array.prototype.push.call(this, list[index++]);
			}
			// return this.concat(list);
			// return Array.prototype.concat.apply(this, arguments);
		}

		return this;
	}

	// count elements that has something
	if (false) {
		Array.prototype.count_things = function() {
			return this.reduce(function(count, e) {
				return e ? count + 1 : count;
			}, 0);
		};
	}

	// Array.prototype.frequency()
	// values count, 發生率
	function array_frequency(select_max, target) {
		var count = 0;
		if (target !== undefined) {
			this.forEach(library_namespace.is_RegExp(pattern) ? function(item) {
				if (target.test(item))
					count++;
			} : function(item) {
				if (item === target)
					count++;
			});
			return count;
		}

		// new Map()
		var hash = Object.create(null);
		if (!select_max) {
			this.forEach(function(item) {
				if (item in hash) {
					hash[item]++;
				} else
					hash[item] = 1;
			});
			return hash;
		}

		var max_count = 0, max_index;
		this.forEach(function(item, index) {
			var count;
			if (item in hash) {
				count = ++hash[item];
			} else
				count = hash[item] = 1;
			if (select_max === true) {
				if (max_count < count) {
					max_count = count;
					max_index = index;
				}
			} else if (max_count <= count) {
				if (max_count < count
				// select_max = 1: maximum case 也選擇較大的 item
				// select_max = -1: min case 選擇較小的item
				|| !(select_max < 0
				//
				? this[max_index] < (isNaN(item) ? item : +item)
				//
				: this[max_index] > (isNaN(item) ? item : +item)))
					max_index = index;
				max_count = count;
			}
		}, this);
		// hash[this[max_index]] === max_count
		return {
			hash : hash,
			value : this[max_index],
			count : max_count,
			index : max_index
		};
	}

	/**
	 * <code>

	// to inherit from native object:

	function Child() {
		// Parent: native object
		var instance = new Parent;

		// do something need to apply arguments
		;

		// The same as `instance.__proto__ = Child.prototype;`
		Object.setPrototypeOf(instance, Child.prototype);
		// ↑ ** if there is no prototype chain, we should copy the properties manually.

		// do something need to initialize
		;

		return instance;
	}

	// setup inheritance: only works for prototype chain.
	// The same as `Child.prototype = new Parent;`
	Object.setPrototypeOf(Child.prototype, Parent.prototype);

	// setup Child.prototype.attributes
	Child.prototype.property = property;
	Child.prototype.method = function () { };


	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

	</code>
	 */

	// subclass and inherit from Array
	// 注意: 此處的繼承重視原生 Array 之功能，因此 instance instanceof SubArray 於 IE8 等舊版中不成立。
	function new_Array_instance(array_type, no_proto, items) {
		var instance;
		// 除 Array 外，其他 TypedArray，如 Uint32Array 有不同行為。
		// 此處僅處理 constructor。
		if (array_type === Array)
			if (items && 1 < items.length)
				// 將 items copy 至 this instance。
				Array.prototype.push.apply(instance = new Array, items);
			else
				instance = new Array(items && items.length ? items[0] : 0);
		else
			instance = items ? new array_type(items[0]) : new array_type();

		if (no_proto)
			// if there is no prototype chain, we should copy the properties
			// manually.
			// TODO: 此方法極無效率！此外，由於並未使用 .prototype，因此即使採用 delete
			// instance[property]，也無法得到預設值，且不能探測是否為 instance 特有 property。
			set_method(instance, this.prototype, null);
		else {
			Object.setPrototypeOf(instance, this.prototype);
			// TODO: NG:
			// instance.prototype = Object.create(this.prototype);
		}

		return instance;
	}

	function Array_derive(sub_class, array_type) {
		if (!array_type)
			array_type = Array;

		Object.setPrototypeOf(sub_class.prototype, array_type.prototype);
		// TODO: NG:
		// sub_class.prototype = Object.create(array_type.prototype);

		return new_Array_instance.bind(sub_class, array_type, false);
	}

	function Array_derive_no_proto(sub_class, array_type) {
		// e.g., IE8
		return new_Array_instance.bind(sub_class, array_type || Array, true);
	}

	/**
	 * 
	 * @example<code>

	function SubArray() {
		var instance = SubArray.new_instance(arguments);

		// do something need to initialize
		;

		return instance;
	}
	// setup inheritance
	SubArray.new_instance = Array.derive(SubArray);
	// setup SubArray.prototype
	SubArray.prototype.property = property;
	SubArray.prototype.method = function () { };


	// manually test code

	// setup

	// see data.code.compatibility.
	if (!Object.setPrototypeOf && typeof {}.__proto__ === 'object')
		Object.setPrototypeOf = function (object, proto) { object.__proto__ = proto; return object; };

	Array.derive = Object.setPrototypeOf ? Array_derive : Array_derive_no_proto;


	// main test code

	function SubArray() {
		var instance = new_instance(arguments);

		// do something need to initialize
		;

		return instance;
	}
	// setup inheritance
	var new_instance = Array.derive(SubArray);
	// setup SubArray.prototype
	SubArray.prototype.last = function () { return this.at(-1); };

	var a = new SubArray(2, 7, 4), b = [4]; a[6] = 3; a.push(8);
	if (Object.setPrototypeOf && !(a instanceof SubArray) || !(a instanceof Array) || a[1] !== 7 || a.length !== 8 || a.last() !== 8 || b.last) console.error('failed');



	function SubUint32Array() {
		var instance = SubUint32Array.new_instance(arguments);

		// do something need to initialize
		;

		return instance;
	}
	// setup inheritance
	SubUint32Array.new_instance = Array.derive(SubUint32Array, Uint32Array);
	// setup SubUint32Array.prototype
	SubUint32Array.prototype.last = function () { return this.at(-1); };

	var a = new SubUint32Array(8, 7, 4), b = new Uint32Array(4); a[6] = 3; a[7] = 5; a[8] = 4;
	if (Object.setPrototypeOf && !(a instanceof SubUint32Array) || !(a instanceof Uint32Array) || a[8] || a[6] !== 3 || a.length !== 8 || a.last() !== 5 || b.last) console.error('failed');


	</code>
	 */

	set_method(Array, {
		// for data.clone()
		clone : Array_clone,
		derive : Object.setPrototypeOf ? Array_derive : Array_derive_no_proto,
		intersection : has_native_Map ? Array_intersection_Map
				: Array_intersection
	});

	// ------------------------------------
	// comparator, compare_function, sort_function for array.sort()

	// 用於由小至大升序序列排序, ascending, smallest to largest, A to Z。
	// 注意：sort 方法會在原地排序 Array 物件。
	// @see std::less<int>()
	function general_ascending(a, b) {
		// '12/34', '56/78' 可以比大小，但不能相減。
		// 但這對數字有問題: '1212'<'987'
		// 若對一般物件，採用 .sort() 即可。
		return a < b ? -1 : a > b ? 1 : 0;
	}

	function general_descending(a, b) {
		return a < b ? 1 : a > b ? -1 : 0;
	}

	function Number_ascending(a, b) {
		// 升序序列排序: 小→大
		return a - b;

		// '12/34', '56/78' 可以比大小，但不能相減。
		// 但這對數字有問題: '1212'<'987'
		// 若對一般物件，採用 .sort() 即可。
		return a < b ? -1 : a > b ? 1 : 0;

		return _1 - _2;
		return Math.sign(a - b);
	}

	function Number_descending(a, b) {
		// 降序序列排序: 大→小
		return b - a;

		return a < b ? 1 : a > b ? -1 : 0;
	}

	_.general_ascending = general_ascending;
	_.general_descending = general_descending;
	_.ascending = Number_ascending;
	_.descending = Number_descending;

	/**
	 * 以二分搜尋法(binary search)搜尋已排序的 array。<br />
	 * binary search an Array.<br /> ** 注意：使用前須先手動將 array 排序！<br />
	 * TODO: 依資料分布:趨近等差/等比/對數等，以加速搜尋。
	 * 
	 * cf. Array.prototype.search()
	 * 
	 * @param {Array}array
	 *            由小至大已排序的 array。
	 * @param value
	 *            value to search.
	 * @param {Object}[options]
	 *            附加參數/設定特殊功能與選項 options = {<br />
	 *            found : found_callback(index, not_found:
	 *            closed/is_near/未準確相符合，僅為趨近、近似),<br />
	 *            near : not_found_callback(較小的 index, not_found),<br />
	 *            start : start index,<br />
	 *            last : last/end index,<br />
	 *            length : search length.<br />
	 *            <em>last 與 length 二選一。</em><br /> }
	 * 
	 * @returns default: 未設定 options 時，未找到為 NOT_FOUND(-1)，找到為 index。
	 * 
	 * @since 2013/3/3 19:30:2 create.<br />
	 */
	function search_sorted_Array(array, value, options) {
		if (library_namespace.is_RegExp(value)
				&& (!options || !options.comparator)) {
			// 處理搜尋 {RegExp} 的情況: 此時回傳最後一個匹配的 index。欲找首次出現，請用 first_matched()。
			if (value.global) {
				library_namespace
						.error('search_sorted_Array: 當匹配時，不應採用 .global！ '
								+ value);
			}
			if (!options) {
				options = Object.create(null);
			}
			options.comparator = function(v) {
				return value.test(v) ? -1 : 1;
			};
			if (!('near' in options)) {
				options.near = true;
			}
		} else if (!options
		//
		&& (typeof value === 'object' || typeof value === 'function')) {
			options = value;
			value = undefined;
		}

		if (typeof options === 'function') {
			options = {
				comparator : options
			};
		} else if (typeof options === 'boolean' || Array.isArray(options)) {
			options = {
				found : options
			};
		} else if (library_namespace.is_digits(options)) {
			options = {
				start : options
			};
		} else if (!library_namespace.is_Object(options)) {
			options = Object.create(null);
		}

		var callback, comparison, not_found = true,
		//
		comparator = options.comparator
				|| (typeof array[0] === 'number' ? Number_ascending
						: search_sorted_Array.default_comparator),
		//
		start = (options.start | 0) || 0, small = start, index = start,
		//
		big = (options.last | 0)
				|| (options.length ? options.length | 0 + start - 1
						: array.length - 1);

		// main comparare loop
		// http://codereview.stackexchange.com/questions/1480/better-more-efficient-way-of-writing-this-javascript-binary-search
		while (true) {
			if (small > big) {
				if (comparison > 0 && index > start)
					// 修正成較小的 index。
					// 除非是 [2,3].search_sorted(1.5,{found:1})，
					// 否則 assert: big + 1 === start === index
					index--;
				break;

			} else {
				// 首引數應該採用最多資訊者，因此array[]擺在value前。
				comparison = comparator(array[index = (small + big) >> 1],
						value
				// , index
				);

				// 若下一 loop 跳出，則此時之狀態為
				// start = index = big; value 介於 array[index±1]。
				// 或
				// start = index, big; value 介於兩者間。

				if (comparison > 0) {
					// 往前找
					big = index - 1;
					// 若下一 loop 跳出，則此時之狀態為
					// big, start = index
					// value 介於兩者間。

				} else if (comparison < 0) {
					// 往後找
					small = index + 1;
					// 若下一 loop 跳出，則此時之狀態為
					// index = big, small
					// value 介於兩者間。

				} else {
					not_found = false;
					break;
				}
			}
		}

		// 挑一個可用的。
		callback = not_found && options.near || options.found;
		// console.log([ not_found, callback, index ]);

		return Array.isArray(callback)
		// treat options.found as mapper
		? callback[index]
		//
		: typeof callback === 'function' ? callback.call(array, index,
				not_found)
		//
		: not_found && (!callback
		// 當 library_namespace.is_RegExp(value) 時，callback 僅表示匹不匹配。
		|| library_namespace.is_RegExp(value)
		// assert: 此時 index === 0 or array.length-1
		// 這樣會判別並回傳首個匹配的。
		&& (index === 0 && comparator(array[index]) > 0)) ? NOT_FOUND
		// default: return index of value
		: index;
	}

	search_sorted_Array.default_comparator = general_ascending;

	_.search_sorted_Array = search_sorted_Array;

	// return first matched index.
	// assert: array 嚴格依照 mismatched→matched，有個首次出現的切分點。
	function first_matched(array, pattern, get_last_matched) {
		if (!array || !pattern) {
			return NOT_FOUND;
		}
		var first_matched_index = array.length;
		if (first_matched_index === 0) {
			return NOT_FOUND;
		}
		var is_RegExp = library_namespace.is_RegExp(pattern), is_Function = !is_RegExp
				&& library_namespace.is_Function(pattern),
		//
		last_mismatched_index = 0;
		if (is_RegExp && pattern.global) {
			library_namespace.error('first_matched: 當匹配時，不應採用 .global！ '
					+ pattern);
		}

		var matched;
		while (last_mismatched_index < first_matched_index) {
			// binary search
			var index = (last_mismatched_index + first_matched_index) / 2 | 0;
			matched = is_RegExp ? pattern.test(array[index])
					: is_Function ? pattern(array[index]) : array[index]
							.includes(pattern);
			if (false && matched && is_RegExp) {
				library_namespace.log(last_mismatched_index + '-[' + index
						+ ']-' + first_matched_index + '/' + array.length
						+ ': ' + matched + ' ' + pattern);
				console.log(array[index].match(pattern));
			}
			if (get_last_matched ? !matched : matched) {
				first_matched_index = index;
			} else if (last_mismatched_index === index) {
				break;
			} else {
				last_mismatched_index = index;
			}
		}

		if (get_last_matched) {
			if (last_mismatched_index === 0 && !matched) {
				library_namespace.debug('Not found.', 3, 'first_matched');
				return NOT_FOUND;
			}
			library_namespace.debug('return ' + last_mismatched_index, 3,
					'first_matched');
			return last_mismatched_index;
		}

		return first_matched_index === array.length ? NOT_FOUND
				: first_matched_index;
	}

	_.first_matched = first_matched;

	/**
	 * merge / combine string with duplicated characters.<br />
	 * merge 2 array by order, without order change<br />
	 * 警告: 此法僅於無重複元素時有效。
	 * 
	 * @param {Array}sequence_list
	 *            sequence list to merge
	 * 
	 * @returns {Array}merged chain
	 * 
	 * @see find duplicate part of 2 strings<br />
	 *      https://en.wikipedia.org/wiki/String_metric
	 *      https://en.wikipedia.org/wiki/Shortest_common_supersequence_problem
	 *      http://codegolf.stackexchange.com/questions/17127/array-merge-without-duplicates
	 *      https://en.wikipedia.org/wiki/Topological_sorting
	 */
	function merge_unduplicated_sequence(sequence_list) {
		var map = Object.create(null);

		function add_node(element, index) {
			var chain = map[element];
			if (!chain)
				chain = map[element]
				// [ 0: possible backward, 1: possible foreword ]
				= [ Object.create(null), Object.create(null) ];
			if (index > 0)
				// 登記前面的。
				chain[0][this[index - 1]] = true;
			if (index + 1 < this.length)
				// 登記前面的。
				chain[1][this[index + 1]] = true;
			return;
			// 不必記太多，反而稱加操作複雜度。上面的相當於把 'abc' 拆成 'ab', 'bc'
			var i = 0;
			for (; i < index; i++)
				// 登記前面的。
				chain[0][this[i]] = true;
			// i++: skip self
			for (i++; i < this.length; i++)
				// 登記後面的。
				chain[1][this[i]] = true;
		}

		sequence_list.forEach(function(sequence) {
			if (typeof sequence === 'string')
				sequence = sequence.split('');
			if (typeof sequence.forEach === 'function'
			// && Array.isArray(sequence)
			) {
				sequence.forEach(add_node, sequence);
			} else {
				library_namespace.warn(
				//
				'merge_unduplicated_sequence: Invalid sequence: [' + sequence
						+ ']');
			}
		});

		// 此法僅於無重複時有效。
		/**
		 * result chain / sequence.<br />
		 * result = [ start of chain, ends of chain ]
		 * 
		 * @type {Array}
		 */
		var result = [ [], [] ];
		while (true) {
			/** {Array}temporary queue */
			var queue = [ [], [] ],
			/** {Array}elements added */
			added = [];
			for ( var element in map) {
				if (element in added)
					continue;
				// 先考慮添入起首，再考慮結尾。
				if (Object.is_empty(map[element][0])) {
					queue[0].push(element);
					// 登記。
					added.push(element);
					continue;
				}
				if (Object.is_empty(map[element][1])) {
					queue[1].push(element);
					// 登記。
					added.push(element);
					continue;
				}
			}

			if (added.length === 0)
				// nothing can do.
				// e.g., a ring / cycle, 有重複。
				break;

			if (queue[0].length === 1)
				result[0].push(queue[0][0]);
			else if (queue[0].length > 0) {
				// 有多重起頭。
				throw new Error('Invalid starts: ' + queue[0]);
			}
			if (queue[1].length === 1)
				result[1].unshift(queue[1][0]);
			else if (queue[1].length > 0) {
				// 有多重結尾。
				throw new Error('Invalid ends: ' + queue[1]);
			}

			// remove node.
			added.forEach(function(element) {
				var data = map[element];
				for ( var node in data[0])
					delete map[node][1][element];
				for ( var node in data[1])
					delete map[node][0][element];
				delete map[element];
			});
		}

		result = result[0].concat(result[1]);
		return result;
	}

	_.merge_sequence = merge_unduplicated_sequence;

	// ------------------------------------

	// TODO: add `Promise` version

	/**
	 * 依照順序從 index 至 last 執行 for_each。
	 * 
	 * @examples <code>

	function for_item(run_next, index, index, list) {
		// do something
		run_next();
	}
	CeL.run_serial(for_item, last_NO, first_NO, function() { 'done'; });

	function for_item(run_next, item, index, list) {
		// do something
		run_next();
	}
	CeL.run_serial(for_item, list, function() { 'done'; });

	 * </code>
	 * 
	 * @param {Function}for_each
	 *            run for_each(run_next, item, index, list[, get_status]) for
	 *            every elements. Must handle exception yourself.
	 *            {Object}function get_status(): 狀態探測函數。
	 * @param {Integer|Array}last
	 *            last index or {Array}list
	 * @param {Integer|Array}[index]
	 *            start index or [start index, last index].<br />
	 *            default: starts from 0.
	 * @param {Function}[callback]
	 *            Will run after all elements executed
	 * @param {Object}[_this]
	 *            passed to for_each
	 * @param {Boolean}[parallelly]
	 *            run parallelly
	 * 
	 * @see CeL.data.code.thread
	 */
	function run_serial_asynchronous(for_each, last, index, callback, _this,
			parallelly) {
		var list;
		// initialization
		if (Array.isArray(last)) {
			list = last;
			last = list.length;
		}
		if (typeof index === 'function') {
			// shift arguments.
			parallelly = _this;
			_this = callback;
			callback = index;
			index = 0;
		} else if (Array.isArray(index)) {
			last = index[1];
			index = index[0];
		} else {
			// default: starts from 0.
			index |= 0;
		}

		// console.log([ for_each, last, index, callback, _this, parallelly ]);

		// ----------------------------------------------------------
		// main loop for serial
		function run_next() {
			// 預留可變動 list 的空間。
			if (list ? index === list.length : index > last) {
				// done.
				typeof callback === 'function' && callback.call(_this);
				return;
			}

			library_namespace.debug(index + '/' + last, 3,
					'run_serial_asynchronous');
			// 先增加 index，預防 callback 直接 call run_next()。
			var _index = index++;
			var _arguments = [ run_next, list ? list[_index] : _index, _index,
					list ];
			if (_this && _this.run_interval >= 0) {
				library_namespace.log_temporary(index + '/' + last
						+ ' Waiting ' + _this.run_interval + ' ms to run');
				setTimeout(function() {
					for_each.apply(_this, _arguments);
				}, _this.run_interval);
			} else {
				for_each.apply(_this, _arguments);
			}
		}

		if (!parallelly
		// parallelly 在這情況下不會執行 callback。
		|| !(index <= last)) {
			run_next();
			return;
		}

		// ----------------------------------------------------------
		// parallelly

		function get_status() {
			return {
				left : left
			};
		}

		var check_left = function(exit_loop) {
			if (--left === 0 || exit_loop === 'quit') {
				// run once only
				check_left = library_namespace.null_function;
				typeof callback === 'function' && callback.call(_this);
				return;
			}
			library_namespace.debug(left + ' left...', 3,
					'run_parallel_asynchronous');
		};

		if (_this) {
			for_each = for_each.bind(_this);
		}

		var left = 0;
		if (list) {
			if (list.length === 0) {
				setImmediate(check_left, 'quit');
			} else {
				list.forEach(function(item, index) {
					left++;
					setImmediate(for_each, check_left, item, index, list,
							get_status);
				});
			}
			return;
		}

		for (; index <= last; index++, left++) {
			setImmediate(for_each, check_left, index, index, list, get_status);
		}

	}

	_.run_serial = run_serial_asynchronous;

	// 警告: 採用非同步的方法，獲得網址的順序不一定。因此不能採用 .push()，而應採用 [index] 的方法來記錄。
	function run_parallel_asynchronous(for_each, last, index, callback, _this) {
		run_serial_asynchronous(for_each, last, index, callback, _this, true);
	}

	_.run_parallel = run_parallel_asynchronous;

	// ---------------------------------------------------------------------//

	if (false) {
		CeL.edit_distance('from A', 'from B');
	}

	// Levenshtein distance (edit distance)
	// @see LCS()
	// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
	// http://www.codeproject.com/Articles/13525/Fast-memory-efficient-Levenshtein-algorithm
	// http://locutus.io/php/strings/levenshtein/
	// https://github.com/component/levenshtein/blob/master/index.js
	// https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance
	// http://jsperf.com/levenshtein-distance-2
	function Levenshtein_distance(string_1, string_2) {
		string_1 = (string_1 || '').chars(true);
		string_2 = (string_2 || '').chars(true);

		var length_1 = string_1 && string_1.length || 0, length_2 = string_2
				&& string_2.length || 0;
		// degenerate cases
		if (length_1 === 0) {
			return length_2;
		}
		if (length_2 === 0) {
			return length_1;
		}
		if (length_1 === length_2 && string_1 === string_2) {
			return 0;
		}

		// create two work vectors of integer distances
		var vector_1 = new Array(length_2 + 1), i = 0;
		// initialize vector_1 (the previous row of distances)
		// this row is A[0][i]: edit distance for an empty string_1
		// the distance is just the number of characters to delete from string_2
		for (; i <= length_2; i++) {
			vector_1[i] = i;
		}

		for (i = 0; i < length_1; i++) {
			// calculate vector_2 (current row distances) from the previous row
			// vector_1

			// use formula to fill in the rest of the row
			for (var j = 0,
			// first element of vector_2 is A[i+1][0]
			// edit distance is delete (i+1) chars from string_1 to match empty
			// string_2
			last_vector_2 = i + 1, vector_2 = [ last_vector_2 ]; j < length_2; j++) {
				last_vector_2 = Math.min(
				// The cell immediately above + 1
				last_vector_2 + 1,
				// The cell immediately to the left + 1
				vector_1[j + 1] + 1,
				// The cell diagonally above and to the left plus the cost
				vector_1[j] + (/* cost */string_1[i] === string_2[j] ? 0 : 1));
				vector_2.push(last_vector_2);
			}

			// copy vector_2 (current row) to vector_1 (previous row) for next
			// iteration
			vector_1 = vector_2;
		}

		return vector_2[length_2];
	}

	_.edit_distance = Levenshtein_distance;

	// ------------------------------------

	/**
	 * Get LCS length / trace array
	 * 
	 * @param {Array}from
	 * @param {Array}to
	 * 
	 * @returns {Array}
	 * 
	 * @since 2017/4/5 10:0:36
	 * 
	 * @see https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
	 *      https://github.com/GerHobbelt/google-diff-match-patch
	 */
	function LCS_length(from, to, get_trace_array) {
		if (typeof from === 'string')
			from = from.chars(true);
		if (typeof to === 'string')
			to = to.chars(true);
		// assert: Array.isArray(from) && Array.isArray(from)

		// console.trace([ from.length, to.length ]);
		var get_length_only = !get_trace_array,
		//
		from_length = from.length, to_length = to.length,
		// TODO: 當文件有過多行時的處置方式。
		trace_Array = from_length * (get_length_only ? 2 : to_length);
		trace_Array = typeof Uint16Array === 'function' ? new Uint16Array(
				trace_Array) : new Array(trace_Array).fill(0);

		// loop of ↓
		for (var to_index = 0, trace_index = 0, last_trace_index = trace_index
				- from_length; to_index < to_length; to_index++) {
			if (get_length_only) {
				if (to_index % 2 === 0) {
					last_trace_index = 0;
					// assert: 已經 = from_length
					// trace_index = from_length;
				} else {
					// assert: 已經 = from_length
					// last_trace_index = from_length;
					trace_index = 0;
				}
			}
			// loop of →
			for (var to_element = to[to_index], from_index = 0; from_index < from_length; from_index++, trace_index++, last_trace_index++) {
				// @see LCS function
				if (to_element === from[from_index]) {
					// to[to_index] === from[from_index]
					trace_Array[trace_index] =
					// 這條件也保證了 last_trace_index > 0
					from_index > 0 && to_index > 0 ? trace_Array[last_trace_index - 1] + 1
							: 1;
				} else {
					// to[to_index] !== from[from_index]
					trace_Array[trace_index] = from_index > 0 ? trace_Array[trace_index - 1]
							: 0;
					if (last_trace_index >= 0
							&& trace_Array[trace_index] < trace_Array[last_trace_index]) {
						trace_Array[trace_index] = trace_Array[last_trace_index];
					}
				}
			}
		}

		if (get_length_only) {
			return trace_Array[trace_index - 1];
		}

		if (library_namespace.is_debug(3)) {
			library_namespace.debug('to\\f\t' + from.join('\t') + '\n'
					+ '-'.repeat(8 * (from.length + 2)));
			for (var to_index = 0; to_index < to_length; to_index++) {
				library_namespace.debug(to[to_index]
						+ '\t'
						+ trace_Array.slice(to_index * from_length,
								(to_index + 1) * from_length).join('\t'));
			}
			library_namespace.debug('-'.repeat(8 * (from.length + 3)));
		}

		// trace_Array.LCS_length = trace_Array[trace_index - 1];
		return trace_Array;
	}

	_.LCS_length = LCS_length;

	if (false) {
		diff_list = CeL.LCS(old_text, new_text, {
			// line : true,
			diff : true
		});

		diff_list.forEach(function(diff_pair) {
			// added_text === inserted_text
			// const [removed_text, added_text] = diff;
			var removed_text = diff_pair[0], added_text = diff_pair[1];
		});
	}

	if (false) {
		diff_list = CeL.LCS('from A', 'from B', {
			diff : true
		});

		diff_list = CeL.LCS('from A', 'from B', {
			with_diff : true
		});
	}

	/**
	 * Get LCS / diff
	 * 
	 * Longest Common Subsequence 最長公共子序列
	 * 
	 * 警告：在 line_mode，"A \n"→"A\n" 的情況下，"A" 會同時出現在增加與刪除的項目中，此時必須自行檢測排除。
	 * 
	 * @param {Array|String}from
	 *            from text
	 * @param {Array|String}to
	 *            to text
	 * @param {Object|String}options
	 *            附加參數/設定選擇性/特殊功能與選項
	 * 
	 * @returns {Array|String}
	 */
	function LCS(from, to, options) {
		// 前置作業。
		options = library_namespace.setup_options(options);

		var treat_as_String = 'treat_as_String' in options ? options.treat_as_String
				: typeof from === 'string' && typeof to === 'string',
		// options.line : 強制採用行模式，連輸入{String|Array}都會以'\n'結合。
		line_mode = 'line' in options ? options.line : treat_as_String
				&& (from.includes('\n') || to.includes('\n')),
		//
		separator = options.separator || (line_mode ? '\n' : '');
		if (false) {
			library_namespace.debug('separator: ' + JSON.stringify(separator)
					+ (line_mode ? '，採用行模式。' : ''), 3, 'LCS');
		}
		if (typeof from === 'string') {
			from = separator ? from.split(separator) : from.chars(true);
		} else if (!from) {
			from = [];
		}
		if (typeof to === 'string') {
			to = separator ? to.split(separator) : to.chars(true);
		} else if (!to) {
			to = [];
		}
		// assert: Array.isArray(from) && Array.isArray(from)

		var from_length = from.length, from_index = from_length - 1, to_index = to.length - 1,
		// Completed LCS Table
		trace_Array = options.trace_Array || LCS_length(from, to, true),
		// 獨特/獨有的 exclusive 元素列表。
		diff_list = [], from_unique, to_unique,
		// flags
		get_all = !!options.all,
		// get index instead of contents
		get_index = options.index || get_all,
		// get diff as well
		get_diff = !!(options.diff || options.with_diff),
		// get diff ONLY
		diff_only = get_diff && !get_all && !options.with_diff,
		//
		try_to_merge_diff = !!options.try_to_merge_diff;

		// ---------------------------------------
		// 工具函數。

		function unique(list) {
			return list.map(function(result_Array) {
				// assert: .join() 與 .split() 採用的是 result_Array 不包含的字串。
				return result_Array.join('\0');
			}).unique().map(function(result_Array) {
				return result_Array.split('\0');
			});
		}

		function normalize_unique(_unique, _this) {
			if (!Array.isArray(_unique)) {
				// return '';
				// return;
				return treat_as_String ? '' : _unique;
				// return [];
			}

			// _unique = [ from index, to index ]
			if (options.diff_line) {
				return _unique;
			}

			var index = _unique[0];
			if (index === _unique[1]) {
				if (_unique === from_unique) {
					from_unique = [ index, index ];
				} else {
					to_unique = [ index, index ];
				}
				// return _this[index];
			}

			_unique = _this.slice(index, _unique[1] + 1);

			return treat_as_String ? _unique.join(separator) : _unique;
		}

		function generate_diff_pair(from_index, to_index) {
			var diff_pair = [ normalize_unique(from_unique, from),
					normalize_unique(to_unique, to) ];
			if (false && to_unique) {
				diff_pair.push(normalize_unique(to_unique, to));
			}
			// Array.isArray(to_unique) && from_unique===undefined 代表本段落為新增文字。
			// diff_pair.index: 本次差異開始的 index。
			// diff_pair.index = [ from_index, to_index ];
			// _index = [ start_index, end_index ]
			// .indexes
			diff_pair.index = [ from_unique, to_unique ];
			// diff_pair.last_index: 上一個相同元素的 index。
			// 警告: diff_pair.last_index 在 _unique 最初的一個 index 可能不準確!
			diff_pair.last_index = [ from_index, to_index ];

			return diff_pair;
		}

		function add_to_diff_list(from_index, to_index) {
			if (!get_diff || (!from_unique && !to_unique)) {
				// assert: 連續相同元素時
				return;
			}

			if (false) {
				library_namespace.debug(JSON.stringify([ from_index, to_index,
						from_unique, to_unique ]), 3, 'add_to_diff_list');
			}

			var diff_pair = generate_diff_pair(from_index, to_index);

			if (try_to_merge_diff && diff_list.length > 0) {
				var previous_diff_of_start = (to_index === NOT_FOUND ? 0
				// 本次不同內容開始的 index 差異。
				: to_index) - (from_index === NOT_FOUND ? 0 : from_index);
				for (var index = 0; index < diff_list.length
				// && 1: 只檢測文字交換。 e.g., "品質" → "質量"，避免太多差異被合併在一起。
				&& 1; index++) {
					var _diff_pair = diff_list[index];
					// 到 _diff_pair 為止，from 之不同內容最後的 index 差異。
					var last_index_0 = _diff_pair.index[0] ? _diff_pair.index[0][1]
							: _diff_pair.last_index[0];
					// 到 _diff_pair 為止，to 之不同內容最後的 index 差異。
					var last_index_1 = _diff_pair.index[1] ? _diff_pair.index[1][1]
							: _diff_pair.last_index[1];
					// 到 _diff_pair 為止，不同內容最後的 index 差異。
					var following_diff_of_start = last_index_1 - last_index_0;
					if (following_diff_of_start !== previous_diff_of_start) {
						continue;
					}

					diff_list.splice(0, index + 1);
					if (!from_unique) {
						from_unique = [ to_unique[0] ];
						if (from_index === NOT_FOUND)
							from_index = 0;
					}
					from_unique[1] = last_index_0;
					if (!to_unique) {
						to_unique = [ from_unique[0] ];
						if (to_index === NOT_FOUND)
							to_index = 0;
					}
					to_unique[1] = last_index_1;
					diff_pair = generate_diff_pair(from_index, to_index);
					break;
				}
			}

			diff_list.unshift(diff_pair);
			// reset indexes.
			from_unique = to_unique = undefined;
		}

		// backtrack subroutine
		function backtrack(from_index, to_index, all_list) {
			// 因為函數 backtrack() 中呼叫自己，可能出現：
			// RangeError: Maximum call stack size exceeded
			// 因此必須採用非遞迴呼叫(recursive call)版本。
			while (true) {
				if (false) {
					library_namespace.debug(String([ from_index, to_index ]),
							3, 'LCS.backtrack');
					library_namespace.debug(String([ from_unique, to_unique,
							diff_list ]), 6, 'LCS.backtrack');
				}
				if (from_index < 0 || to_index < 0) {
					if (false) {
						library_namespace.debug('→ '
								+ JSON.stringify([ from_index, to_index,
										from_unique, to_unique ]), 3);
					}
					if (from_index === -1 && to_index === -1) {
						// assert: from_index === -1 && to_index === -1
						if (false) {
							library_namespace.debug(
							//
							'LCS starts from the first element of each list, '
									+ JSON.stringify([ from_index, to_index,
											from[0], to[0] ]), 3,
									'LCS.backtrack');
						}
					} else if (to_index === -1) {
						if (false) {
							library_namespace.debug('因為 ' + from_index + ',0→('
									+ from_index + '|' + (from_index - 1)
									+ '),-1 時不會處理到 from_unique，只好補處理。', 3,
									'LCS.backtrack');
						}
						if (Array.isArray(from_unique)) {
							// e.g., CeL.LCS('abc123', 'def123', 'diff')
							from_unique[0] = 0;
						} else {
							// e.g., CeL.LCS('a0', 'b0', 'diff')
							// e.g., CeL.LCS('a0', '0b', 'diff')
							from_unique = [ 0, from_index ];
						}
					} else if (from_index === -1) {
						if (false) {
							library_namespace.debug('因為 0,' + to_index
									+ ',0→-1,(' + to_index + '|'
									+ (to_index - 1)
									+ ') 時不會處理到 to_unique，只好補處理。', 3,
									'LCS.backtrack');
						}
						if (Array.isArray(to_unique)) {
							to_unique[0] = 0;
						} else {
							// e.g., CeL.LCS('a1b2', '1a2b', 'diff')
							to_unique = [ 0, to_index ];
						}
					} else {
						library_namespace
								.warn('LCS.backtrack: Invalid situation!');
					}
					add_to_diff_list(from_index, to_index);
					return;
				}

				if (from[from_index] === to[to_index]) {
					// 此元素為 LCS 之一部分。
					if (false) {
						library_namespace.debug('相同元素 @ '
								+ [ from_index, to_index ] + ': '
								+ from[from_index], 3, 'LCS.backtrack');
					}
					if (!diff_only) {
						// get_index = 1: from_index, 2: to_index
						var common = get_index ? get_index === 2 ? to_index
								: from_index : from[from_index];
						if (get_all) {
							all_list.forEach(function(result_Array) {
								result_Array.unshift(common);
							});
						} else {
							all_list[0].unshift(common);
						}
					}
					add_to_diff_list(from_index, to_index);

					// 常常出現 Maximum call stack size exceeded 錯誤的地方...
					// backtrack(from_index - 1, to_index - 1, all_list);
					// return;

					// 採用 iteration
					from_index--;
					to_index--;
					continue;
				}

				var trace_index;
				if (from_index > 0
				// ↑ 預防 trace_Array[trace_index - 1] 取到範圍外的值。
				&& (trace_index = to_index * from_length + from_index) > 0
				// trace_Array[trace_index]
				// = max( trace_Array[trace_index - 1], [上面一階])
				&& trace_Array[trace_index] === trace_Array[trace_index - 1]) {
					if (false) {
						library_namespace.debug('trace_index: ' + trace_index,
								3, 'LCS.backtrack');
						library_namespace.debug('trace: '
								+ trace_Array[trace_index - 1] + ' → '
								+ trace_Array[trace_index], 3);
					}

					var _all_list;
					if (get_all
					// 如此亦保證 to_index > 0
					&& trace_index >= from_length && trace_Array[trace_index]
					//
					=== trace_Array[trace_index - from_length]) {
						if (false) {
							library_namespace.debug(trace_Array[trace_index]
									+ ': ' + all_list, 3, 'LCS.backtrack');
						}
						_all_list = all_list.map(function(result_Array) {
							return result_Array.clone();
						});
						backtrack(from_index, to_index - 1, _all_list);
					}

					if (false) {
						library_namespace.debug('檢測前一個。 '
								+ [ from_index, to_index ], 3, 'LCS.backtrack');
					}
					if (get_diff) {
						if (Array.isArray(from_unique)) {
							from_unique[0] = from_index;
						} else {
							from_unique = [ from_index, from_index ];
						}
						if (false) {
							library_namespace.debug(
							//
							'from_index: ' + [ from_index,
							//
							JSON.stringify(from_unique) ], 3, 'LCS.backtrack');
						}
					}
					// TODO: 對於比較長的結構，這裡常出現
					// RangeError: Maximum call stack size exceeded
					// workaround: 現在只能以重跑程式、跳過這篇文章繞過。
					backtrack(from_index - 1, to_index, all_list);

					if (get_all) {
						if (false) {
							library_namespace.debug('merge: '
									+ [ all_list, _all_list ], 3);
						}
						all_list = unique(all_list.append(_all_list));
					}
					return;

				} else {
					if (false) {
						library_namespace.debug('檢測上一排。 '
								+ [ from_index, to_index ], 3, 'LCS.backtrack');
					}
					if (get_diff) {
						if (to_unique) {
							to_unique[0] = to_index;
						} else {
							to_unique = [ to_index, to_index ];
						}
						if (false) {
							library_namespace.debug('to_index: '
									+ [ to_index, JSON.stringify(to_unique) ],
									3, 'LCS.backtrack');
						}
					}

					// backtrack(from_index, to_index - 1, all_list);

					// 採用 iteration
					to_index--;
				}
			}
		}

		// ---------------------------------------

		// 取得所有 LCS，而不僅是其中之一。
		var all_list = [ [] ];

		// 主要作業。
		if (isNaN(from_index) || isNaN(to_index)) {
			library_namespace.error('LCS: isNaN(from:' + from_index
					+ ') || isNaN(' + to_index + ')');
			throw new Error('LCS: isNaN(from_index) || isNaN(to_index)');
		}
		try {
			backtrack(from_index, to_index, all_list);
		} catch (e) {
			if (options.no_throw_when_stack_size_is_exceeded) {
				all_list.error = e;
				return all_list;
			}
			throw new RangeError(
					'Maximum call stack size exceeded @ backtrack()');
		}

		// 以下為後續處理。
		if (get_all) {
			// 去掉重複的 LCS。
			all_list = unique(all_list);
		}

		if (treat_as_String || !get_index) {
			all_list = all_list
			// index → 元素
			.map(function(result_Array) {
				if (get_index) {
					result_Array = result_Array.map(function(index) {
						return get_index === 2 ? to[index] : from[index];
					});
				}
				return treat_as_String && !options.index
				// 特別指定 options.index 時，即使輸入{String}亦保持index，不轉換為{String}。
				? result_Array.join(separator) : result_Array;
			});
		}

		if (get_diff && !diff_only) {
			// 應為 treat_as_String
			if (treat_as_String) {
				// 為了能設定 .diff。
				// assert: diff_list 設定在 all_list[0] 上，
				// 且不因前面 unique(all_list) 而改變。
				all_list[0] = new String(all_list[0]);
			}
			all_list[0].diff = diff_list;
		}

		if (!get_all) {
			all_list = diff_only ? diff_list : all_list[0];
		}
		if (options.with_list) {
			if (typeof all_list !== 'object') {
				all_list = new String(all_list);
			}
			all_list.from = from;
			all_list.to = to;
		}

		/**
		 * <code>

		all_list = diff_only ? diff_list : all_list[0];

		{Array}diff_list
			= [ diff_pair 1, diff_pair 2, ...,
			// need options.with_list
			from: [ 'diff from source' ], to: [ 'diff to source' ]
			]

		{Array}diff_pair =
			// 若僅有 from_diff_unique 或 to_diff_unique 則另一方會是 undefined。
			// 兩者皆有則表示為變更/modify。
			[ {Array}from_diff_unique, {Array}to_diff_unique,
			$**deprecated** [ {Array|String|Undefined}from_diff_unique, {Array|String|Undefined}to_diff_unique,

			// diff_pair.index = [ from_index, to_index ];
			// _index = [ start_index, end_index ]
			index:
				[ {Array}from_unique_indexes, {Array}to_unique_indexes ],

			// diff_pair.last_index: 上一個相同元素的 index。
			// 警告: diff_pair.last_index 在 _unique 最初的一個 index 可能不準確!
			last_index: [ {Integer}index of from, {Integer}index of to ]
			]

		{Array|Undefined}from_unique_indexes, to_unique_indexes
			= [ {Integer}start_index, {Integer}end_index ]
			or = undefined (insert / delete only)
			$**deprecated** or = {Integer}start index

		{Array}from_diff_unique, to_diff_unique
			= [ {String}diff_unique_line, {String}diff_unique_line, ...  ]
			$**deprecated** or = {String}diff_unique_line
			$**deprecated** or = undefined

		</code>
		 */
		return all_list;
	}

	_.LCS = LCS;

	/**
	 * <code>

	from	*    *   *        *
	index	012345678 9 10 11 12 13 14
	text	_0123_456 7 8  9  a
	text	 0123 456_7 8  9
	index	 0123 45678 9  10    11 12
	to

	</code>
	 */

	// 取得 to 之與 from 相對應的索引。
	// from[index_of_from] 相對應於
	// to[CeL.LCS.corresponding_index(diff_list, index_of_from_text)]
	function get_corresponding_index(diff_list, index_of_from_text,
			is_index_of_to) {
		if (diff_list.length === 0)
			return index_of_from_text;

		var FROM_INDEX = is_index_of_to ? 1 : 0, TO_INDEX = 1 - FROM_INDEX;
		var diff_list_index = 0, latest_offset_delta = 0;
		while (true) {
			var diff_pair = diff_list[diff_list_index++];
			var from_indexes = diff_pair.index[FROM_INDEX];
			if (!from_indexes) {
				from_indexes = diff_pair.last_index[FROM_INDEX] + 1;
				from_indexes = [ from_indexes, from_indexes ];
			}

			if (index_of_from_text > from_indexes[1]) {
				if (diff_list_index < diff_list.length) {
					continue;
				}
				from_indexes = diff_pair.index[FROM_INDEX];
				var to_indexes = diff_pair.index[TO_INDEX];
				latest_offset_delta = (to_indexes ? to_indexes[1]
						: diff_pair.last_index[TO_INDEX])
						- (from_indexes ? from_indexes[1]
								: diff_pair.last_index[FROM_INDEX]);
				break;
			}

			if (index_of_from_text < from_indexes[0]) {
				latest_offset_delta = diff_pair.last_index[TO_INDEX]
						- diff_pair.last_index[FROM_INDEX];
				break;
			}

			var to_indexes = diff_pair.index[TO_INDEX];
			if (!to_indexes) {
				to_indexes = diff_pair.last_index[TO_INDEX] + 1;
				to_indexes = [ to_indexes, to_indexes ];
			}

			if (from_indexes[1] === from_indexes[0]
					|| to_indexes[1] === to_indexes[0]) {
				return diff_pair.index[FROM_INDEX] ? to_indexes[0]
						: to_indexes[1] + 1;
			}

			return to_indexes[0] + (index_of_from_text - from_indexes[0])
					* (to_indexes[1] - to_indexes[0])
					/ (from_indexes[1] - from_indexes[0]);

		}

		return index_of_from_text + latest_offset_delta;
	}

	LCS.corresponding_index = get_corresponding_index;

	// unfinished
	function diff_with_Array(to, options) {
		function append(array, item, item_index) {
			if (item) {
				if (Array.isArray(item_index)) {
					item_index = item_index[0];
				}
				if (Array.isArray(item)) {
					array.append(item.filter(function(i, index) {
						if (i) {
							array.index.push(item_index + index);
							return true;
						}
					}));
				} else {
					array.index.push(item_index);
					array.push(item);
				}
			}
		}

		var diff = LCS(this || [], to || [], Object.assign({
			diff : true
		}, options)), from_added = [], to_added = [],
		// 避免經過重排後，已經無法回溯至原先資料。
		from_added_index = from_added.index = [], move_to = Object.create(null);
		to_added.index = [];

		// TODO: diff其中有undefined

		diff.forEach(function(pair) {
			append(from_added, pair[0], pair.index[0]);
			append(to_added, pair[1], pair.index[1]);
		});

		function scan_list(from_item, from_index) {
			// assert: {String}item
			var index = to_added.indexOf(from_item);
			// 去掉完全相同的行。
			if (index !== NOT_FOUND) {
				move_to[from_added.index[from_index]] = to_added.index[index];
				to_added.index.splice(index, 1);
				to_added.splice(index, 1);
				from_added.index.splice(from_index, 1);
				from_added[from_index] = false;
				return;
			}

			if (typeof from_item === 'string') {
				from_item = from_item.chars(true);
			}
			// use LCS() again
			var max_LCS_length = 0,
			// ↑ = Math.max(20, from_item.length / 2 | 0)
			max_LCS_data;
			to_added.forEach(function(to_item, to_index) {
				// assert: from_item, to_item 皆無 "\n"
				// console.log(to_item);
				if (typeof to_item === 'string') {
					to_item = to_item.chars(true);
				}
				var trace_Array = LCS_length(from_item, to_item, true),
				// const
				this_LCS_length = trace_Array.at(-1);
				if (max_LCS_length < this_LCS_length) {
					max_LCS_length = this_LCS_length;
					max_LCS_data = [ to_index, trace_Array, to_item ];
				}
			});

			if (!max_LCS_data) {
				return;
			}

			var diff = from_item.diff_with(max_LCS_data[2], {
				trace_Array : max_LCS_data[1]
			});
			if (diff[1]) {
				to_added[max_LCS_data[0]] = diff[1];
			} else {
				to_added.splice(max_LCS_data[0], 1);
			}
			from_added[from_index] = from_item = diff[0];
			if (from_item) {
				// 經過變更之後，可能需要再掃描一次。
				scan_list(from_item, from_index);
			}
		}
		// 檢查是否有被移到前方的，確保回傳的真正是 unique 的。在只有少量增加時較有效率。
		from_added.forEach(scan_list);

		from_added = from_added.filter(function(item) {
			return !!item;
		});

		if (from_added.length === 0) {
			from_added = [];
		} else {
			// 將item轉為{String}
			from_added = [ from_added.map(function(line) {
				return Array.isArray(line) ? line.join('') : line;
			}) ];
		}

		if (to_added.length > 0) {
			// 將item轉為{String}
			to_added = to_added.map(function(line) {
				return Array.isArray(line) ? line.join('') : line;
			});
			from_added[1] = to_added;
		}

		from_added.index = from_added_index;
		from_added.moved = move_to;
		return from_added;
	}

	function diff_with_String(to, options) {
		return diff_with_Array.call((this || '').split('\n'),
				Array.isArray(to) ? to : (to || '').split('\n'), options);
	}

	// ---------------------------------------------------------------------//

	if (false) {
		styled_list = CeL.coloring_diff('a b c d', 'a a c c', {
			headers : [ 'header 1: ', 'header 2: ' ],
			print : true
		});
	}

	// 為 diff 著色。
	function coloring_diff(from, to, options) {
		options = library_namespace.new_options(options);
		var diff_list = library_namespace.LCS(from, to, {
			diff : true
		});
		from = [ from /* , { reset : true } */];
		to = [ to /* , { reset : true } */];
		var diff, normal_style = options.normal_style || {
			// bold : false,
			fg : 'white',
			bg : 'black'
		}, diff_style = options.diff_style || {
			// bold : true,
			fg : 'red',
			bg : 'white'
		},
		// 用在多出來的文字的格式。
		insertion_style = options.insertion_style || {
			// bold : true,
			fg : 'white',
			bg : 'green'
		};

		function add_diff(styled_list, diff_index, is_insertion) {
			if (!diff_index) {
				// e.g., 只有義方有多東西。
				return;
			}

			var next_index = diff_index[1] + 1, this_slice = styled_list
					.shift();
			styled_list.unshift(this_slice.slice(0, diff_index[0]),
					is_insertion ? insertion_style : diff_style, this_slice
							.slice(diff_index[0], next_index), normal_style,
					this_slice.slice(next_index));
		}

		while (diff = diff_list.pop()) {
			var from_index = diff.index[0], to_index = diff.index[1];
			// console.trace([ from, from_index, to, to_index ]);
			add_diff(from, from_index, !to_index);
			add_diff(to, to_index, !from_index);
		}

		var headers = Array.isArray(options.headers) ? options.headers : [];
		from.unshift(headers[0] || '', normal_style);
		to.unshift(headers[1] || '', normal_style);
		if (options.header_style) {
			from.unshift('', options.header_style);
			to.unshift('', options.header_style);
		}

		if (options.print) {
			library_namespace.slog(from);
			library_namespace.slog(to);
		}
		// 注意: from.length 不一定等於 to.length
		return [ from, to ];
	}

	_.coloring_diff = coloring_diff;

	// ---------------------------------------------------------------------//

	/**
	 * Find the longest common starting substring in a set of strings
	 * 
	 * TODO: 可以嘗試先分割words整個字比對
	 * 
	 * @param {Array}string_list
	 *            array of strings
	 * @returns {ℕ⁰:Natural+0} longest common starting substring length
	 * 
	 * @see https://stackoverflow.com/questions/1916218/find-the-longest-common-starting-substring-in-a-set-of-strings/1917041#1917041
	 */
	function longest_common_starting_length(string_list) {
		string_list = string_list.filter(function(string) {
			return typeof string === 'string';
		});
		if (string_list.length <= 1) {
			return string_list[0] ? string_list[0].length : 0;
		}

		var char_index = 0;
		for (var last = string_list[0].length; char_index < last; char_index++) {
			var char_now = string_list[0].charAt(char_index);
			for (var index = 1; index < string_list.length; index++) {
				if (char_now !== string_list[index].charAt(char_index)) {
					return char_index;
				}
			}
		}
		return char_index;
	}

	_.longest_common_starting_length = longest_common_starting_length;

	// ---------------------------------------------------------------------//
	// https://en.wikipedia.org/wiki/Letter_case#Headings_and_publication_titles
	// http://adminsecret.monster.com/training/articles/358-what-to-capitalize-in-a-title
	// http://stackoverflow.com/questions/196972/convert-string-to-title-case-with-javascript
	// to_title_case()
	// Capitalize the first letter of string
	// also use CSS: text-transform:capitalize;
	// @see Camel_to_underscore() @ data.code
	/**
	 * 轉成標題用之格式。基本上即將每個字的第一個字母轉成大寫。
	 * 
	 * @param {Boolean}to_lower_first
	 *            將要轉換的文字先全部轉換成小寫。
	 * 
	 * @returns {String}標題用之格式文字。
	 * 
	 * @see function upper_case_initial(words) @ CeL.application.net.wiki
	 */
	function toTitleCase(to_lower_first) {
		// 要轉換的文字。
		var title = this.trim();
		if (to_lower_first)
			title = title.toLowerCase();
		return title.replace(/(^|\s)((\w)(\w*))/g,
		//
		function($0, $1, $2, $3, $4) {
			// console.log($0);
			return $2 in toTitleCase.lower ? $0
			//
			: $2 in toTitleCase.upper ? $0.toUpperCase()
			//
			: ($1 ? ' ' : '') + $3.toUpperCase() + $4;
		})
		// capitalize the first and last word of the title itself.
		// in case title === ''
		.replace(/^\w/, function($0) {
			return $0.toUpperCase();
		}).replace(/\s(\w)([\w\-]*)$/, function($0, $1, $2) {
			return ' ' + $1.toUpperCase() + $2;
		});
	}

	/**
	 * add exception words
	 * 
	 * @param {String|Array}words
	 *            exception words
	 */
	toTitleCase.add_exception = function(words, upper) {
		// initialize
		if (!toTitleCase.lower)
			toTitleCase.lower = Object.create(null);
		if (!toTitleCase.upper)
			toTitleCase.upper = Object.create(null);

		var target = upper ? toTitleCase.upper : toTitleCase.lower;

		if (typeof words === 'string')
			words = words.split(',');

		if (Array.isArray(words))
			words.forEach(function(word) {
				target[word] = true;
			});
		else
			Object.assign(target, words);
	};

	toTitleCase
			.add_exception('at,by,down,for,from,in,into,like,near,of,off,on,onto,over,past,to,upon,with,and,but,or,yet,for,nor,so,as,if,once,than,that,till,when,to,a,an,the');
	toTitleCase.add_exception(
			'id,tv,i,ii,iii,iv,v,vi,vii,viii,ix,x,xi,xii,xiii', true);

	if (false) {
		// 警告:這將使 node.js 6.2.2 卡住。
		/\[\[(?:[^\[\]]+|\[[^\[]|\][^\]])*\]\]/
				.test("[[                                     [[ ]] [[ ]] [[ ]] ]]");
	}

	if (false) {
		// 若要消除 "'''" 與 "''"，應將長的置於前面。
		wikitext = wikitext.remove_head_tail("'''", 0, ' ').remove_head_tail(
				"''", 0, ' ');
	}

	/**
	 * 去除首尾。這或許該置於 CeL.data.native...
	 * 
	 * @param {String}text
	 *            指定的輸入字串。
	 * @param {String}head
	 *            欲移除之首字串。
	 * @param {String}[tail]
	 *            欲移除之尾字串。
	 * @param {String}[insert_string]
	 *            將首尾以((insert_string))取代。 有設定 insert_string 時，會保留內容物。未設定
	 *            insert_string 時，會將內容物連同首尾一同移除。
	 * 
	 * @returns {String}replaced text. 變更/取代後的結果。
	 */
	function remove_head_tail(text, head, tail, insert_string) {
		var head_eq_tail, index_start, index_end;
		if (!tail) {
			tail = head;
			head_eq_tail = true;
		} else {
			head_eq_tail = head === tail;
		}

		var head_length = head.length, tail_length = tail.length;

		while (true) {

			if (head_eq_tail) {
				// 改採自前面搜尋模式。
				index_start = text.indexOf(head);
				if (index_start === NOT_FOUND) {
					// 無首
					return text;
				}
				index_end = text.indexOf(tail, index_start + head_length);
				if (index_end === NOT_FOUND) {
					// 有首無尾
					return text;
				}

			} else {
				index_end = text.indexOf(tail);
				if (index_end === NOT_FOUND) {
					// 無尾
					return text;
				}
				// 須預防中間包含 head / tail 之字元。
				index_start = text.lastIndexOf(head, index_end - head_length);
				if (index_start === NOT_FOUND) {
					// 有尾無首
					return text;
				}
			}

			text = text.slice(0, index_start)
			// 未設定 insert_string 時，會將內容物連同首尾一同移除。
			+ (insert_string === undefined ? '' : insert_string
			// 有設定 insert_string 時，會保留內容物。
			+ text.slice(index_start + head_length, index_end) + insert_string)
					+ text.slice(index_end + tail_length);
		}
	}

	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
	var has_replacement_offset;
	'ab'.replace(/b/g, function(all, offset, string) {
		has_replacement_offset = offset === 1 && string === 'ab';
	});

	/**
	 * 持續執行 .replace()，直到處理至穩定平衡無變動為止。
	 * 
	 * @param {String}text
	 *            指定的輸入字串。
	 * @param {RegExp}pattern
	 *            要搜索的正規表示式/規則運算式模式。
	 * @param {String|Function}replace_to
	 *            用於替換的字串。
	 * 
	 * @returns {String}replaced text. 變更/取代後的結果。
	 */
	function replace_till_stable(text, pattern, replace_to) {
		if (false)
			library_namespace.debug('pattern: ' + pattern, 6,
					'replace_till_stable');
		for (var original; original !== text;) {
			original = text;
			if (has_replacement_offset) {
				// new browsers
				text = original.replace(pattern, replace_to);
			} else {
				// old browsers
				text = original.replace(pattern, function(all) {
					var args = Array.from(arguments);
					args.push(original.indexOf(all, pattern.lastIndex || 0),
					// original_string 原字串。
					original,
					// 缺乏 group 的代表非常舊的 JavaScript 引擎版本，只能模擬一個代替。
					Object.create(null));
					replace_to.apply(undefined, args);
				});
			}
			if (false) {
				library_namespace.debug('[' + original + '] '
						+ (original === text ? 'done.' : '→ [' + text + ']'),
						6, 'replace_till_stable');
			}
		}
		return text;
	}

	/**
	 * 當欲變更/取代文字前後的文字符合要求時，才執行取代。
	 * 
	 * @param {String}text
	 *            指定的輸入字串。
	 * @param {RegExp}pattern
	 *            要搜索的正規表示式/規則運算式模式。
	 * @param {String|Function}replace_to
	 *            用於替換的字串。
	 * @param {Function|Undefined}[match_previous]
	 *            filter match_previous(previous token) return true if it's OK
	 *            to replace, false if it's NOT OK to replace.
	 * @param {Function|Undefined}[match_following]
	 *            filter match_following(next token) return true if it's OK to
	 *            replace, false if it's NOT OK to replace.
	 * 
	 * @returns {String}replaced text. 變更/取代後的結果。
	 */
	function replace_check_near(text, pattern, replace_to, match_previous,
			match_following) {
		var matched, results = [], last_index = 0;
		if (!pattern.global) {
			library_namespace.debug("The pattern doesn't has 'global' flag!",
					2, 'replace_check_near');
		}

		while (matched = pattern.exec(text)) {
			library_namespace.debug(pattern + ': ' + matched, 5,
					'replace_check_near');
			var previous_text = text.slice(last_index, matched.index),
			//
			_last_index = matched.index + matched[0].length;
			if ((!match_previous || match_previous(previous_text))
			// context 上下文 前後文
			// 前面的 foregoing paragraphs, see above, previously stated, precedent
			// 後面的 next; behind rearwards;back;posteriority;atergo;rearward
			&& (!match_following || match_following(text.slice(_last_index)))) {
				last_index = pattern.lastIndex;
				library_namespace.debug(previous_text + ',' + matched[0] + ','
						+ matched[0].replace(pattern, replace_to), 5,
						'replace_check_near');
				results.push(
				//
				previous_text, matched[0].replace(pattern, replace_to));
				// restore lastIndex.
				pattern.lastIndex = last_index;
				last_index = _last_index;
			}
			if (!pattern.global) {
				// 僅執行此一次。
				break;
			}
		}

		// 收尾。理想的 pattern 應該用 /([\s\S]*?)(delimiter|$)/g 之類，如此則無須收尾。
		if (last_index < text.length) {
			if (last_index === 0)
				// 完全沒相符的。
				return text;
			results.push(text.slice(last_index));
		}
		return results.join('');
	}

	var PATTERN_bigrams = /.{2}/g;

	/**
	 * Get Sørensen index, or Dice's coefficient.
	 * 
	 * String.similarity()
	 * 
	 * @param {String}string_1
	 *            sequence 1
	 * @param {String}string_2
	 *            sequence 2
	 * 
	 * @returns {Number}index (or named coefficient)
	 * 
	 * @see https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient
	 */
	function similarity_coefficient(string_1, string_2) {
		var count = 0,
		//
		bigrams_1 = string_1.match(PATTERN_bigrams).concat(
				string_1.slice(1).match(PATTERN_bigrams)),
		//
		bigrams_2 = string_2.match(PATTERN_bigrams).concat(
				string_2.slice(1).match(PATTERN_bigrams));

		bigrams_1.forEach(function(bigram) {
			if (bigrams_2.includes(bigram))
				count++;
		});

		// 0–1
		return 2 * count / (bigrams_1.length + bigrams_2.length);
	}

	function Array_truncate(length) {
		length = Math.max(0, length | 0);
		// This is faster than ((this.length = 0))
		while (this.length > length) {
			this.pop();
		}
		return this;
	}

	// ------------------------------------

	// 傳回字串於等寬字型monospaced font的寬度。螢幕對齊用。
	// 對fullwidth全形字元, width應算2
	// http://hyperrate.com/topic-view-thread.php?tid=3322
	// TODO: 警告:本函數尚未完善
	// @see http://unicode.org/Public/UNIDATA/EastAsianWidth.txt
	function String_display_width(string, font) {
		// 須注意:不同的字型對不同字元的規範可能不同!如'→'可能為2或1

		// @see [[en:Arrow (symbol)]]
		string = String(string).replace(/[\u2190-\u21FF]/g, '  ');

		// @see http://www.allenkuo.com/genericArticle/view1299.aspx
		// @see [[zh:全形和半形]]
		return string.replace(/[\u3000-\uFF5E]/g, '  ').length;
	}

	var DEFAULT_DISPLAY_WIDTH = 80;
	// 螢幕寬度多少字元。
	function screen_display_width() {
		return library_namespace.platform.nodejs && process.stdout.columns
		// process.stdout.columns 可能被設定為0。 e.g., at Travis CI
		|| DEFAULT_DISPLAY_WIDTH;
	}
	if (false) {
		library_namespace.debug('screen_display_width: '
				+ screen_display_width());
	}

	// CLI 螢幕顯示對齊用。e.g., 對比兩者。
	// left justification, to line up in correct
	function display_align(lines, options) {
		// 前置作業。
		options = library_namespace.setup_options(options);

		if (library_namespace.is_Object(lines)) {
			// pairs/lines={key:value,key:value,...}
			lines = Object.entries(lines);
		}
		var use_display_width = options.display_width || screen_display_width();
		library_namespace.debug('display width: ' + use_display_width, 3);
		var key_display_width = [], line_separator = String('line_separator' in options ? options.line_separator
				// '\n'
				: determine_line_separator()), force_using_new_line = line_separator === '\r\n' ? '\n'
				: line_separator;
		var some_has_new_line = lines.some(function(line) {
			var key = String(line[0]), value = String(line[1]);
			// assert: key.includes(line_separator) === false
			if ((value.length > use_display_width)
					|| value.includes(force_using_new_line)) {
				return true;
			}
			key_display_width.push(String_display_width(key));
		});

		var key_style = options.key_style,
		// e.g., value_style : { color : 'green' }
		value_style = options.value_style,
		// 採用醒目多色彩的顯示方式。
		using_style = !!('using_style' in options ? options.using_style
				: key_style || value_style),
		//
		display_lines = [], max_key_display_width = !some_has_new_line
				&& Math.max.apply(null, key_display_width);
		lines.forEach(function(line) {
			var key = String(line[0]), value = line[1];
			if (some_has_new_line) {
				key = key.trim();
				value = String(value);
				if (using_style) {
					value = [ key_style ? {
						T : key,
						S : key_style
					} : key, line_separator, value_style ? {
						T : value,
						S : value_style
					} : value ];
				} else {
					value = key + line_separator + value;
				}
			} else {
				// 可能沒有 key.padStart()!
				key = key.pad(key.length + max_key_display_width
				// assert: String_display_width(' ') === 1
				- key_display_width.shift(), options.to_fill || ' ',
						options.from_start);
				if (using_style) {
					value = [ key_style ? {
						T : key,
						S : key_style
					} : key, value_style ? {
						T : value,
						S : value_style
					} : value ];
				} else {
					value = key + value;
				}
			}
			if (using_style) {
				if (display_lines.length > 0) {
					// 前面有東西就先跳一行。
					display_lines.push(line_separator);
				}
				display_lines.append(value);
			} else {
				display_lines.push(value);
			}
		});
		return using_style ? display_lines : display_lines.join(line_separator);
	}

	_.display_align = display_align;

	// ------------------------------------

	var has_native_Math_imul = Math.imul
			&& !Math.imul[library_namespace.env.not_native_keyword];

	// ------------------------------------

	set_method(String.prototype, {
		covers : function(string) {
			return this.length >= string.length
			//
			&& !!String_covers(string, this);
		},

		count_of : set_bind(count_occurrence, true),
		// gText : getText,
		// HTML_to_Unicode : HTML_to_Unicode,

		// split_by : split_String_by_length,
		chunk : chunk,

		// 對於可能出現 surrogate pairs 的字串，應當以此來取代 .split('')！
		chars : split_by_code_point,
		codePoints : codePoints,

		remove_head_tail : set_bind(remove_head_tail, true),

		// repeatedly replace till stable
		replace_till_stable : set_bind(replace_till_stable, true),
		replace_check_near : set_bind(replace_check_near, true),

		pad : set_bind(pad, true),
		// 2021/5/4: ''.toRegExp() remane to → ''.to_RegExp()
		to_RegExp : set_bind(String_to_RegExp, true),
		toTitleCase : toTitleCase,
		between : function(head, foot, index, return_data) {
			// 確保可用 string.between().between() 的方法來作簡易篩選。
			/**
			 * <code>
			var data = get_intermediate([ this, head, foot, index ]);
			return data[3] !== NOT_FOUND && data[4] || '';
			</code>
			 */
			return get_intermediate(this, head, foot, index,
			//
			return_data) || '';
		},
		find_between : find_between,
		all_between : all_between,
		each_between : each_between,

		edit_distance : set_bind(Levenshtein_distance),
		diff_with : diff_with_String,

		display_width : set_bind(String_display_width),

		// https://docs.oracle.com/javase/10/docs/api/java/lang/String.html#hashCode()
		hashCode : has_native_Math_imul
		// https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0
		? function hashCode_imul() {
			var hash = 0;
			for (var index = 0, length = this.length; index < length; index++) {
				hash = (Math.imul(31, hash) + this.charCodeAt(index))
				// Convert to 32bit integer
				| 0;
			}
			return hash;
		}
		// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
		// https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
		: function hashCode_left_shift() {
			var hash = 0;
			for (var index = 0, length = this.length; index < length; index++) {
				hash = ((hash << 5) - hash + this.charCodeAt(index))
				// Convert to 32bit integer
				| 0;
			}
			return hash;
		}
	});

	set_method(Number.prototype, {
		// 'super' 於 IE 為保留字。
		to_super : superscript_integer,
		to_sub : subscript_integer,
		to_fixed : to_fixed,
		mod : set_bind(non_negative_modulo),
		pad : set_bind(pad, true)
	});

	set_method(RegExp.prototype, {
		clone : function() {
			// TODO: using Object.getOwnPropertyNames() to copy others
			return new RegExp(this.source, this.flags);
		},
		reflags : set_bind(renew_RegExp_flags)
	});

	set_method(library_namespace.env.global, {
		// 在 old IE 中 typeof alert==='object'
		// alert : JSalert,

		// https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate
		// https://github.com/YuzuJS/setImmediate/blob/master/setImmediate.js
		// TODO: window.postMessage can be used to trigger an immediate but
		// yielding callback.
		setImmediate : function setImmediate(callback) {
			var args = arguments;
			// 因為 setTimeout(callback, 0) 可能使 callback 傳入未規範的 arguments，因此不在外面處理
			// callback。
			return setTimeout(function() {
				if (args.length === 0) {
					callback();
					return;
				}
				args = Array.from(args);
				args.shift();
				callback.apply(null, args);
			}, 0);
		},
		clearImmediate : function clearImmediate(id) {
			return clearTimeout(id);
		}
	});

	// 建議不用，因為在for(in Array)時會...
	set_method(Array.prototype, {
		// Array.prototype.clone
		clone : function() {
			// TODO: using Object.getOwnPropertyNames() to copy others
			return this.slice();
		},
		remove_once : function(value) {
			var index = this.indexOf(value);
			if (index !== NOT_FOUND)
				return this.splice(index, 1);
		},
		// remove all. https://esdiscuss.org/topic/array-prototype-remove-item
		// value 很多的話，應該用 Set, 或 delete + 去除 blank。
		remove : function(value) {
			var index = 0;
			while ((index = this.indexOf(value, index)) !== NOT_FOUND)
				this.splice(index, 1);
		},
		// TODO: should use Array.prototype.reduce()
		// e.g., array.reduce((p,e)=>p+e)
		// Array.prototype.sum()
		sum : function(using_index) {
			// total summation
			// ABSORBING_ELEMENT
			var sum = 0;
			this.forEach(using_index ? function(e, i) {
				sum += i;
			} : function(e) {
				sum += +e;
			});
			return sum;
		},
		// Array.prototype.product()
		product : function(using_index) {
			// MULTIPLICATIVE_IDENTITY
			var product = 1;
			this.every(using_index ? function(e, i) {
				return product *= i;
			} : function(e) {
				return product *= +e;
			});
			return product;
		},
		// TODO: Object.revert_key_value(array, get_key, hash)
		//
		// Array.prototype.to_hash()
		// ['1e3',5,66]→{'1e3':0,'5':1,'66':2}
		// {Function}[get_key]
		to_hash : function(get_key, hash) {
			if (false) {
				return Object.assign(hash || Object.create(null),
				//
				Object.fromEntries(this.map(function(value, index) {
					if (get_key)
						value = get_key(value);
					if (typeof value === 'object')
						value = JSON.stringify(value);
					return [ value, index ];
				})));
			}

			if (!hash) {
				hash = Object.create(null);
			}
			// TODO: 衝突時處理。
			this.forEach(get_key ? function(value, index) {
				value = get_key(value);
				hash[typeof value === 'object' ? JSON.stringify(value)
				//
				: value] = index;
			} : function(value, index) {
				hash[typeof value === 'object' ? JSON.stringify(value)
				//
				: value] = index;
			});
			return hash;
		},
		// Array.prototype.frequency()
		frequency : array_frequency,
		// clone: Array.prototype.slice,
		append : append_to_Array,
		unique_sorted : unique_sorted_Array,
		/**
		 * 去掉已排序，或最起碼將相同元素集在一起之 Array 中重複的 items。
		 * 
		 * 應能確保順序不變。
		 */
		unique : function unique_Array(sorted, get_key) {
			if (typeof sorted === 'function') {
				// shift arguments.
				get_key = sorted;
				sorted = false;

			} else if (sorted) {
				return this.unique_sorted(get_key);
			} else if (typeof get_key !== 'function') {
				return Array.from(new Set(this));
			}

			// @see function Array_intersection_Map()
			var map = new Map;
			function set_item(item) {
				if (!map['has'](item))
					map['set'](get_key(item), item);
			}
			this.forEach(set_item);
			return Array.from(map.values());
		},
		// Check if there is only one unique/single value in the array.
		// 集合中包含不重複的元素的個數=1
		cardinal_1 : function cardinal_1() {
			var configured, value;
			value = this.every(function(element) {
				if (configured) {
					return Object.is(element, value);
				}
				value = element;
				return configured = true;
			});
			return !!(configured && value);
		},
		// Array.prototype.search_sorted
		search_sorted : set_bind(search_sorted_Array, true),
		// Array.prototype.first_matched
		first_matched : set_bind(first_matched, true),

		diff_with : diff_with_Array,

		// warpper
		run_serial :
		// [].run_serial(for_each(run_next, item, index, list), callback, _this)
		function Array_run_serial_asynchronous(for_each, callback, _this) {
			run_serial_asynchronous(for_each, this, callback, _this);
		},
		// [].run_parallel(for_each(run_next, item, index, list, get_status),
		// callback, _this)
		run_parallel : function Array_run_parallel_asynchronous(for_each,
				callback, _this) {
			run_serial_asynchronous(for_each, this, callback, _this, true);
		},

		truncate : Array_truncate,
		// empty the array. 清空 array. truncate
		// Array.prototype.clear()
		clear : Array_truncate
	});

	// ---------------------------------------------------------------------//

	/**
	 * patch: parse ISO date String for IE.<br />
	 * for this function, you should also include 'data.code.compatibility' for
	 * toISOString().
	 * 
	 * @example <code>

	CeL.log((new Date).toISOString());
	CeL.log('' + CeL.ISO_date((new Date).toISOString()));

	 * </code>
	 * 
	 * @param {String}ISO_date_String
	 * 
	 * @returns {Date} date
	 * 
	 * @since 2014/7/26 11:56:1
	 */
	function IE_ISO_date(ISO_date_String) {
		return new Date(IE_ISO_date.parse(ISO_date_String));
	}

	// 應測試是否可正確 parse。
	if (isNaN(Date.parse('0000-01-01T00:00:00.000Z'))) {
		// IE8?
		IE_ISO_date.offset = (new Date).getTimezoneOffset();

		IE_ISO_date.parse = function(ISO_date_String) {
			if (false) {
				library_namespace.debug(ISO_date_String
						.replace(/\.\d{3}Z$/, '').replace(/-/, '/'));
				library_namespace.debug(Date.parse(ISO_date_String.replace(
						/\.\d{3}Z$/, '').replace(/-/, '/')));
			}
			return Date.parse(ISO_date_String.replace(/\.\d{3}Z$/, '').replace(
					/-/, '/'))
					+ IE_ISO_date.offset;
		};
	} else {
		// normal.
		IE_ISO_date.parse = Date.parse;
	}

	_.ISO_date = IE_ISO_date;

	set_method(Date.prototype, {
		clone : function() {
			// TODO: using Object.getOwnPropertyNames() to copy others
			return new Date(this.getTime());
		}
	});

	// ---------------------------------------------------------------------//

	var fulfilled = Object.create(null);
	// assert: needs Promise.race()!
	//
	// status_handler(value is fulfilled && ('not thenable' || 'resolved' ||
	// 'rejected'), value: this_thenable)
	//
	// return promise to wait for the result
	function status_of_thenable(value, status_handler) {
		// Promise.isPromise()
		if (!library_namespace.is_thenable(value)) {
			status_handler('not thenable', value);
			return;
		}

		// https://stackoverflow.com/questions/30564053/how-can-i-synchronously-determine-a-javascript-promises-state
		// https://github.com/kudla/promise-status-async/blob/master/lib/promiseState.js
		/**
		 * <code>
		Promise.race([value, fulfilled]).then(v => { status = v === t ? "pending" : "fulfilled" }, () => { status = "rejected" });
		</code>
		 */
		return Promise.race([ value, fulfilled ])
		//
		.then(function(first_fulfilled) {
			var result = first_fulfilled !== fulfilled && 'resolved';
			status_handler(result, value);
			return result;
		}, function(error_reason) {
			// assert: first_fulfilled !== fulfilled
			// 因為 fulfilled 不會 throw
			var result = 'rejected';
			status_handler(result, value);
			return result;
		});
	}

	_.status_of_thenable = status_of_thenable;

	// ---------------------------------------------------------------------//

	// reverse key and value, 改成 value → key
	function Map__reverse_key_value(options) {
		// 前置處理。
		options = library_namespace.setup_options(options);
		var preserve_older = options.preserve_older,
		// original key is number
		key_is_number = options.key_is_number,
		//
		ignore_null_value = options.ignore_null_value;

		// if (library_namespace.env.has_for_of)
		var reversed_Map = new Map;
		this.forEach(preserve_older === false ? function(key, value) {
			if (ignore_null_value && !key)
				return;
			if (key_is_number)
				value = +value;
			// newer will overwrite older
			reversed_Map.set(key, value);
		} : function(key, value) {
			if (ignore_null_value && !key)
				return;
			if (key_is_number)
				value = +value;
			if (reversed_Map.has(key)) {
				if (!preserve_older) {
					// ignore exists
					return;
				}
				library_namespace
						.warn('Map__reverse_key_value: duplicated key [' + key
								+ ']: ' + reversed_Map.get(key) + '→' + value);
			}
			reversed_Map.set(key, value);
		});
		return reversed_Map;
	}

	set_method(Map.prototype, {
		reverse_key_value : Map__reverse_key_value
	});

	// ------------------------------------

	return (_// JSDT:_module_
	);
}
