function _parse()

in src/main.ts [40:499]


function _parse(content: string, filename:string|null, locationKeyName:string|null): any {
	const len = content.length;

	let pos = 0;
	let line = 1;
	let char = 0;

	// Skip UTF8 BOM
	if (len > 0 && content.charCodeAt(0) === ChCode.BOM) {
		pos = 1;
	}

	function advancePosBy(by:number): void {
		if (locationKeyName === null) {
			pos = pos + by;
		} else {
			while (by > 0) {
				let chCode = content.charCodeAt(pos);
				if (chCode === ChCode.LINE_FEED) {
					pos++; line++; char=0;
				} else {
					pos++; char++;
				}
				by--;
			}
		}
	}
	function advancePosTo(to:number): void {
		if (locationKeyName === null) {
			pos = to;
		} else {
			advancePosBy(to - pos);
		}
	}

	function skipWhitespace(): void {
		while (pos < len) {
			let chCode = content.charCodeAt(pos);
			if (chCode !== ChCode.SPACE && chCode !== ChCode.TAB && chCode !== ChCode.CARRIAGE_RETURN && chCode !== ChCode.LINE_FEED) {
				break;
			}
			advancePosBy(1);
		}
	}

	function advanceIfStartsWith(str:string): boolean {
		if (content.substr(pos, str.length) === str) {
			advancePosBy(str.length);
			return true;
		}
		return false;
	}

	function advanceUntil(str:string): void {
		let nextOccurence = content.indexOf(str, pos);
		if (nextOccurence !== -1) {
			advancePosTo(nextOccurence + str.length);
		} else {
			// EOF
			advancePosTo(len);
		}
	}

	function captureUntil(str:string): string {
		let nextOccurence = content.indexOf(str, pos);
		if (nextOccurence !== -1) {
			let r = content.substring(pos, nextOccurence);
			advancePosTo(nextOccurence + str.length);
			return r;
		} else {
			// EOF
			let r = content.substr(pos);
			advancePosTo(len);
			return r;
		}
	}

	let state = State.ROOT_STATE;

	let cur:any = null;
	let stateStack:State[] = [];
	let objStack:any[] = [];
	let curKey:string|null = null;

	function pushState(newState:State, newCur:any): void {
		stateStack.push(state);
		objStack.push(cur);
		state = newState;
		cur = newCur;
	}

	function popState(): void {
		if (stateStack.length === 0) {
			return fail('illegal state stack');
		}
		state = stateStack.pop()!;
		cur = objStack.pop();
	}

	function fail(msg:string): void {
		throw new Error('Near offset ' + pos + ': ' + msg + ' ~~~' + content.substr(pos, 50) + '~~~');
	}

	const dictState = {
		enterDict: function() {
			if (curKey === null) {
				return fail('missing <key>');
			}
			let newDict: Dict = {};
			if (locationKeyName !== null) {
				newDict[locationKeyName] = {
					filename: filename,
					line: line,
					char: char
				};
			}
			cur[curKey] = newDict;
			curKey = null;
			pushState(State.DICT_STATE, newDict);
		},
		enterArray: function() {
			if (curKey === null) {
				return fail('missing <key>');
			}
			let newArr:any[] = [];
			cur[curKey] = newArr;
			curKey = null;
			pushState(State.ARR_STATE, newArr);
		}
	};

	const arrState = {
		enterDict: function() {
			let newDict: Dict = {};
			if (locationKeyName !== null) {
				newDict[locationKeyName] = {
					filename: filename,
					line: line,
					char: char
				};
			}
			cur.push(newDict);
			pushState(State.DICT_STATE, newDict);
		},
		enterArray: function() {
			let newArr:any[] = [];
			cur.push(newArr);
			pushState(State.ARR_STATE, newArr);
		}
	};


	function enterDict() {
		if (state === State.DICT_STATE) {
			dictState.enterDict();
		} else if (state === State.ARR_STATE) {
			arrState.enterDict();
		} else { // ROOT_STATE
			cur = {};
			if (locationKeyName !== null) {
				cur[locationKeyName] = {
					filename: filename,
					line: line,
					char: char
				};
			}
			pushState(State.DICT_STATE, cur);
		}
	}
	function leaveDict() {
		if (state === State.DICT_STATE) {
			popState();
		} else if (state === State.ARR_STATE) {
			return fail('unexpected </dict>');
		} else { // ROOT_STATE
			return fail('unexpected </dict>');
		}
	}
	function enterArray() {
		if (state === State.DICT_STATE) {
			dictState.enterArray();
		} else if (state === State.ARR_STATE) {
			arrState.enterArray();
		} else { // ROOT_STATE
			cur = [];
			pushState(State.ARR_STATE, cur);
		}
	}
	function leaveArray() {
		if (state === State.DICT_STATE) {
			return fail('unexpected </array>');
		} else if (state === State.ARR_STATE) {
			popState();
		} else { // ROOT_STATE
			return fail('unexpected </array>');
		}
	}
	function acceptKey(val:string) {
		if (state === State.DICT_STATE) {
			if (curKey !== null) {
				return fail('too many <key>');
			}
			curKey = val;
		} else if (state === State.ARR_STATE) {
			return fail('unexpected <key>');
		} else { // ROOT_STATE
			return fail('unexpected <key>');
		}
	}
	function acceptString(val:string) {
		if (state === State.DICT_STATE) {
			if (curKey === null) {
				return fail('missing <key>');
			}
			cur[curKey] = val;
			curKey = null;
		} else if (state === State.ARR_STATE) {
			cur.push(val);
		} else { // ROOT_STATE
			cur = val;
		}
	}
	function acceptReal(val:number) {
		if (isNaN(val)) {
			return fail('cannot parse float');
		}
		if (state === State.DICT_STATE) {
			if (curKey === null) {
				return fail('missing <key>');
			}
			cur[curKey] = val;
			curKey = null;
		} else if (state === State.ARR_STATE) {
			cur.push(val);
		} else { // ROOT_STATE
			cur = val;
		}
	}
	function acceptInteger(val:number) {
		if (isNaN(val)) {
			return fail('cannot parse integer');
		}
		if (state === State.DICT_STATE) {
			if (curKey === null) {
				return fail('missing <key>');
			}
			cur[curKey] = val;
			curKey = null;
		} else if (state === State.ARR_STATE) {
			cur.push(val);
		} else { // ROOT_STATE
			cur = val;
		}
	}
	function acceptDate(val:Date) {
		if (state === State.DICT_STATE) {
			if (curKey === null) {
				return fail('missing <key>');
			}
			cur[curKey] = val;
			curKey = null;
		} else if (state === State.ARR_STATE) {
			cur.push(val);
		} else { // ROOT_STATE
			cur = val;
		}
	}
	function acceptData(val:string) {
		if (state === State.DICT_STATE) {
			if (curKey === null) {
				return fail('missing <key>');
			}
			cur[curKey] = val;
			curKey = null;
		} else if (state === State.ARR_STATE) {
			cur.push(val);
		} else { // ROOT_STATE
			cur = val;
		}
	}
	function acceptBool(val:boolean) {
		if (state === State.DICT_STATE) {
			if (curKey === null) {
				return fail('missing <key>');
			}
			cur[curKey] = val;
			curKey = null;
		} else if (state === State.ARR_STATE) {
			cur.push(val);
		} else { // ROOT_STATE
			cur = val;
		}
	}

	function escapeVal(str:string): string {
		return str.replace(/&#([0-9]+);/g, function(_:string, m0:string) {
			return (<any>String).fromCodePoint(parseInt(m0, 10));
		}).replace(/&#x([0-9a-f]+);/g, function(_:string, m0:string) {
			return (<any>String).fromCodePoint(parseInt(m0, 16));
		}).replace(/&amp;|&lt;|&gt;|&quot;|&apos;/g, function(_:string) {
			switch (_) {
				case '&amp;': return '&';
				case '&lt;': return '<';
				case '&gt;': return '>';
				case '&quot;': return '"';
				case '&apos;': return '\'';
			}
			return _;
		})
	}

	interface IParsedTag {
		name: string;
		isClosed: boolean;
	}

	function parseOpenTag(): IParsedTag {
		let r = captureUntil('>');
		let isClosed = false;
		if (r.charCodeAt(r.length - 1) === ChCode.SLASH) {
			isClosed = true;
			r = r.substring(0, r.length - 1);
		}

		return {
			name: r.trim(),
			isClosed: isClosed
		};
	}

	function parseTagValue(tag:IParsedTag): string {
		if (tag.isClosed) {
			return '';
		}
		let val = captureUntil('</');
		advanceUntil('>');
		return escapeVal(val);
	}

	while (pos < len) {
		skipWhitespace();
		if (pos >= len) {
			break;
		}

		const chCode = content.charCodeAt(pos);
		advancePosBy(1);
		if (chCode !== ChCode.LESS_THAN) {
			return fail('expected <');
		}

		if (pos >= len) {
			return fail('unexpected end of input');
		}

		const peekChCode = content.charCodeAt(pos);

		if (peekChCode === ChCode.QUESTION_MARK) {
			advancePosBy(1);
			advanceUntil('?>');
			continue;
		}

		if (peekChCode === ChCode.EXCLAMATION_MARK) {
			advancePosBy(1);

			if (advanceIfStartsWith('--')) {
				advanceUntil('-->');
				continue;
			}

			advanceUntil('>');
			continue;
		}

		if (peekChCode === ChCode.SLASH) {
			advancePosBy(1);
			skipWhitespace();

			if (advanceIfStartsWith('plist')) {
				advanceUntil('>');
				continue;
			}

			if (advanceIfStartsWith('dict')) {
				advanceUntil('>');
				leaveDict();
				continue;
			}

			if (advanceIfStartsWith('array')) {
				advanceUntil('>');
				leaveArray();
				continue;
			}

			return fail('unexpected closed tag');
		}

		let tag = parseOpenTag();

		switch (tag.name) {
			case 'dict':
				enterDict();
				if (tag.isClosed) {
					leaveDict();
				}
				continue;

			case 'array':
				enterArray();
				if (tag.isClosed) {
					leaveArray();
				}
				continue;

			case 'key':
				acceptKey(parseTagValue(tag));
				continue;

			case 'string':
				acceptString(parseTagValue(tag));
				continue;

			case 'real':
				acceptReal(parseFloat(parseTagValue(tag)));
				continue;

			case 'integer':
				acceptInteger(parseInt(parseTagValue(tag), 10));
				continue;

			case 'date':
				acceptDate(new Date(parseTagValue(tag)));
				continue;

			case 'data':
				acceptData(parseTagValue(tag));
				continue;

			case 'true':
				parseTagValue(tag);
				acceptBool(true);
				continue;

			case 'false':
				parseTagValue(tag);
				acceptBool(false);
				continue;
		}

		if (/^plist/.test(tag.name)) {
			continue;
		}

		return fail('unexpected opened tag ' + tag.name);
	}

	return cur;
}