igir
Version:
🕹 A zero-setup ROM collection manager that sorts, filters, extracts or archives, patches, and reports on collections of any size on any OS.
122 lines (121 loc) • 3.76 kB
JavaScript
import IgirException from '../../exceptions/igirException.js';
/**
* A parser for CMPRo schema DATs.
* @see http://www.logiqx.com/DatFAQs/CMPro.php
*/
export default class CMProParser {
static WHITESPACE_CHARS = new Set([' ', '\t', '\n', '\r', '\v']);
contents;
pos = 0;
constructor(contents) {
this.contents = contents;
}
/**
* Parse the CMPro DAT's file contents.
*/
parse() {
this.pos = 0;
const result = {};
while (this.pos < this.contents.length) {
const tag = this.parseTag();
const value = this.parseValue();
const existing = result[tag];
if (existing === undefined) {
result[tag] = value;
}
else {
if (Array.isArray(existing)) {
result[tag] = [...existing, value];
}
else {
result[tag] = [existing, value];
}
}
this.skipWhitespace();
}
return result;
}
skipWhitespace() {
while (CMProParser.WHITESPACE_CHARS.has(this.contents.charAt(this.pos))) {
this.pos += 1;
}
}
parseObject() {
if (this.contents.charAt(this.pos) === '(') {
this.pos += 1;
}
this.skipWhitespace();
const result = {};
while (this.contents.charAt(this.pos) !== ')') {
const tag = this.parseTag();
const value = this.parseValue();
const existing = result[tag];
if (existing === undefined) {
result[tag] = value;
}
else {
if (Array.isArray(existing)) {
result[tag] = [...existing, value];
}
else {
result[tag] = [existing, value];
}
}
this.skipWhitespace();
}
this.pos += 1;
return result;
}
parseTag() {
this.skipWhitespace();
const initialPos = this.pos;
while (!CMProParser.WHITESPACE_CHARS.has(this.contents.charAt(this.pos))) {
this.pos += 1;
}
return this.contents.slice(initialPos, this.pos);
}
parseValue() {
this.skipWhitespace();
// Parse object
if (this.contents.charAt(this.pos) === '(') {
this.pos += 1;
return this.parseObject();
}
// Parse quoted string
if (this.contents.charAt(this.pos) === '"') {
return this.parseQuotedString();
}
// Parse unquoted string
return this.parseUnquotedString();
}
parseQuotedString() {
if (this.contents.charAt(this.pos) !== '"') {
throw new IgirException('invalid quoted string');
}
this.pos += 1;
const initialPos = this.pos;
while (this.pos < this.contents.length) {
// String termination, return the value
if (this.contents.charAt(this.pos) === '"') {
const value = this.contents.slice(initialPos, this.pos);
this.pos += 1;
return value;
}
// Quoted character, skip it
if (this.contents.charAt(this.pos) === '\\') {
this.pos += 2;
}
else {
this.pos += 1;
}
}
throw new IgirException('invalid quoted string');
}
parseUnquotedString() {
const initialPos = this.pos;
while (!CMProParser.WHITESPACE_CHARS.has(this.contents.charAt(this.pos))) {
this.pos += 1;
}
return this.contents.slice(initialPos, this.pos);
}
}