【ネタ】サクラエディタ用の diff.exe を JScript で自作してみる

実際にコンパイル、実行可能ですが、動作はとっても遅いです……。
あくまでも 4/1 用のネタとしてご利用ください。


# サクラエディタ用の diff.exe を JScript で自作してみる

サクラエディタでは、diff.exeを呼び出すことで、２つのファイルの差分を表示する機能があります。
しかし、Windows用の出回っている diff.exe は古いし、新しめの diff.exe は依存ファイルをいろいろ要求されて動きません。
そこで、[以前作った JavaScript のソース](https://qiita.com/stonee/items/e2d88f07d08171b2816f)を流用して、自前の diff.exe を作成してみました。
内部のアルゴリズムについては、過去の記事で解説しています。

# コンパイル手順

まず、.NET Framework に含まれる JScript のコンパイラを探します。
エクスプローラで jsc.exe を検索してみてください。Windows7、Windows10 であれば、C:\Windows\ 以下のどこかにあると思います。
jsc.exe が見つかれば、この記事の末尾にあるソースをコマンドラインからコンパイルして、diff.exeを作成します。

```
cd /d C:\test
C:\Windows\Microsoft.NET\Framework\v4.0.30319\jsc.exe C:\test\diff.js
```

パスはそれぞれの環境にあわせて適宜書き換えてください。

出来上がった diff.exe をサクラエディタと同じフォルダに配置します。
既に diff.exe を利用している場合は、いつでも元に戻せるよう、どこか別フォルダに退避しておいて下さい。間違っても上書きしないように！！！

コンパイルした diff.exe は、コマンドラインから起動して使用可能です。
サポートしている出力形式は、ノーマル出力と --context、--unified、--brief です。
引数の仕様は本家 diffutils に合わせていますが、サクラエディタで diff を表示するのに必要な最低限の機能しか実装していません。

```
diff -u "c:\test\old.txt" "c:\test\new.txt"
```


# ソース

diff.js の名前でローカルに保存して下さい。

```javascript:diff.js
import System;

/****************************************************************************
  lesser diff.exe for Windows  (NOT compatible with GNU Diff)
  version 0.01
  Copyleft 2020-2021 stonee

  license: GPL3  https://www.gnu.org/licenses/gpl-3.0.html

  compile:
  C:\Windows\Microsoft.NET\Framework\v4.0.30319\jsc.exe diff.js
 ****************************************************************************/

var fso = new ActiveXObject("Scripting.FileSystemObject");

function isBlank(s) {
	for (var i = s.length - 1; i >= 0; i--) {
		var n = s.charCodeAt(i);
		if (!((n === 0x9) || (n === 0x20) || (n === 0xD) || (n === 0xA))) {
			return false;
		}
	}
	return true;
}
//==============================================================================
var Hunks = function() {
	this.vList = [];
}
Hunks.prototype.hunk_size = function(i) { return (this.vList[i].end - this.vList[i].start - 1); }
Hunks.prototype.gap = function(i) { return ((i == 0) ? (this.vList[i].start + 1) : (this.vList[i].start - this.vList[i - 1].end + 1)); }
Hunks.prototype.addEntry = function(start, end, isBlankLines) {
	this.vList.push({start:start, end:end, isBlankLines:isBlankLines});
}
Hunks.prototype.length = function() { return this.vList.length; }
Hunks.prototype.getHunk = function(i) { return this.vList[i]; }
Hunks.prototype.merge = function(lines, o_Hunk_other) {
	var i, n = this.vList.length, nEnd = 0;
	for (i = 1; i < n; i++) {
		if (nEnd + 1 < i) {
			this.vList[nEnd + 1] = this.vList[i];
			o_Hunk_other.vList[nEnd + 1] = o_Hunk_other.vList[i];
		}
		if ((this.vList[i].start - this.vList[nEnd].end + 1) <= lines) {
			this.vList[nEnd].end = this.vList[i].end;
			this.vList[nEnd].isBlankLines = (this.vList[nEnd].isBlankLines && this.vList[i].isBlankLines);
			o_Hunk_other.vList[nEnd].end = o_Hunk_other.vList[i].end;
			o_Hunk_other.vList[nEnd].isBlankLines = (o_Hunk_other.vList[nEnd].isBlankLines && o_Hunk_other.vList[i].isBlankLines);
		} else {
			nEnd++;
		}
	}

	for (i = nEnd + 2; i < n; i++) {
		this.vList.pop();
		o_Hunk_other.vList.pop();
	}
}
//==============================================================================
var DiffEngine = {
	bIgnoreCase: false,
	flgIgnoreBlank: 0,	//0:compare 1:ignore change 2:ignore all
	bIgnoreBlankLines: false,
	bDetectSimilarLine: true,
	strFile_L: "",
	strFile_R: "",
	vInfo_L: [],
	vInfo_R: [],
	oHunks_L: null,
	oHunks_R: null,
	threshold: 0.5,
	BLANK_PATTERN: /[\s\t]+/g,
	lines: 3,

	Compare: function(s1, s2, flgIgnoreBlank) {
		var i = 0, j = 0, n1, n2, len1 = s1.length, len2 = s2.length;
		var _isWhite = function(n) { return ((n == 0x9) || (n == 0x20) || (n == 0xD) || (n == 0xA)); };

		if (flgIgnoreBlank != 0) {
			while ((i < len1) && _isWhite(s1.charCodeAt(i))) { i++; }
			while ((j < len2) && _isWhite(s2.charCodeAt(j))) { j++; }
		} else if (len1 != len2) {
			return false;
		}
		while ((i < len1) && (j < len2)) {
			n1 = s1.charCodeAt(i);
			n2 = s2.charCodeAt(j);
			if ((flgIgnoreBlank != 0) && _isWhite(n1)) {
				if (flgIgnoreBlank == 1) {
					if (!_isWhite(n2)) { return false; }
					j++;
				}
				i++;
				while ((i < len1) && _isWhite(s1.charCodeAt(i))) { i++; }
				while ((j < len2) && _isWhite(s2.charCodeAt(j))) { j++; }
			} else if ((flgIgnoreBlank != 0) && _isWhite(n2)) {
				if (flgIgnoreBlank == 1) { return false; }
				j++;
				while ((j < len2) && _isWhite(s2.charCodeAt(j))) { j++; }
			} else if (n1 != n2) {
				return false;
			} else {
				i++;
				j++;
			}
		}
		if (flgIgnoreBlank != 0) {
			while ((i < len1) && _isWhite(s1.charCodeAt(i))) { i++; }
			while ((j < len2) && _isWhite(s2.charCodeAt(j))) { j++; }
		}
		return ((i >= len1) && (j >= len2));
	},
	CompareLine: function(x, y, bStrict) {
		if (this.vInfo_L[x].isBlank != this.vInfo_R[y].isBlank) { return 0; }
		var s1 = this.vLeft[x], s2 = this.vRight[y];

		if (this.vInfo_L[x].isBlank) {
			if ((this.flgIgnoreBlank != 0) || (s1 == s2)) { return 2; }
			return bStrict ? 0 : 1;
		}
		if (this.bIgnoreCase) {
			s1 = s1.toLowerCase();
			s2 = s2.toLowerCase();
		}
		if (this.Compare(s1, s2, this.flgIgnoreBlank)) { return 2; }
		if (bStrict || (this.vInfo_L[x].UniquePair >= 0) || (this.vInfo_R[y].UniquePair >= 0)) {
			return 0;
		}
		s1 = s1.replace(this.BLANK_PATTERN, "");
		s2 = s2.replace(this.BLANK_PATTERN, "");
		var len1 = s1.length, len2 = s2.length;
		return ((len1 > len2) ? this.CalcScore(s2, s1, len2, len1) : this.CalcScore(s1, s2, len1, len2));
	},
	CalcScore: function(s1, s2, len1, len2) {
		var p_max = len1 + 1 - this.threshold * len2;
		if (p_max < 1) { return 0; }	//optimize
		var y, p = 0, k = (len1 + len2 + 2), fp = new Array(k + 1);
		do {
			fp[k] = -1;
		} while (k--);
		for (; (p <= p_max) && (fp[len2 + 1] != len2); p++) {
			for (k = len1 - p; k < len2; k++) {
				for (y = Math.max(fp[k] + 1, fp[k + 2]);
					(y < k) && (s1.charCodeAt(y - k + len1) == s2.charCodeAt(y));
					y++) {}
				fp[k + 1] = y;
			}
			for (k = len2 + p; k >= len2; k--) {
				for (y = Math.max(fp[k] + 1, fp[k + 2]);
					(y < len2) && (s1.charCodeAt(y - k + len1) == s2.charCodeAt(y));
					y++) {}
				fp[k + 1] = y;
			}
		}
		return (1 - (len2 - len1 + p - 1) / len2);	//distance score
	},
	IsDuplicated: function(v, vInfo, i, j, bIgnoreCase, flgIgnoreBlank) {
		if ((vInfo[i].isBlank != vInfo[j].isBlank) || (vInfo[i].hash != vInfo[j].hash)) { return false; }
		return (bIgnoreCase ? this.Compare(v[i].toLowerCase(), v[j].toLowerCase(), flgIgnoreBlank) : this.Compare(v[i], v[j], flgIgnoreBlank));
	},
	hash_: function(s, bIgnoreCase) {
		s = s.replace(this.BLANK_PATTERN, "");
		var i, len = s.length, result = len;
		if (len == 0) { return 0; }
		if (bIgnoreCase) { s = s.toLowerCase(); }
		for (i = 0; i < len; i++) {
			result = s.charCodeAt(i) ^ (result << 2);
		}
		return result;
	},
	InitInfo: function(len1, len2) {
		var i, j, isUnique_L = new Array(len1), isUnique_R = new Array(len2);
		const bIgnoreCase = this.bIgnoreCase;
		this.vInfo_L = new Array(len1);
		this.vInfo_R = new Array(len2);
		for (i = 0; i < len1; i++) {
			this.vInfo_L[i] = {status:"+", pair:-1, isBlank:isBlank(this.vLeft[i]), hash:this.hash_(this.vLeft[i], bIgnoreCase), UniquePair:-1};
			isUnique_L[i] = !(this.vInfo_L[i].isBlank);
		}
		for (i = 0; i < len2; i++) {
			this.vInfo_R[i] = {status:"+", pair:-1, isBlank:isBlank(this.vRight[i]), hash:this.hash_(this.vRight[i], bIgnoreCase), UniquePair:-1};
			isUnique_R[i] = !(this.vInfo_R[i].isBlank);
		}
		if (!this.bDetectSimilarLine) { return; }
		for (i = 0; i < len1; i++) {
			if (!(isUnique_L[i])) { continue; }
			for (j = len1 - 1; j > i; j--) {
				if (!(this.vInfo_L[j].isBlank) && this.IsDuplicated(this.vLeft, this.vInfo_L, i, j, bIgnoreCase, 2)) {
					isUnique_L[i] = isUnique_L[j] = false;
					break;
				}
			}
		}
		for (i = 0; i < len2; i++) {
			if (!(isUnique_R[i])) { continue; }
			for (j = len2 - 1; j > i; j--) {
				if (!(this.vInfo_R[j].isBlank) && this.IsDuplicated(this.vRight, this.vInfo_R, i, j, bIgnoreCase, 2)) {
					isUnique_R[i] = isUnique_R[j] = false;
					break;
				}
			}
			if (!(isUnique_R[i])) { continue; }
			for (j = 0; j < len1; j++) {
				if (isUnique_L[j] && (this.vInfo_L[j].UniquePair < 0) && (this.CompareLine(j, i, true) > 1)) {
					this.vInfo_R[i].UniquePair = j;
					this.vInfo_L[j].UniquePair = i;
					break;
				}
			}
		}
	},
	Brief: function(strFile1, strFile2) {
		this.strFile_L = strFile1;
		this.strFile_R = strFile2;
		var s1 = LoadFile(strFile1);
		var s2 = LoadFile(strFile2);
		this.vLeft = s1.replace(/^\n+|\n+$/g, "").split("\n");
		this.vRight = s2.replace(/^\n+|\n+$/g, "").split("\n");

		var len1 = this.vLeft.length, len2 = this.vRight.length;

		//work around - "No newline at end of file"
		if (isBlank(this.vLeft[len1 - 1])) {
			this.vLeft.pop();
			len1--;
		}
		if (isBlank(this.vRight[len2 - 1])) {
			this.vRight.pop();
			len2--;
		}
		this.InitInfo(len1, len2);

		var x, y;
		for (x = 0, y = 0; (x < len1) && (y < len2); x++, y++) {
			if (this.bIgnoreBlankLines) {
				while (this.vInfo_L[x].isBlank && (x < len1 - 1)) { x++; }
				while (this.vInfo_R[y].isBlank && (y < len2 - 1)) { y++; }
			}
			if (this.CompareLine(x, y, true) < 1) { return true; }
		}
		return false;
	},
	Diff: function(strFile1, strFile2) {
		this.strFile_L = strFile1;
		this.strFile_R = strFile2;
		var s1 = LoadFile(strFile1);
		var s2 = LoadFile(strFile2);
		var eol1 = s1.match(/\r\n|\n|\r/);
		var eol2 = s2.match(/\r\n|\n|\r/);
		this.vLeft = ((eol1 != null) ? s1.split(eol1) : [s1]);
		this.vRight = ((eol2 != null) ? s2.split(eol2) : [s2]);

		var len1 = this.vLeft.length, len2 = this.vRight.length;

		//work around - "No newline at end of file"
		if (isBlank(this.vLeft[len1 - 1])) {
			this.vLeft.pop();
			len1--;
		}
		if (isBlank(this.vRight[len2 - 1])) {
			this.vRight.pop();
			len2--;
		}
		this.InitInfo(len1, len2);

		var o = (len1 > len2) ? this.Diff_(len2, len1, true) : this.Diff_(len1, len2, false);

		var vResult = [], vWork = [];
		for (; o; o = o.prev) {
			vResult.unshift(o);
			if ((o.op == "-") || (o.op == "+")) { continue; }
			this.vInfo_L[o.x].status = this.vInfo_R[o.y].status = o.op;
			this.vInfo_L[o.x].pair = o.y;
			this.vInfo_R[o.y].pair = o.x;
		}

		this.CreateHunksList(len1, len2, vResult);

		return vResult;
	},
	//len1 <= len2
	Diff_: function(len1, len2, bReverse) {
		var p, k = len1 + len2 + 2, fp = new Array(k + 1), ed = new Array(k + 1);
		var x, y, x1, y1, y0, score, bIsBlank, org;
		var bStrict = !this.bDetectSimilarLine, threshold = this.threshold;
		do {
			fp[k] = -1;
			ed[k] = null;
		} while (k--);
		for (p = 0; fp[len2 + 1] != len2; p++) {
			for (k = len1 - p; k < len2; k++) {
				y = Math.max(fp[k] + 1, fp[k + 2]);
				x = y - k + len1;
				if (bReverse) {
					x1 = y; y1 = x;
					if (y == fp[k + 2]) {
						ed[k + 1] = {op:"+", x:-1, y:y1 - 1, prev:ed[k + 2]};
					} else if (y > 0) {
						ed[k + 1] = {op:"-", x:x1 - 1, y:-1, prev:ed[k]};
					}
				} else {
					x1 = x; y1 = y;
					if ((y != (fp[k] + 1)) || (fp[k + 2] == 0)) {
						ed[k + 1] = {op:"-", x:x1 - 1, y:-1, prev:ed[k + 2]};
					} else if (y > 0) {
						ed[k + 1] = {op:"+", x:-1, y:y1 - 1, prev:ed[k]};
					}
				}
				for (bIsBlank = true, y0 = y, org = ed[k + 1]; x < len1; x++, y++, x1++, y1++) {
					if ((score = this.CompareLine(x1, y1, bStrict)) <= threshold) { break; }
					if (!this.vInfo_L[x1].isBlank) { bIsBlank = false; }
					ed[k + 1] = {op:((score > 1) ? "=" : "!"), x:x1, y:y1, prev:ed[k + 1]};
				}
				if (bIsBlank && !((x >= (len1 - 1)) && (y >= (len2 - 1)))) { y = y0; ed[k + 1] = org; }
				fp[k + 1] = y;
			}
			for (k = len2 + p; k >= len2; k--) {
				y = Math.max(fp[k] + 1, fp[k + 2]);
				x = y - k + len1;
				if (bReverse) {
					x1 = y; y1 = x;
					if (y == fp[k + 2]) {
						ed[k + 1] = {op:"+", x:-1, y:x - 1, prev:ed[k + 2]};
					} else if (y > 0) {
						ed[k + 1] = {op:"-", x:y - 1, y:-1, prev:ed[k]};
					}
				} else {
					x1 = x; y1 = y;
					if ((y != (fp[k] + 1)) || (fp[k + 2] == 0)) {
						ed[k + 1] = {op:"-", x:x - 1, y:-1, prev:ed[k + 2]};
					} else if (y > 0) {
						ed[k + 1] = {op:"+", x:-1, y:y - 1, prev:ed[k]};
					}
				}
				for (bIsBlank = true, y0 = y, org = ed[k + 1]; y < len2; x++, y++, x1++, y1++) {
					if ((score = this.CompareLine(x1, y1, bStrict)) <= threshold) { break; }
					if (!this.vInfo_L[x1].isBlank) { bIsBlank = false; }
					ed[k + 1] = {op:((score > 1) ? "=" : "!"), x:x1, y:y1, prev:ed[k + 1]};
				}
				if (bIsBlank && !((x >= (len1 - 1)) && (y >= (len2 - 1)))) { y = y0; ed[k + 1] = org; }
				fp[k + 1] = y;
			}
		}
		return ed[len2 + 1];
	},
	CreateHunksList: function(len1, len2, vResult) {
		this.oHunks_L = new Hunks();
		this.oHunks_R = new Hunks();
		var o_L = this.oHunks_L, o_R = this.oHunks_R;
		var o, x = 0, y = 0, x0 = -1, y0 = -1, bIsBlank_L, bIsBlank_R, op_bak = "", i;

		for (i = 0; o = vResult[i]; ) {
			if (o.op == "-") {			//left block
				for (x0 = o.x - 1, bIsBlank_L = true; (o = vResult[i]) && (x < len1) && (o.op == "-"); i++, x++) {
					if (!this.vInfo_L[x].isBlank) { bIsBlank_L = false; }
				}
				o_L.addEntry(x0, x, bIsBlank_L);
				op_bak = "-";
			} else if (o.op == "+") {	//right block
				if (op_bak != "-") { o_L.addEntry(x - 1, x, true); }
				for (y0 = o.y - 1, bIsBlank_R = true; (o = vResult[i]) && (y < len2) && (o.op == "+"); i++, y++) {
					if (!this.vInfo_R[o.y].isBlank) { bIsBlank_R = false; }
				}
				o_R.addEntry(y0, y, bIsBlank_R);
				op_bak = "+";
			} else if (o.op == "!") {	//change
				if (op_bak == "-") { o_R.addEntry(o.y - 1, o.y, true); }
				x0 = o.x - 1, bIsBlank_L = true;
				y0 = o.y - 1, bIsBlank_R = true;
				for (op_bak = o.op; (o = vResult[i]) && (o.op == op_bak); i++, x++, y++) {
					if (!this.vInfo_L[x].isBlank) { bIsBlank_L = false; }
					if (!this.vInfo_R[o.y].isBlank) { bIsBlank_R = false; }
				}
				o_L.addEntry(x0, x, bIsBlank_L);
				o_R.addEntry(y0, y, bIsBlank_R);
			} else {	//common
				if (op_bak == "-") { o_R.addEntry(o.y - 1, o.y, true); }
				for (op_bak = o.op; (o = vResult[i]) && (o.op == op_bak); i++, x++, y++) {}
			}
		}
		if (op_bak == "-") { o_R.addEntry(len2 - 1, len2, true); }
	}
};//DiffEngine
//==============================================================================
function Int2Str(v) { return ((v == 0) ? "" : (v + "")); }

function do_diff_c(strFile1, strFile2) {
	var objDiff = DiffEngine;
	objDiff.bDetectSimilarLine = true;
	var vResult = objDiff.Diff(strFile1, strFile2);
	var vTable = [];
	var i, x, y, x_min, y_min, x_max, y_max, oHunk_L, oHunk_R, nHunkLen = 0;
	var lines = objDiff.lines;

	var dt1 = new Date(fso.GetFile(strFile1).DateLastModified);
	var dt2 = new Date(fso.GetFile(strFile2).DateLastModified);
	vTable.push("*** " + strFile1 + "	" + formatdate(dt1));
	vTable.push("--- " + strFile2 + "	" + formatdate(dt2));

	nHunkLen = objDiff.oHunks_L.length();
	for (i = 0; i < nHunkLen; i++) {
		oHunk_L = objDiff.oHunks_L.getHunk(i);
		oHunk_R = objDiff.oHunks_R.getHunk(i);

		vTable.push("***************");

		x_min = Math.max(oHunk_L.start - lines + 1, 0);
		x_max = Math.min(objDiff.vLeft.length - 1, oHunk_L.end + lines - 1);
		vTable.push("*** "  + (x_min + 1) + ", "  + (x_max + 1) + " ****");

		for (x = x_min; x <= x_max; x++) {
			switch (objDiff.vInfo_L[x].status) {
				case "+":
					vTable.push("- " + objDiff.vLeft[x]);
					break;
				case "!":
					vTable.push("! " + objDiff.vLeft[x]);
					break;
				default:
					vTable.push("  " + objDiff.vLeft[x]);
					break;
			}
		}

		y_min = Math.max(oHunk_R.start - lines + 1, 0);
		y_max = Math.min(objDiff.vRight.length - 1, oHunk_R.end + lines - 1);
		vTable.push("--- "  + (y_min + 1) + ", "  + (y_max + 1) + " ----");

		for (y = y_min; y <= y_max; y++) {
			switch (objDiff.vInfo_R[y].status) {
				case "+":
					vTable.push("+ " + objDiff.vRight[y]);
					break;
				case "!":
					vTable.push("! " + objDiff.vRight[y]);
					break;
				default:
					vTable.push("  " + objDiff.vRight[y]);
					break;
			}
		}
	}

	for (i = 0; i < vTable.length; i++) {
		print(vTable[i]);
	}
}
function do_diff_u(strFile1, strFile2) {
	var objDiff = DiffEngine;
	objDiff.bDetectSimilarLine = false;
	var vResult = objDiff.Diff(strFile1, strFile2);
	var vTable = [], vWork = [], nCount_L = 0, nCount_R = 0, nCount_M = -1;
	var i, j, o, n1 = 1, n2 = 1, current;
	var lines = objDiff.lines;

	var dt1 = new Date(fso.GetFile(strFile1).DateLastModified);
	var dt2 = new Date(fso.GetFile(strFile2).DateLastModified);
	vTable.push("--- " + strFile1 + "	" + formatdate(dt1));
	vTable.push("+++ " + strFile2 + "	" + formatdate(dt2));

	for (i = 0; current = vResult[i]; i++) {
		if (current.op == "+") {
			if ((i <= 0 || vResult[i - 1].op == "=") && (nCount_M < 0 || nCount_M >= lines)) {
				CountLine(i, lines);
				vTable.push("@@ -" +  + Int2Str(n1 - vWork.length) + "," + nCount_L
					+ " +" + Int2Str(n2 - vWork.length) + "," + nCount_R + " @@");
				vTable = vTable.concat(vWork);
				vWork = [];
			}
			vTable.push("+" + objDiff.vRight[current.y]);
			nCount_M = 0;
			n2++;
		} else if (current.op == "-") {
			if ((i <= 0 || vResult[i - 1].op == "=") && (nCount_M < 0 || nCount_M >= lines)) {
				CountLine(i, lines);
				vTable.push("@@ -" + Int2Str(n1 - vWork.length) + "," + nCount_L
					+ " +" + Int2Str(n2 - vWork.length) + "," + nCount_R + " @@");
				vTable = vTable.concat(vWork);
				vWork = [];
			}
			vTable.push("-" + objDiff.vLeft[current.x]);
			nCount_M = 0;
			n1++;
		} else {
			if (nCount_M >= 0 && nCount_M < lines) {
				vTable.push(" " + objDiff.vLeft[current.x]);
				nCount_M++;
			} else {
				nCount_L = 0;
				nCount_R = 0;
				nCount_M = -1;
				for (j = i + 1; o = vResult[j]; j++) {
					if (j > (i + lines)) { break; }
					if (o.op != "=") {
						vTable.push(" " + objDiff.vLeft[current.x]);
						break;
					}
				}
			}
			n1++;
			n2++;
		}
	}

	function CountLine(i_start, lines) {
		nCount_L = vWork.length;
		nCount_R = vWork.length;
		var nCount_M2 = -1, j, o;
		for (j = i_start; o = vResult[j]; j++) {
				switch (o.op) {
					case "-":
						nCount_L++;
						nCount_M2 = 0;
						break;
					case "+":
						nCount_R++;
						nCount_M2 = 0;
						break;
					default:
						nCount_L++;
						nCount_R++;
						if (nCount_M2 >= 0 && nCount_M2 < lines) {
							nCount_M2++;
							if (nCount_M2 >= lines) { return; }
						}
						break;
				}
		}
	}

	for (i = 0; i < vTable.length; i++) {
		print(vTable[i]);
	}
}

function do_diff_normal(strFile1, strFile2) {
	var objDiff = DiffEngine;
	objDiff.bDetectSimilarLine = true;
	var vResult = objDiff.Diff(strFile1, strFile2);
	var vTable = [], vWork = [], vWork2 = [], i, current, strHeader = "", epos;
	var arrlen = vResult.length - 1, x = -1;

	vTable.push("$diff " + strFile1 + " " + strFile2);
	for (i = 0; current = vResult[i]; i++) {
		if (objDiff.bIgnoreBlankLines && 
			(((current.x >= 0) && objDiff.vInfo_L[current.x].isBlank)
			|| ((current.y >= 0) && objDiff.vInfo_R[current.y].isBlank))) {
			if (current.x >= 0) { x = current.x; }
			continue;
		}
		if (current.op == "+") {
			if (i == 0) {
				strHeader = "0a" + (current.y + 1);
			} else {
				strHeader = (x + 1) + "a" + (current.y + 1);
			}

			vWork = [];
			vWork.push("> " + objDiff.vRight[current.y]);
			while ((i < (arrlen - 1)) && vResult[i + 1].op == "+") {
				i++;
				vWork.push("> " + objDiff.vRight[vResult[i].y]);
			}

			if (current.y != vResult[i].y) {
				epos = vResult[i].y;
				if (objDiff.bIgnoreBlankLines) {
					while (objDiff.vInfo_R[epos].isBlank) {
						vWork.pop();
						epos--;
					}
				}
				if (current.y != epos) {
					strHeader += "," + (epos + 1);
				}
			}
			vTable.push(strHeader);
			vTable = vTable.concat(vWork);
		} else if (current.op == "-") {
			strHeader = (current.x + 1);

			vWork = [];
			vWork.push("< " + objDiff.vLeft[current.x]);
			while ((i < (arrlen - 1)) && vResult[i + 1].op == "-") {
				i++;
				vWork.push("< " + objDiff.vLeft[vResult[i].x]);
			}

			if (current.x != vResult[i].x) {
				epos = vResult[i].x;
				if (objDiff.bIgnoreBlankLines) {
					while (objDiff.vInfo_L[epos].isBlank) {
						vWork.pop();
						epos--;
					}
				}
				if (current.x != epos) {
					strHeader += "," + (epos + 1);
				}
			}
			if (i >= (arrlen - 1)) {
				strHeader += "d " + objDiff.vRight.length;
			} else {
				strHeader += "d " + (vResult[i + 1].y);
			}
			vTable.push(strHeader);
			vTable = vTable.concat(vWork);
			x = vResult[i].x;
		} else if (current.op == "!") {
			vWork = [];
			vWork2 = [];
			var x0 = (current.x + 1), y0 = (current.y + 1);

			vWork.push("< " + objDiff.vLeft[current.x]);
			vWork2.push("> " + objDiff.vRight[current.y]);
			while ((i < (arrlen - 1)) && vResult[i + 1].op == "!") {
				i++;
				vWork.push("< " + objDiff.vLeft[vResult[i].x]);
				vWork2.push("> " + objDiff.vRight[vResult[i].y]);
			}

			if (current.x != vResult[i].x) {
				strHeader = x0 + "," + (vResult[i].x + 1) + "c" + y0 + "," + (vResult[i].y + 1);
			} else {
				strHeader = x0 + "c" + y0;
			}
			vTable.push(strHeader);
			vTable = vTable.concat(vWork)
			vTable.push("---");
			vTable = vTable.concat(vWork2)
			x = vResult[i].x;
		} else {
			x = vResult[i].x;
		}
	}//for

	for (i = 0; i < vTable.length; i++) {
		print(vTable[i]);
	}
}

function do_diff_q(strFile1, strFile2, strOutputFormat) {
	var objDiff = DiffEngine;
	objDiff.bDeectSimilarLine = false;
	if (objDiff.Brief(strFile1, strFile2)) {
		print("Files " + strFile1 + " and " + strFile2 + " differ");
	} else if (strOutputFormat == "s") {
		print("Files " + strFile1 + " and " + strFile2 + " are identical");
	}
}


function pad_zero(n) { return ((n < 10) ? ("0" + n) : n); }
function formatdate(dt) {
	var month = dt.getMonth() + 1;
	return (dt.getFullYear() + "-" + pad_zero(month) + "-" + pad_zero(dt.getDate()) + " "
		+ pad_zero(dt.getHours()) + ":" + pad_zero(dt.getMinutes()) + ":" + pad_zero(dt.getSeconds())
		+ ".000000000 +0900");
}

function LoadFile(strFile) {
	var o = new ActiveXObject("ADODB.Stream");
	var text = "";
	o.type = 2;
	o.charset = "_autodetect_all";
	o.open();
	o.loadFromFile(strFile);
	text = o.readText(-1);
	o.close();
	return text;
}
//==============================================================================
function Version() {
	print("lesser diff.exe for Windows  (NOT compatible with GNU Diff)");
}

function do_diff_main() {
	try {
		var arg = Environment.GetCommandLineArgs();
		var strFile1 = "", strFile2 = "", strOption;
		var strOutputFormat = "", nLines = 3;

		if (arg.length < 2) {
			Version();
			return 0;
		}

		for (var i = 1; i < arg.length; i++) {
			strOption = arg[i];

			var index = strOption.indexOf("=");
			var strOption2 = "";
			if (index !== -1) {
				strOption2 = strOption.substring(index + 1);
				strOption = strOption.substring(0, index);
			}

			switch (strOption) {
				case "--text":
				case "--minimal":
				case "--label":
					break;
				case "--context":
					strOutputFormat = "c";
					if (strOption2 != "") {
						nLines = parseInt(strOption2, 10);
						if (isNaN(nLines)) { nLines = 3; }
					}
					break;
				case "--inhibit-hunk-merge":
					//not implemented
					break;
				case "--ignore-space-change":
					if (DiffEngine.flgIgnoreBlank != 2) {
						DiffEngine.flgIgnoreBlank = 1;
					}
					break;
				case "--ignore-all-space":
					DiffEngine.flgIgnoreBlank = 2;
					break;
				case "--ignore-blank-lines":
					DiffEngine.bIgnoreBlankLines = true;
					break;
				case "--ignore-tab-expansion":
				case "--expand-tabs":
				case "--initial-tab":
					//not implemented
					break;
				case "--ignore-case":
					DiffEngine.bIgnoreCase = true;
					break;
				case "--brief":
					if (strOutputFormat != "s") { strOutputFormat = "q"; }
					break;
				case "--unified":
					strOutputFormat = "u";
					if (strOption2 != "") {
						nLines = parseInt(strOption2, 10);
						if (isNaN(nLines)) { nLines = 3; }
					}
					break;
				case "--report-identical-files":
					strOutputFormat = "s";
					break;
				case "--recursive":
					break;
				case "-v":
				case "--version":
					Version();
					return 0;
					break;
				case "--help":
					Version();
					return 0;
					break;
				default:
					var strPrefix = strOption.substring(0, 1);
					if ((strPrefix == "-") || (strPrefix == "/")) {
						if (strOption.substring(0, 2) == "--") {
							//do nothing
						} else {
							for (var j = 1; j < strOption.length; j++) {
								switch (strOption.substring(j, j + 1)) {
									case "a":	//--text
									case "d":	//--minimal
									case "e":	//--ed
									case "E":	//--ignore-tab-expansion
									case "I":	//--ignore-matching-lines
									case "L":	//--label
									case "n":	//--rcs
									case "t":	//--expand-tabs
									case "T":	//--initial-tab
									case "r":	//--recursive
									case "y":	//--side-by-side
										//not implemented
										break;
									case "c":	//--context
									case "C":	//--context
										strOutputFormat = "c";
										if ((i + 1) < arg.length) {
											nLines = parseInt(arg[i + 1], 10);
											if (isNaN(nLines)) {
												nLines = 3;
											} else {
												i++
											}
										}
										break;
									case "q":	//--brief
										if (strOutputFormat != "s") { strOutputFormat = "q"; }
										break;
									case "s":	//--report-identical-files
										strOutputFormat = "s";
										break;
									case "i":	//--ignore-case
										break;
									case "b":	//--ignore-space-change
										if (DiffEngine.flgIgnoreBlank != 2) {
											DiffEngine.flgIgnoreBlank = 1;
										}
										break;
									case "w":	//--inore-all-space
										DiffEngine.flgIgnoreBlank = 2;
										break;
									case "B":	//--ignore-blank-lines
										DiffEngine.bIgnoreBlankLines = true;
										break;
									case "u":	//--unified
									case "U":	//--unified
										strOutputFormat = "u";
										if ((i + 1) < arg.length) {
											nLines = parseInt(arg[i + 1], 10);
											if (isNaN(nLines)) {
												nLines = 3;
											} else {
												i++
											}
										}
										break;
									default:
										break;
								}
							}
						}
					} else if (strFile1 == "") {
						strFile1 = strOption;
					} else if (strFile2 == "") {
						strFile2 = strOption;
					}
					break;
			}
		}

		if ((strFile1 == "") || (strFile2 == "")) {
			throw "file name is empty";
		}
		if (!fso.FileExists(strFile1) || !fso.FileExists(strFile2)) {
			throw "file not found";
		}

		DiffEngine.lines = nLines;

		if (strOutputFormat == "u") {	//unified
			do_diff_u(strFile1, strFile2);
		} else if ((strOutputFormat == "q") || (strOutputFormat == "s")) {
			do_diff_q(strFile1, strFile2, strOutputFormat);
		} else if (strOutputFormat == "c") {	//context
			do_diff_c(strFile1, strFile2);
		} else {
			do_diff_normal(strFile1, strFile2);
		}

		return 0;
	} catch (e) {
		print (e.message || e.toString())
		return -1;
	} finally {
	}
}

do_diff_main();

```