UNPKG

onelang

Version:

OneLang transpiler framework core

264 lines 10.2 kB
(function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "../../Generator/Utils"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Utils_1 = require("../../Generator/Utils"); class Cursor { constructor(offset, line, column, lineStart, lineEnd) { this.offset = offset; this.line = line; this.column = column; this.lineStart = lineStart; this.lineEnd = lineEnd; } } exports.Cursor = Cursor; class ParseError { constructor(message, cursor = null, reader = null) { this.message = message; this.cursor = cursor; this.reader = reader; } } exports.ParseError = ParseError; class Reader { constructor(input) { this.input = input; this.wsOffset = 0; this.offset = 0; this.lineComment = "//"; this.supportsBlockComment = true; this.blockCommentStart = "/*"; this.blockCommentEnd = "*/"; this.commentDisabled = false; this.identifierRegex = "[A-Za-z_][A-Za-z0-9_]*"; this.numberRegex = "[+-]?(\\d*\\.\\d+|\\d+\\.\\d+|0x[0-9a-fA-F_]+|0b[01_]+|[0-9_]+)"; this.errors = []; this.errorCallback = null; this.wsLineCounter = 0; this.moveWsOffset = true; this.prevTokenOffset = -1; this.cursorSearch = new CursorPositionSearch(input); } clone() { return Object.assign(Object.create(Object.getPrototypeOf(this)), this); } get eof() { return this.offset >= this.input.length; } get cursor() { return this.cursorSearch.getCursorForOffset(this.offset); } get linePreview() { const cursor = this.cursor; const line = this.input.substring(cursor.lineStart, cursor.lineEnd - 1); return `${line}\n${" ".repeat(cursor.column - 1)}^^^`; } get preview() { let preview = this.input.substr(this.offset, 20).replace(/\n/g, "\\n"); if (preview.length === 20) preview += "..."; return preview; } fail(message) { const error = new ParseError(message, this.cursor, this); this.errors.push(error); if (this.errorCallback) this.errorCallback(error); else throw new Error(`${message} at ${error.cursor.line}:${error.cursor.column}\n${this.linePreview}`); } skipWhitespace(includeInTrivia = false) { for (; this.offset < this.input.length; this.offset++) { const c = this.input[this.offset]; if (c === '\n') this.wsLineCounter++; if (!(c === '\n' || c === '\r' || c === '\t' || c === ' ')) break; } if (!includeInTrivia) this.wsOffset = this.offset; } skipUntil(token) { const index = this.input.indexOf(token, this.offset); if (index === -1) return false; this.offset = index + token.length; if (this.moveWsOffset) this.wsOffset = this.offset; return true; } skipLine() { if (!this.skipUntil("\n")) this.offset = this.input.length; } isAlphaNum(c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c === '_'; } peekToken(token) { this.skipWhitespaceAndComment(); if (this.input.startsWith(token, this.offset)) { // TODO: hackish way to make sure space comes after word tokens if (this.isAlphaNum(token[token.length - 1]) && this.isAlphaNum(this.input[this.offset + token.length])) return false; return true; } else { return false; } } readToken(token) { if (this.peekToken(token)) { this.prevTokenOffset = this.offset; this.wsOffset = this.offset = this.offset + token.length; return true; } return false; } readAnyOf(tokens) { for (const token of tokens) if (this.readToken(token)) return token; return null; } expectToken(token, errorMsg = null) { if (!this.readToken(token)) this.fail(errorMsg || `expected token '${token}'`); } expectOneOf(tokens) { const result = this.readAnyOf(tokens); if (result === null) this.fail(`expected one of ${tokens.map(x => `'${x}'`).join(", ")}`); return result; } static matchFromIndex(pattern, input, offset) { const regex = new RegExp(pattern, "gy"); regex.lastIndex = offset; const matches = regex.exec(input); return matches === null ? null : Array.from(matches); } readRegex(pattern) { const matches = Reader.matchFromIndex(pattern, this.input, this.offset); if (matches !== null) { this.prevTokenOffset = this.offset; this.wsOffset = this.offset = this.offset + matches[0].length; } return matches; } skipWhitespaceAndComment() { if (this.commentDisabled) return; this.moveWsOffset = false; while (true) { this.skipWhitespace(true); if (this.input.startsWith(this.lineComment, this.offset)) { this.skipLine(); } else if (this.supportsBlockComment && this.input.startsWith(this.blockCommentStart, this.offset)) { if (!this.skipUntil(this.blockCommentEnd)) this.fail(`block comment end ("${this.blockCommentEnd}") was not found`); } else { break; } } this.moveWsOffset = true; } readLeadingTrivia() { this.skipWhitespaceAndComment(); const thisLineStart = this.input.lastIndexOf("\n", this.offset); if (thisLineStart <= this.wsOffset) return ""; let result = this.input.substring(this.wsOffset, thisLineStart + 1); result = Utils_1.deindent(result); this.wsOffset = thisLineStart; return result; } readIdentifier() { this.skipWhitespace(); const idMatch = this.readRegex(this.identifierRegex); if (idMatch === null) return null; return idMatch[0]; } readNumber() { this.skipWhitespace(); const numMatch = this.readRegex(this.numberRegex); if (numMatch === null) return null; if (this.readRegex("[0-9a-zA-Z]") !== null) this.fail("invalid character in number"); return numMatch[0]; } readString() { this.skipWhitespace(); const strMatch = this.readRegex("'(\\\\'|[^'])*'") || this.readRegex('"(\\\\"|[^"])*"'); if (!strMatch) return null; let str = strMatch[0].substr(1, strMatch[0].length - 2); str = strMatch[0] === "'" ? str.replace("\\'", "'") : str.replace('\\"', '"'); // TODO: hack: this logic is langauge-dependent str = str.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\\\/g, "\\"); return str; } expectIdentifier(errorMsg = null) { const id = this.readIdentifier(); if (id === null) this.fail(errorMsg || "expected identifier"); return id; } readModifiers(modifiers) { const result = []; while (true) { let success = false; for (const modifier of modifiers) { if (this.readToken(modifier)) { result.push(modifier); success = true; } } if (!success) break; } return result; } } exports.Reader = Reader; class CursorPositionSearch { constructor(input) { this.input = input; this.lineOffsets = [0]; for (let i = 0; i < input.length; i++) if (input[i] === '\n') this.lineOffsets.push(i + 1); this.lineOffsets.push(input.length); } getLineIdxForOffset(offset) { let low = 0, high = this.lineOffsets.length - 1; while (low <= high) { const middle = Math.floor((low + high) / 2); const middleOffset = this.lineOffsets[middle]; if (offset == middleOffset) return middle; else if (offset <= middleOffset) high = middle - 1; else low = middle + 1; } return low - 1; } getCursorForOffset(offset) { const lineIdx = this.getLineIdxForOffset(offset); const lineStart = this.lineOffsets[lineIdx]; const lineEnd = this.lineOffsets[lineIdx + 1]; const column = offset - lineStart + 1; if (column < 1) debugger; return new Cursor(offset, lineIdx + 1, offset - lineStart + 1, lineStart, lineEnd); } } }); //# sourceMappingURL=Reader.js.map