UNPKG

nadesiko3

Version:
263 lines (231 loc) 8.2 kB
interface PreprocessItem { text: string; sourcePosition: number } /** prepareとtokenizeのソースマッピング */ export class SourceMappingOfTokenization { private readonly sourceCodeLength: number private readonly preprocessed: PreprocessItem[] private readonly cumulativeSum: number[] private lastIndex: number private lastPreprocessedCodePosition: number /** * @param {number} sourceCodeLength * @param {PreprocessItem[]} preprocessed */ constructor (sourceCodeLength: number, preprocessed: PreprocessItem[]) { /** @private @readonly */ this.sourceCodeLength = sourceCodeLength /** @private @readonly */ this.preprocessed = preprocessed let i = 0 /** @private @readonly @type {number[]} */ this.cumulativeSum = [] for (const el of preprocessed) { this.cumulativeSum.push(i) i += el.text.length } /** @private */ this.lastIndex = 0 /** @private */ this.lastPreprocessedCodePosition = 0 } /** * preprocess後の文字列上のoffsetからソースコード上のoffsetへ変換 * @param {number} preprocessedCodePosition * @returns {number} */ map (preprocessedCodePosition: number): number { const i = this.findIndex(preprocessedCodePosition) return Math.min( this.preprocessed[i].sourcePosition + (preprocessedCodePosition - this.cumulativeSum[i]), i === this.preprocessed.length - 1 ? this.sourceCodeLength : this.preprocessed[i + 1].sourcePosition - 1 ) } /** * @param {number} preprocessedCodePosition * @returns {number} */ findIndex (preprocessedCodePosition: number): number { // 連続アクセスに対する高速化 if (preprocessedCodePosition < this.lastPreprocessedCodePosition) { this.lastIndex = 0 } this.lastPreprocessedCodePosition = preprocessedCodePosition for (let i = this.lastIndex; i < this.preprocessed.length - 1; i++) { if (preprocessedCodePosition < this.cumulativeSum[i + 1]) { this.lastIndex = i return i } } this.lastIndex = this.preprocessed.length - 1 return this.preprocessed.length - 1 } } export class SourceMappingOfIndentSyntax { private lines: { offset: number, len: number }[] private readonly linesInsertedByIndentationSyntax: number[] private readonly linesDeletedByIndentationSyntax: { lineNumber: number, len: number }[] lastLineNumber: number lastOffset: number /** * @param {string} codeAfterProcessingIndentationSyntax * @param {readonly number[]} linesInsertedByIndentationSyntax * @param {readonly { lineNumber: number, len: number }[]} linesDeletedByIndentationSyntax */ constructor ( codeAfterProcessingIndentationSyntax: string, linesInsertedByIndentationSyntax: number[], linesDeletedByIndentationSyntax: { lineNumber: number, len: number }[] ) { /** @private @type {{ offset: number, len: number }[]} */ this.lines = [] /** @private @readonly */ this.linesInsertedByIndentationSyntax = linesInsertedByIndentationSyntax /** @private @readonly */ this.linesDeletedByIndentationSyntax = linesDeletedByIndentationSyntax let offset = 0 for (const line of codeAfterProcessingIndentationSyntax.split('\n')) { this.lines.push({ offset, len: line.length }) offset += line.length + 1 } /** @private */ this.lastLineNumber = 0 /** @private */ this.lastOffset = 0 } /** * @param {number | null} startOffset * @param {number | null} endOffset * @returns {{ startOffset: number | null, endOffset: number | null }} */ map (startOffset: number | null, endOffset:number | null): { startOffset: number | null, endOffset: number | null } { if (startOffset === null) { return { startOffset, endOffset } } // 何行目かを判定 const tokenLine = this.getLineNumber(startOffset) for (const insertedLine of this.linesInsertedByIndentationSyntax) { // インデント構文の処理後のソースコードの `insertedLine` 行目にあるトークンのソースマップ情報を削除する。 if (tokenLine === insertedLine) { startOffset = null endOffset = null break } // インデント構文の処理後のソースコードの `insertedLine` 行目以降にあるトークンのoffsetから // `linesInsertedByIndentationSyntax[i]` 行目の文字数(\rを含む) を引く。 if (tokenLine > insertedLine) { // "\n"の分1足す startOffset -= this.lines[insertedLine].len + 1 if (endOffset !== null) { endOffset -= this.lines[insertedLine].len + 1 } } } for (const deletedLine of this.linesDeletedByIndentationSyntax) { if (tokenLine >= deletedLine.lineNumber) { // "\n"の分1足す if (startOffset !== null) { startOffset += deletedLine.len + 1 } if (endOffset !== null) { endOffset += deletedLine.len + 1 } } } return { startOffset, endOffset } } /** * @param {number} offset * @returns {number} * @private */ getLineNumber (offset: number): number { // 連続アクセスに対する高速化 if (offset < this.lastOffset) { this.lastLineNumber = 0 } this.lastOffset = offset for (let i = this.lastLineNumber; i < this.lines.length - 1; i++) { if (offset < this.lines[i + 1].offset) { this.lastLineNumber = i return i } } this.lastLineNumber = this.lines.length - 1 return this.lines.length - 1 } } /** offsetから (line, column) へ変換する。 */ export class OffsetToLineColumn { private lineOffsets: number[] private lastLineNumber: number private lastOffset: number /** * @param {string} code */ constructor (code: string) { /** @private @type {number[]} */ this.lineOffsets = [] // 各行の先頭位置を先に計算しておく let offset = 0 for (const line of code.split('\n')) { this.lineOffsets.push(offset) offset += line.length + 1 } /** @private */ this.lastLineNumber = 0 /** @private */ this.lastOffset = 0 } /** * @param {number} offset * @param {boolean} oneBasedLineNumber trueのときlineを1から始める * @returns {{ line: number, column: number }} */ map (offset: number, oneBasedLineNumber: boolean):{ line: number, column: number } { // 連続アクセスに対する高速化 if (offset < this.lastOffset) { this.lastLineNumber = 0 } this.lastOffset = offset for (let i = this.lastLineNumber; i < this.lineOffsets.length - 1; i++) { if (offset < this.lineOffsets[i + 1]) { this.lastLineNumber = i return { line: i + (oneBasedLineNumber ? 1 : 0), column: offset - this.lineOffsets[i] } } } this.lastLineNumber = this.lineOffsets.length - 1 return { line: this.lineOffsets.length - 1 + (oneBasedLineNumber ? 1 : 0), column: offset - this.lineOffsets[this.lineOffsets.length - 1] } } } /** * preCodeの分、ソースマップのoffset、行数、列数を減らす。 * @type {<T extends {line?: number, column?: number, startOffset: number | null, endOffset: number | null }>(sourceMap: T, preCode: string) => T} */ export function subtractSourceMapByPreCodeLength (sourceMap: any, preCode: string) { // offsetは単純に引くだけでよい if (typeof sourceMap.startOffset === 'number') { sourceMap.startOffset -= preCode.length } if (typeof sourceMap.endOffset === 'number') { sourceMap.endOffset -= preCode.length } // たとえば preCode = 'abc\ndef\nghi' のとき、line -= 2 して、先頭行なら column -= 3 もする。 if (preCode !== '') { const lines = preCode.split('\n') if (typeof sourceMap.line === 'number') { sourceMap.line -= lines.length - 1 } if (sourceMap.line === 0 && typeof sourceMap.column === 'number') { sourceMap.column -= lines[lines.length - 1].length } } return sourceMap }