UNPKG

@chess-fu/pgn-parser

Version:

Chess PGN parser for 8x8 chess games for import or export standards

464 lines 16.9 kB
"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