Skip to main content
Module

x/ohm_js/src/util.js

A library and language for building parsers, interpreters, compilers, etc.
Go to Latest
File
'use strict';
// --------------------------------------------------------------------// Imports// --------------------------------------------------------------------
const common = require('./common');
// --------------------------------------------------------------------// Private stuff// --------------------------------------------------------------------
// Given an array of numbers `arr`, return an array of the numbers as strings,// right-justified and padded to the same length.function padNumbersToEqualLength(arr) { let maxLen = 0; const strings = arr.map(n => { const str = n.toString(); maxLen = Math.max(maxLen, str.length); return str; }); return strings.map(s => common.padLeft(s, maxLen));}
// Produce a new string that would be the result of copying the contents// of the string `src` onto `dest` at offset `offest`.function strcpy(dest, src, offset) { const origDestLen = dest.length; const start = dest.slice(0, offset); const end = dest.slice(offset + src.length); return (start + src + end).substr(0, origDestLen);}
// Casts the underlying lineAndCol object to a formatted message string,// highlighting `ranges`.function lineAndColumnToMessage(...ranges) { const lineAndCol = this; const {offset} = lineAndCol; const {repeatStr} = common;
const sb = new common.StringBuffer(); sb.append('Line ' + lineAndCol.lineNum + ', col ' + lineAndCol.colNum + ':\n');
// An array of the previous, current, and next line numbers as strings of equal length. const lineNumbers = padNumbersToEqualLength([ lineAndCol.prevLine == null ? 0 : lineAndCol.lineNum - 1, lineAndCol.lineNum, lineAndCol.nextLine == null ? 0 : lineAndCol.lineNum + 1, ]);
// Helper for appending formatting input lines to the buffer. const appendLine = (num, content, prefix) => { sb.append(prefix + lineNumbers[num] + ' | ' + content + '\n'); };
// Include the previous line for context if possible. if (lineAndCol.prevLine != null) { appendLine(0, lineAndCol.prevLine, ' '); } // Line that the error occurred on. appendLine(1, lineAndCol.line, '> ');
// Build up the line that points to the offset and possible indicates one or more ranges. // Start with a blank line, and indicate each range by overlaying a string of `~` chars. const lineLen = lineAndCol.line.length; let indicationLine = repeatStr(' ', lineLen + 1); for (let i = 0; i < ranges.length; ++i) { let startIdx = ranges[i][0]; let endIdx = ranges[i][1]; common.assert(startIdx >= 0 && startIdx <= endIdx, 'range start must be >= 0 and <= end');
const lineStartOffset = offset - lineAndCol.colNum + 1; startIdx = Math.max(0, startIdx - lineStartOffset); endIdx = Math.min(endIdx - lineStartOffset, lineLen);
indicationLine = strcpy(indicationLine, repeatStr('~', endIdx - startIdx), startIdx); } const gutterWidth = 2 + lineNumbers[1].length + 3; sb.append(repeatStr(' ', gutterWidth)); indicationLine = strcpy(indicationLine, '^', lineAndCol.colNum - 1); sb.append(indicationLine.replace(/ +$/, '') + '\n');
// Include the next line for context if possible. if (lineAndCol.nextLine != null) { appendLine(2, lineAndCol.nextLine, ' '); } return sb.contents();}
// --------------------------------------------------------------------// Exports// --------------------------------------------------------------------
let builtInRulesCallbacks = [];
// Since Grammar.BuiltInRules is bootstrapped, most of Ohm can't directly depend it.// This function allows modules that do depend on the built-in rules to register a callback// that will be called later in the initialization process.exports.awaitBuiltInRules = cb => { builtInRulesCallbacks.push(cb);};
exports.announceBuiltInRules = grammar => { builtInRulesCallbacks.forEach(cb => { cb(grammar); }); builtInRulesCallbacks = null;};
// Return an object with the line and column information for the given// offset in `str`.exports.getLineAndColumn = (str, offset) => { let lineNum = 1; let colNum = 1;
let currOffset = 0; let lineStartOffset = 0;
let nextLine = null; let prevLine = null; let prevLineStartOffset = -1;
while (currOffset < offset) { const c = str.charAt(currOffset++); if (c === '\n') { lineNum++; colNum = 1; prevLineStartOffset = lineStartOffset; lineStartOffset = currOffset; } else if (c !== '\r') { colNum++; } }
// Find the end of the target line. let lineEndOffset = str.indexOf('\n', lineStartOffset); if (lineEndOffset === -1) { lineEndOffset = str.length; } else { // Get the next line. const nextLineEndOffset = str.indexOf('\n', lineEndOffset + 1); nextLine = nextLineEndOffset === -1 ? str.slice(lineEndOffset) : str.slice(lineEndOffset, nextLineEndOffset); // Strip leading and trailing EOL char(s). nextLine = nextLine.replace(/^\r?\n/, '').replace(/\r$/, ''); }
// Get the previous line. if (prevLineStartOffset >= 0) { // Strip trailing EOL char(s). prevLine = str.slice(prevLineStartOffset, lineStartOffset).replace(/\r?\n$/, ''); }
// Get the target line, stripping a trailing carriage return if necessary. const line = str.slice(lineStartOffset, lineEndOffset).replace(/\r$/, '');
return { offset, lineNum, colNum, line, prevLine, nextLine, toString: lineAndColumnToMessage, };};
// Return a nicely-formatted string describing the line and column for the// given offset in `str` highlighting `ranges`.exports.getLineAndColumnMessage = function(str, offset, ...ranges) { return exports.getLineAndColumn(str, offset).toString(...ranges);};
exports.uniqueId = (() => { let idCounter = 0; return prefix => '' + prefix + idCounter++;})();