@chess-fu/pgn-parser
Version:
Chess PGN parser for 8x8 chess games for import or export standards
464 lines • 16.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var EOF = '';
var NEWLINE = '\n';
var PgnTokenType;
(function (PgnTokenType) {
PgnTokenType["EndOfFile"] = "";
PgnTokenType["Newline"] = "\n";
PgnTokenType["Whitespace"] = " ";
PgnTokenType["CommentStart"] = "{";
PgnTokenType["CommentEnd"] = "}";
PgnTokenType["TagPairStart"] = "[";
PgnTokenType["TagPairEnd"] = "]";
PgnTokenType["RavStart"] = "(";
PgnTokenType["RavEnd"] = ")";
PgnTokenType["ExpansionStart"] = "<";
PgnTokenType["ExpansionEnd"] = ">";
PgnTokenType["LineEscape"] = "%";
PgnTokenType["Quote"] = "\"";
PgnTokenType["CommentToEOL"] = ";";
PgnTokenType["FullStop"] = ".";
PgnTokenType["Asterisks"] = "*";
PgnTokenType["NAG"] = "$";
PgnTokenType["SymbolExt"] = "-extra";
PgnTokenType["SymbolChar"] = "alpha-num";
PgnTokenType["Unknown"] = "unknown";
})(PgnTokenType = exports.PgnTokenType || (exports.PgnTokenType = {}));
var PgnDataCursor = (function () {
function PgnDataCursor(data, index, length) {
var _this = this;
this.Readers = {
SymbolSimple: function () {
return _this.peekToken() === PgnTokenType.SymbolChar || _this.peek() === '_';
},
NotNewline: function () {
return _this.peekToken() !== PgnTokenType.Newline;
},
NotCommentEnd: function () {
return _this.peekToken() !== PgnTokenType.CommentEnd;
},
NotExpansionEnd: function () {
return _this.peekToken() !== PgnTokenType.ExpansionEnd;
}
};
this.pieceSAN = {
P: true, R: true, N: true, B: true, Q: true, K: true
};
this._data = data;
this._offset = index || 0;
this._validLength = length || (this._data ? this._data.length : -1);
this._line = 1;
this._lineOffset = 0;
}
PgnDataCursor.prototype.throwError = function (text) {
var message = text + " (at line " + this._line + ":" + (this._offset - this._lineOffset) + ")";
var error = new Error(message);
error.lineNumber = this._line;
error.lineOffset = this._offset - this._lineOffset;
error.textOffset = this._offset;
throw error;
};
PgnDataCursor.prototype.position = function () { return this._offset; };
PgnDataCursor.prototype.save = function () {
var _a = this, _line = _a._line, _lineOffset = _a._lineOffset, _offset = _a._offset;
return { _line: _line, _lineOffset: _lineOffset, _offset: _offset };
};
PgnDataCursor.prototype.restore = function (saveData) {
this._line = saveData._line;
this._lineOffset = saveData._lineOffset;
this._offset = saveData._offset;
};
PgnDataCursor.prototype.peek = function () {
if (this._offset >= this._validLength) {
return EOF;
}
return this._data[this._offset];
};
PgnDataCursor.prototype.peekExact = function (match) {
for (var ix = 0; ix < match.length; ix++) {
if (this._data[this._offset + ix] !== match[ix]) {
return null;
}
}
return match;
};
PgnDataCursor.prototype.peekToken = function () {
var ch = this.peek();
switch (ch) {
case '': return PgnTokenType.EndOfFile;
case '\n':
case '\r': return PgnTokenType.Newline;
case '\t':
case '\v':
case ' ': return PgnTokenType.Whitespace;
case '{': return PgnTokenType.CommentStart;
case '}': return PgnTokenType.CommentEnd;
case '[': return PgnTokenType.TagPairStart;
case ']': return PgnTokenType.TagPairEnd;
case '(': return PgnTokenType.RavStart;
case ')': return PgnTokenType.RavEnd;
case '<': return PgnTokenType.ExpansionStart;
case '>': return PgnTokenType.ExpansionEnd;
case '%': return PgnTokenType.LineEscape;
case '"': return PgnTokenType.Quote;
case ';': return PgnTokenType.CommentToEOL;
case '.': return PgnTokenType.FullStop;
case '*': return PgnTokenType.Asterisks;
case '$': return PgnTokenType.NAG;
case '_':
case '+':
case '#':
case '=':
case ':':
case '-': return PgnTokenType.SymbolExt;
default: break;
}
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
return PgnTokenType.SymbolChar;
}
return PgnTokenType.Unknown;
};
PgnDataCursor.prototype.isEOF = function () {
return this.peek() === EOF;
};
PgnDataCursor.prototype.seek = function (relativeOffset) {
for (var ix = 0; ix < relativeOffset; ix++) {
this.read();
}
};
PgnDataCursor.prototype.read = function () {
if (this._offset >= this._validLength) {
return EOF;
}
var char = this._data[this._offset++];
if (char === NEWLINE) {
this._lineOffset = this._offset;
this._line++;
}
return char;
};
PgnDataCursor.prototype.readFromPrevious = function (position) {
if (position < this._offset) {
return this._data.substr(position, this._offset - position);
}
return '';
};
PgnDataCursor.prototype.readWhile = function (test) {
var result = [];
while (test() && !this.isEOF()) {
result.push(this.read());
}
return result.join('');
};
PgnDataCursor.prototype.readSymbol = function () {
if (this.peekToken() !== PgnTokenType.SymbolChar) {
return this.throwError("Expected a symbol start character");
}
return this.read() + this.readWhile(this.Readers.SymbolSimple);
};
PgnDataCursor.prototype.readAll = function (char, limit) {
var count = 0;
var next = this.peek();
while (next === char) {
if (limit && count >= limit) {
return count;
}
count++;
this.read();
next = this.peek();
}
return count;
};
PgnDataCursor.prototype.readNumber = function () {
var digits = [];
var next = this.peek();
while (next >= '0' && next <= '9') {
digits.push(this.read());
next = this.peek();
}
if (!digits.length) {
return null;
}
return parseInt(digits.join(''), 10);
};
PgnDataCursor.prototype.readString = function () {
if (this.peekToken() !== PgnTokenType.Quote) {
return this.throwError("Expected a quoted string");
}
this.read();
var result = [];
var next = this.read();
while (next !== EOF && next !== '"') {
if (next === '\\') {
var escaped = this.peek();
if (escaped === '\\' || escaped === '"') {
result.push(this.read());
next = this.read();
continue;
}
}
if (next === '\n' || next === '\r') {
return this.throwError("String contains new line \"" + result.join('') + "\"");
}
result.push(next);
next = this.read();
}
if (next !== '"') {
return this.throwError("Unterminated string \"" + result.join('') + "\"");
}
return result.join('');
};
PgnDataCursor.prototype.skipCommentsFrom = function (comments) {
var startPos = this._offset;
var found = [];
while (true) {
var savePos = this.save();
var next = this.peekToken();
if (next === PgnTokenType.CommentToEOL) {
this.read();
found.push(this.readWhile(this.Readers.NotNewline));
if (this.peekToken() === PgnTokenType.Newline) {
this.read();
}
}
else if (next === PgnTokenType.CommentStart) {
this.read();
found.push(this.readWhile(this.Readers.NotCommentEnd));
if (this.peekToken() === PgnTokenType.CommentEnd) {
this.read();
}
else {
this.restore(savePos);
return this.throwError('Unterminated comment.');
}
}
else if (next === PgnTokenType.ExpansionStart) {
this.read();
this.readWhile(this.Readers.NotExpansionEnd);
if (this.peekToken() === PgnTokenType.ExpansionEnd) {
this.read();
}
else {
this.restore(savePos);
return this.throwError('Unterminated expansion text.');
}
}
else if (next === PgnTokenType.LineEscape && (this._offset === this._lineOffset)) {
this.read();
this.readWhile(this.Readers.NotNewline);
}
else {
break;
}
}
if (comments) {
found.forEach(function (c) { return comments.push(c); });
}
return this._offset > startPos;
};
PgnDataCursor.prototype.skipWhitespace = function (skipNewline, comments) {
if (skipNewline === void 0) { skipNewline = false; }
while (true) {
var next = this.peekToken();
if (next === PgnTokenType.Whitespace || (skipNewline && next === PgnTokenType.Newline)) {
this.read();
}
else if (this.skipCommentsFrom(comments)) {
}
else {
break;
}
}
};
PgnDataCursor.prototype.readTagPair = function () {
if (this.peekToken() !== PgnTokenType.TagPairStart) {
return this.throwError("Expected a tag-pair open");
}
var comments = [];
var savePos = this.save();
this.read();
this.skipWhitespace(true, comments);
var symbol = this.readSymbol();
this.skipWhitespace(true, comments);
var value = this.readString();
this.skipWhitespace(true, comments);
if (this.peekToken() !== PgnTokenType.TagPairEnd) {
this.restore(savePos);
return this.throwError("The tag pair " + symbol + " = " + value + " was not closed");
}
this.read();
var hdr = { name: symbol, value: value };
if (comments.length) {
hdr.comments = comments;
}
return hdr;
};
PgnDataCursor.prototype.readMoveText = function () {
var savePos = this.save();
var startPos = this.position();
var rawText = '';
var move = null;
try {
move = this._readMoveText();
rawText = this.readFromPrevious(startPos);
if (move && move.to) {
move.raw = rawText;
move.san = move.piece === 'P' ? '' : move.piece;
if (move.piece === 'K' && move.to && move.to[0] === 'O') {
move.san = '';
}
move.san += move.from || '';
move.san += move.captured ? 'x' : '';
move.san += move.to || '';
move.san += move.promotion ? ('=' + move.promotion) : '';
move.san += move.check === '+' ? '+' : (move.check ? '#' : '');
return move;
}
}
finally {
if (!move) {
this.restore(savePos);
}
}
return this.throwError("Invalid move \"" + rawText + "\"");
};
PgnDataCursor.prototype._readMoveText = function () {
var _this = this;
var move = {};
var next = this.peek();
if (next === 'O') {
move.piece = 'K';
}
else if (this.pieceSAN[next]) {
move.piece = this.read();
next = this.peek();
}
else {
move.piece = 'P';
}
if (next >= 'a' && next <= 'h') {
move.to = this.read();
next = this.peek();
}
if (next >= '1' && next <= '8') {
move.to = (move.to || '') + this.read();
next = this.peek();
}
if (next === '-' || next === ':' || next === 'x' || (next >= 'a' && next <= 'h')) {
if (move.to) {
move.from = move.to;
}
delete move.to;
if (next === '-' || next === ':' || next === 'x') {
move.captured = (next === ':' || next === 'x');
this.read();
next = this.peek();
}
if (next >= 'a' && next <= 'h') {
move.to = this.read();
next = this.peek();
}
if (next >= '1' && next <= '8') {
move.to = (move.to || '') + this.read();
next = this.peek();
}
}
if (!move.to && next === 'O') {
var castle = this.peekExact('O-O-O') || this.peekExact('O-O');
if (castle) {
move.to = castle;
this.seek(castle.length);
next = this.peek();
}
}
if (next === '=') {
if (move.piece !== 'P') {
return this.throwError("Invalid promotion from piece " + move.piece);
}
this.read();
next = this.peek();
move.promotion = this.read().toUpperCase();
if (move.promotion !== 'Q' && move.promotion !== 'B' && move.promotion !== 'N' && move.promotion !== 'R') {
return this.throwError("Invalid promotion to piece " + move.promotion);
}
}
var annotations = [
'!!', '!?', '?!', '??', '!', '?',
'+/=', '=/+', '+/−', '−/+', '+−', '−+', '=',
'=/\u221E', '\u221E',
];
var checkOrMate = ['#', '++', '+'];
var find = function (arr, test) {
for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) {
var item = arr_1[_i];
if (test(item))
return item;
}
return undefined;
};
var found = undefined;
var last = -1;
while (last !== this.position()) {
last = this.position();
found = find(annotations, function (m) { return _this.peekExact(m); });
if (found) {
this.seek(found.length);
if (move.annotations) {
return this.throwError("Found multiple annotations \"" + move.annotations + " and " + found);
}
move.annotations = found;
}
found = find(checkOrMate, function (m) { return _this.peekExact(m); });
if (found) {
this.seek(found.length);
if (move.check) {
return this.throwError("Found multiple check flags \"" + move.check + " and " + found);
}
move.check = found;
}
if (this.peekExact('e.p.')) {
this.seek(4);
}
if (this.peek() === '$') {
this.read();
var nagNum = this.readNumber();
if (typeof nagNum !== 'number') {
return this.throwError("Invalid NAG supplied");
}
if (move.nag) {
return this.throwError("Found multiple annotations \"" + move.nag + " and " + nagNum);
}
move.nag = '$' + ("" + nagNum);
}
}
var unknown = '';
while (!this.isMoveStop(this.peekToken())) {
unknown += this.read();
}
if (unknown.length) {
move.unknown = unknown;
}
if (move.to && move.to.length) {
return move;
}
return null;
};
PgnDataCursor.prototype.isMoveStop = function (token) {
switch (token) {
case PgnTokenType.NAG:
case PgnTokenType.SymbolExt:
case PgnTokenType.SymbolChar:
case PgnTokenType.Unknown:
case PgnTokenType.LineEscape:
case PgnTokenType.Quote:
return false;
default:
return true;
}
};
return PgnDataCursor;
}());
exports.PgnDataCursor = PgnDataCursor;
exports.default = PgnDataCursor;
//# sourceMappingURL=pgnDataCursor.js.map