markz

Positions

Offsets are what the AST stores; editors and error messages want lines and columns. position builds a table of line starts once, then converts each offset by binary search. Lines are 1-based and columns 0-based in UTF-16 code units, visdown's convention and source-map v3's. CRLF, LF and a lone CR each end a line, as they do for the parser. An offset past the end is clamped to the end.

export function position(source: string): (offset: number) => Position {
  const starts = [0];
  for (let i = 0; i < source.length; i++) {
    const c = source.charCodeAt(i);
    if (c === 10 || (c === 13 && source.charCodeAt(i + 1) !== 10)) starts.push(i + 1);
  }
  return (offset) => {
    const at = Math.max(0, Math.min(offset, source.length));
    let low = 0;
    let high = starts.length - 1;
    while (low < high) {
      const mid = (low + high + 1) >> 1;
      if (starts[mid]! <= at) low = mid;
      else high = mid - 1;
    }
    return { line: low + 1, column: at - starts[low]! };
  };
}

/** A line, from 1, and a column, from 0, in UTF-16 code units. */
export interface Position {
  line: number;
  column: number;
}