UNPKG

awoken-bible-reference

Version:

Bible verse reference parser, generator and manipulator

4 lines 226 kB
{ "version": 3, "sources": ["../node_modules/parsimmon/src/parsimmon.js", "../src/vidx.ts", "../src/geometry.ts", "../src/range-manip.ts", "../src/Versification.ts", "../src/printer.ts", "../src/validate.ts", "../src/parser.ts", "../src/parser-url.ts", "../src/lib.ts", "../src/index.ts"], "sourcesContent": ["\"use strict\";\n\nfunction Parsimmon(action) {\n if (!(this instanceof Parsimmon)) {\n return new Parsimmon(action);\n }\n this._ = action;\n}\n\nvar _ = Parsimmon.prototype;\n\nfunction times(n, f) {\n var i = 0;\n for (i; i < n; i++) {\n f(i);\n }\n}\n\nfunction forEach(f, arr) {\n times(arr.length, function(i) {\n f(arr[i], i, arr);\n });\n}\n\nfunction reduce(f, seed, arr) {\n forEach(function(elem, i, arr) {\n seed = f(seed, elem, i, arr);\n }, arr);\n return seed;\n}\n\nfunction map(f, arr) {\n return reduce(\n function(acc, elem, i, a) {\n return acc.concat([f(elem, i, a)]);\n },\n [],\n arr\n );\n}\n\nfunction lshiftBuffer(input) {\n var asTwoBytes = reduce(\n function(a, v, i, b) {\n return a.concat(\n i === b.length - 1\n ? Buffer.from([v, 0]).readUInt16BE(0)\n : b.readUInt16BE(i)\n );\n },\n [],\n input\n );\n return Buffer.from(\n map(function(x) {\n return ((x << 1) & 0xffff) >> 8;\n }, asTwoBytes)\n );\n}\n\nfunction consumeBitsFromBuffer(n, input) {\n var state = { v: 0, buf: input };\n times(n, function() {\n state = {\n v: (state.v << 1) | bitPeekBuffer(state.buf),\n buf: lshiftBuffer(state.buf)\n };\n });\n return state;\n}\n\nfunction bitPeekBuffer(input) {\n return input[0] >> 7;\n}\n\nfunction sum(numArr) {\n return reduce(\n function(x, y) {\n return x + y;\n },\n 0,\n numArr\n );\n}\n\nfunction find(pred, arr) {\n return reduce(\n function(found, elem) {\n return found || (pred(elem) ? elem : found);\n },\n null,\n arr\n );\n}\n\nfunction bufferExists() {\n return typeof Buffer !== \"undefined\";\n}\n\nfunction setExists() {\n if (Parsimmon._supportsSet !== undefined) {\n return Parsimmon._supportsSet;\n }\n var exists = typeof Set !== \"undefined\";\n Parsimmon._supportsSet = exists;\n return exists;\n}\n\nfunction ensureBuffer() {\n if (!bufferExists()) {\n throw new Error(\n \"Buffer global does not exist; please use webpack if you need to parse Buffers in the browser.\"\n );\n }\n}\n\nfunction bitSeq(alignments) {\n ensureBuffer();\n var totalBits = sum(alignments);\n if (totalBits % 8 !== 0) {\n throw new Error(\n \"The bits [\" +\n alignments.join(\", \") +\n \"] add up to \" +\n totalBits +\n \" which is not an even number of bytes; the total should be divisible by 8\"\n );\n }\n var bytes = totalBits / 8;\n\n var tooBigRange = find(function(x) {\n return x > 48;\n }, alignments);\n if (tooBigRange) {\n throw new Error(\n tooBigRange + \" bit range requested exceeds 48 bit (6 byte) Number max.\"\n );\n }\n\n return new Parsimmon(function(input, i) {\n var newPos = bytes + i;\n if (newPos > input.length) {\n return makeFailure(i, bytes.toString() + \" bytes\");\n }\n return makeSuccess(\n newPos,\n reduce(\n function(acc, bits) {\n var state = consumeBitsFromBuffer(bits, acc.buf);\n return {\n coll: acc.coll.concat(state.v),\n buf: state.buf\n };\n },\n { coll: [], buf: input.slice(i, newPos) },\n alignments\n ).coll\n );\n });\n}\n\nfunction bitSeqObj(namedAlignments) {\n ensureBuffer();\n var seenKeys = {};\n var totalKeys = 0;\n var fullAlignments = map(function(item) {\n if (isArray(item)) {\n var pair = item;\n if (pair.length !== 2) {\n throw new Error(\n \"[\" +\n pair.join(\", \") +\n \"] should be length 2, got length \" +\n pair.length\n );\n }\n assertString(pair[0]);\n assertNumber(pair[1]);\n if (Object.prototype.hasOwnProperty.call(seenKeys, pair[0])) {\n throw new Error(\"duplicate key in bitSeqObj: \" + pair[0]);\n }\n seenKeys[pair[0]] = true;\n totalKeys++;\n return pair;\n } else {\n assertNumber(item);\n return [null, item];\n }\n }, namedAlignments);\n if (totalKeys < 1) {\n throw new Error(\n \"bitSeqObj expects at least one named pair, got [\" +\n namedAlignments.join(\", \") +\n \"]\"\n );\n }\n var namesOnly = map(function(pair) {\n return pair[0];\n }, fullAlignments);\n var alignmentsOnly = map(function(pair) {\n return pair[1];\n }, fullAlignments);\n\n return bitSeq(alignmentsOnly).map(function(parsed) {\n var namedParsed = map(function(name, i) {\n return [name, parsed[i]];\n }, namesOnly);\n\n return reduce(\n function(obj, kv) {\n if (kv[0] !== null) {\n obj[kv[0]] = kv[1];\n }\n return obj;\n },\n {},\n namedParsed\n );\n });\n}\n\nfunction parseBufferFor(other, length) {\n return new Parsimmon(function(input, i) {\n ensureBuffer();\n if (i + length > input.length) {\n return makeFailure(i, length + \" bytes for \" + other);\n }\n return makeSuccess(i + length, input.slice(i, i + length));\n });\n}\n\nfunction parseBuffer(length) {\n return parseBufferFor(\"buffer\", length).map(function(unsafe) {\n return Buffer.from(unsafe);\n });\n}\n\nfunction encodedString(encoding, length) {\n return parseBufferFor(\"string\", length).map(function(buff) {\n return buff.toString(encoding);\n });\n}\n\nfunction isInteger(value) {\n return typeof value === \"number\" && Math.floor(value) === value;\n}\n\nfunction assertValidIntegerByteLengthFor(who, length) {\n if (!isInteger(length) || length < 0 || length > 6) {\n throw new Error(who + \" requires integer length in range [0, 6].\");\n }\n}\n\nfunction uintBE(length) {\n assertValidIntegerByteLengthFor(\"uintBE\", length);\n return parseBufferFor(\"uintBE(\" + length + \")\", length).map(function(buff) {\n return buff.readUIntBE(0, length);\n });\n}\n\nfunction uintLE(length) {\n assertValidIntegerByteLengthFor(\"uintLE\", length);\n return parseBufferFor(\"uintLE(\" + length + \")\", length).map(function(buff) {\n return buff.readUIntLE(0, length);\n });\n}\n\nfunction intBE(length) {\n assertValidIntegerByteLengthFor(\"intBE\", length);\n return parseBufferFor(\"intBE(\" + length + \")\", length).map(function(buff) {\n return buff.readIntBE(0, length);\n });\n}\n\nfunction intLE(length) {\n assertValidIntegerByteLengthFor(\"intLE\", length);\n return parseBufferFor(\"intLE(\" + length + \")\", length).map(function(buff) {\n return buff.readIntLE(0, length);\n });\n}\n\nfunction floatBE() {\n return parseBufferFor(\"floatBE\", 4).map(function(buff) {\n return buff.readFloatBE(0);\n });\n}\n\nfunction floatLE() {\n return parseBufferFor(\"floatLE\", 4).map(function(buff) {\n return buff.readFloatLE(0);\n });\n}\n\nfunction doubleBE() {\n return parseBufferFor(\"doubleBE\", 8).map(function(buff) {\n return buff.readDoubleBE(0);\n });\n}\n\nfunction doubleLE() {\n return parseBufferFor(\"doubleLE\", 8).map(function(buff) {\n return buff.readDoubleLE(0);\n });\n}\n\nfunction toArray(arrLike) {\n return Array.prototype.slice.call(arrLike);\n}\n// -*- Helpers -*-\n\nfunction isParser(obj) {\n return obj instanceof Parsimmon;\n}\n\nfunction isArray(x) {\n return {}.toString.call(x) === \"[object Array]\";\n}\n\nfunction isBuffer(x) {\n /* global Buffer */\n return bufferExists() && Buffer.isBuffer(x);\n}\n\nfunction makeSuccess(index, value) {\n return {\n status: true,\n index: index,\n value: value,\n furthest: -1,\n expected: []\n };\n}\n\nfunction makeFailure(index, expected) {\n if (!isArray(expected)) {\n expected = [expected];\n }\n return {\n status: false,\n index: -1,\n value: null,\n furthest: index,\n expected: expected\n };\n}\n\nfunction mergeReplies(result, last) {\n if (!last) {\n return result;\n }\n if (result.furthest > last.furthest) {\n return result;\n }\n var expected =\n result.furthest === last.furthest\n ? union(result.expected, last.expected)\n : last.expected;\n return {\n status: result.status,\n index: result.index,\n value: result.value,\n furthest: last.furthest,\n expected: expected\n };\n}\n\n// index of { input => { index => { lineNumber, startOfLine } } }\n// when we see a new index we just walk backwards to the last seen index and\n// compute the new lineNumber and startOfLine from there so we don't have to\n// recompute from the whole input\nvar lineColumnIndex = {};\nfunction makeLineColumnIndex(input, i) {\n if (isBuffer(input)) {\n return {\n offset: i,\n line: -1,\n column: -1\n };\n }\n\n // initialize if we haven't seen this input yet\n if (!(input in lineColumnIndex)) {\n lineColumnIndex[input] = {};\n }\n\n var inputIndex = lineColumnIndex[input];\n\n var prevLine = 0;\n var newLines = 0;\n var lineStart = 0;\n var j = i;\n while (j >= 0) {\n if (j in inputIndex) {\n prevLine = inputIndex[j].line;\n // lineStart === 0 when we haven't found a new line on the walk\n // back from i, so we are on the same line as the previously cached\n // index\n if (lineStart === 0) {\n lineStart = inputIndex[j].lineStart;\n }\n break;\n }\n\n if (\n // Unix LF (\\n) or Windows CRLF (\\r\\n) line ending\n input.charAt(j) === \"\\n\" ||\n // Old Mac CR (\\r) line ending\n (input.charAt(j) === \"\\r\" && input.charAt(j + 1) !== \"\\n\")\n ) {\n newLines++;\n // lineStart === 0 when this is the first new line we have found\n if (lineStart === 0) {\n lineStart = j + 1;\n }\n }\n j--;\n }\n\n var lineWeAreUpTo = prevLine + newLines;\n var columnWeAreUpTo = i - lineStart;\n\n inputIndex[i] = { line: lineWeAreUpTo, lineStart: lineStart };\n\n // lines and columns are 1-indexed\n return {\n offset: i,\n line: lineWeAreUpTo + 1,\n column: columnWeAreUpTo + 1\n };\n}\n\n// Returns the sorted set union of two arrays of strings\nfunction union(xs, ys) {\n // for newer browsers/node we can improve performance by using\n // modern JS\n if (setExists() && Array.from) {\n // eslint-disable-next-line no-undef\n var set = new Set(xs);\n for (var y = 0; y < ys.length; y++) {\n set.add(ys[y]);\n }\n var arr = Array.from(set);\n arr.sort();\n return arr;\n }\n var obj = {};\n for (var i = 0; i < xs.length; i++) {\n obj[xs[i]] = true;\n }\n for (var j = 0; j < ys.length; j++) {\n obj[ys[j]] = true;\n }\n var keys = [];\n for (var k in obj) {\n if ({}.hasOwnProperty.call(obj, k)) {\n keys.push(k);\n }\n }\n keys.sort();\n return keys;\n}\n\nfunction assertParser(p) {\n if (!isParser(p)) {\n throw new Error(\"not a parser: \" + p);\n }\n}\n\nfunction get(input, i) {\n if (typeof input === \"string\") {\n return input.charAt(i);\n }\n return input[i];\n}\n\n// TODO[ES5]: Switch to Array.isArray eventually.\nfunction assertArray(x) {\n if (!isArray(x)) {\n throw new Error(\"not an array: \" + x);\n }\n}\n\nfunction assertNumber(x) {\n if (typeof x !== \"number\") {\n throw new Error(\"not a number: \" + x);\n }\n}\n\nfunction assertRegexp(x) {\n if (!(x instanceof RegExp)) {\n throw new Error(\"not a regexp: \" + x);\n }\n var f = flags(x);\n for (var i = 0; i < f.length; i++) {\n var c = f.charAt(i);\n // Only allow regexp flags [imus] for now, since [g] and [y] specifically\n // mess up Parsimmon. If more non-stateful regexp flags are added in the\n // future, this will need to be revisited.\n if (c !== \"i\" && c !== \"m\" && c !== \"u\" && c !== \"s\") {\n throw new Error('unsupported regexp flag \"' + c + '\": ' + x);\n }\n }\n}\n\nfunction assertFunction(x) {\n if (typeof x !== \"function\") {\n throw new Error(\"not a function: \" + x);\n }\n}\n\nfunction assertString(x) {\n if (typeof x !== \"string\") {\n throw new Error(\"not a string: \" + x);\n }\n}\n\n// -*- Error Formatting -*-\n\nvar linesBeforeStringError = 2;\nvar linesAfterStringError = 3;\nvar bytesPerLine = 8;\nvar bytesBefore = bytesPerLine * 5;\nvar bytesAfter = bytesPerLine * 4;\nvar defaultLinePrefix = \" \";\n\nfunction repeat(string, amount) {\n return new Array(amount + 1).join(string);\n}\n\nfunction formatExpected(expected) {\n if (expected.length === 1) {\n return \"Expected:\\n\\n\" + expected[0];\n }\n return \"Expected one of the following: \\n\\n\" + expected.join(\", \");\n}\n\nfunction leftPad(str, pad, char) {\n var add = pad - str.length;\n if (add <= 0) {\n return str;\n }\n return repeat(char, add) + str;\n}\n\nfunction toChunks(arr, chunkSize) {\n var length = arr.length;\n var chunks = [];\n var chunkIndex = 0;\n\n if (length <= chunkSize) {\n return [arr.slice()];\n }\n\n for (var i = 0; i < length; i++) {\n if (!chunks[chunkIndex]) {\n chunks.push([]);\n }\n\n chunks[chunkIndex].push(arr[i]);\n\n if ((i + 1) % chunkSize === 0) {\n chunkIndex++;\n }\n }\n\n return chunks;\n}\n\n// Get a range of indexes including `i`-th element and `before` and `after` amount of elements from `arr`.\nfunction rangeFromIndexAndOffsets(i, before, after, length) {\n return {\n // Guard against the negative upper bound for lines included in the output.\n from: i - before > 0 ? i - before : 0,\n to: i + after > length ? length : i + after\n };\n}\n\nfunction byteRangeToRange(byteRange) {\n // Exception for inputs smaller than `bytesPerLine`\n if (byteRange.from === 0 && byteRange.to === 1) {\n return {\n from: byteRange.from,\n to: byteRange.to\n };\n }\n\n return {\n from: byteRange.from / bytesPerLine,\n // Round `to`, so we don't get float if the amount of bytes is not divisible by `bytesPerLine`\n to: Math.floor(byteRange.to / bytesPerLine)\n };\n}\n\nfunction formatGot(input, error) {\n var index = error.index;\n var i = index.offset;\n\n var verticalMarkerLength = 1;\n var column;\n var lineWithErrorIndex;\n var lines;\n var lineRange;\n var lastLineNumberLabelLength;\n\n if (i === input.length) {\n return \"Got the end of the input\";\n }\n\n if (isBuffer(input)) {\n var byteLineWithErrorIndex = i - (i % bytesPerLine);\n var columnByteIndex = i - byteLineWithErrorIndex;\n var byteRange = rangeFromIndexAndOffsets(\n byteLineWithErrorIndex,\n bytesBefore,\n bytesAfter + bytesPerLine,\n input.length\n );\n var bytes = input.slice(byteRange.from, byteRange.to);\n var bytesInChunks = toChunks(bytes.toJSON().data, bytesPerLine);\n\n var byteLines = map(function(byteRow) {\n return map(function(byteValue) {\n // Prefix byte values with a `0` if they are shorter than 2 characters.\n return leftPad(byteValue.toString(16), 2, \"0\");\n }, byteRow);\n }, bytesInChunks);\n\n lineRange = byteRangeToRange(byteRange);\n lineWithErrorIndex = byteLineWithErrorIndex / bytesPerLine;\n column = columnByteIndex * 3;\n\n // Account for an extra space.\n if (columnByteIndex >= 4) {\n column += 1;\n }\n\n verticalMarkerLength = 2;\n lines = map(function(byteLine) {\n return byteLine.length <= 4\n ? byteLine.join(\" \")\n : byteLine.slice(0, 4).join(\" \") + \" \" + byteLine.slice(4).join(\" \");\n }, byteLines);\n lastLineNumberLabelLength = (\n (lineRange.to > 0 ? lineRange.to - 1 : lineRange.to) * 8\n ).toString(16).length;\n\n if (lastLineNumberLabelLength < 2) {\n lastLineNumberLabelLength = 2;\n }\n } else {\n var inputLines = input.split(/\\r\\n|[\\n\\r\\u2028\\u2029]/);\n column = index.column - 1;\n lineWithErrorIndex = index.line - 1;\n lineRange = rangeFromIndexAndOffsets(\n lineWithErrorIndex,\n linesBeforeStringError,\n linesAfterStringError,\n inputLines.length\n );\n\n lines = inputLines.slice(lineRange.from, lineRange.to);\n lastLineNumberLabelLength = lineRange.to.toString().length;\n }\n\n var lineWithErrorCurrentIndex = lineWithErrorIndex - lineRange.from;\n\n if (isBuffer(input)) {\n lastLineNumberLabelLength = (\n (lineRange.to > 0 ? lineRange.to - 1 : lineRange.to) * 8\n ).toString(16).length;\n\n if (lastLineNumberLabelLength < 2) {\n lastLineNumberLabelLength = 2;\n }\n }\n\n var linesWithLineNumbers = reduce(\n function(acc, lineSource, index) {\n var isLineWithError = index === lineWithErrorCurrentIndex;\n var prefix = isLineWithError ? \"> \" : defaultLinePrefix;\n var lineNumberLabel;\n\n if (isBuffer(input)) {\n lineNumberLabel = leftPad(\n ((lineRange.from + index) * 8).toString(16),\n lastLineNumberLabelLength,\n \"0\"\n );\n } else {\n lineNumberLabel = leftPad(\n (lineRange.from + index + 1).toString(),\n lastLineNumberLabelLength,\n \" \"\n );\n }\n\n return [].concat(\n acc,\n [prefix + lineNumberLabel + \" | \" + lineSource],\n isLineWithError\n ? [\n defaultLinePrefix +\n repeat(\" \", lastLineNumberLabelLength) +\n \" | \" +\n leftPad(\"\", column, \" \") +\n repeat(\"^\", verticalMarkerLength)\n ]\n : []\n );\n },\n [],\n lines\n );\n\n return linesWithLineNumbers.join(\"\\n\");\n}\n\nfunction formatError(input, error) {\n return [\n \"\\n\",\n \"-- PARSING FAILED \" + repeat(\"-\", 50),\n \"\\n\\n\",\n formatGot(input, error),\n \"\\n\\n\",\n formatExpected(error.expected),\n \"\\n\"\n ].join(\"\");\n}\n\nfunction flags(re) {\n if (re.flags !== undefined) {\n return re.flags;\n }\n // legacy browser support\n return [\n re.global ? \"g\" : \"\",\n re.ignoreCase ? \"i\" : \"\",\n re.multiline ? \"m\" : \"\",\n re.unicode ? \"u\" : \"\",\n re.sticky ? \"y\" : \"\"\n ].join(\"\");\n}\n\nfunction anchoredRegexp(re) {\n return RegExp(\"^(?:\" + re.source + \")\", flags(re));\n}\n\n// -*- Combinators -*-\n\nfunction seq() {\n var parsers = [].slice.call(arguments);\n var numParsers = parsers.length;\n for (var j = 0; j < numParsers; j += 1) {\n assertParser(parsers[j]);\n }\n return Parsimmon(function(input, i) {\n var result;\n var accum = new Array(numParsers);\n for (var j = 0; j < numParsers; j += 1) {\n result = mergeReplies(parsers[j]._(input, i), result);\n if (!result.status) {\n return result;\n }\n accum[j] = result.value;\n i = result.index;\n }\n return mergeReplies(makeSuccess(i, accum), result);\n });\n}\n\nfunction seqObj() {\n var seenKeys = {};\n var totalKeys = 0;\n var parsers = toArray(arguments);\n var numParsers = parsers.length;\n for (var j = 0; j < numParsers; j += 1) {\n var p = parsers[j];\n if (isParser(p)) {\n continue;\n }\n if (isArray(p)) {\n var isWellFormed =\n p.length === 2 && typeof p[0] === \"string\" && isParser(p[1]);\n if (isWellFormed) {\n var key = p[0];\n if (Object.prototype.hasOwnProperty.call(seenKeys, key)) {\n throw new Error(\"seqObj: duplicate key \" + key);\n }\n seenKeys[key] = true;\n totalKeys++;\n continue;\n }\n }\n throw new Error(\n \"seqObj arguments must be parsers or [string, parser] array pairs.\"\n );\n }\n if (totalKeys === 0) {\n throw new Error(\"seqObj expects at least one named parser, found zero\");\n }\n return Parsimmon(function(input, i) {\n var result;\n var accum = {};\n for (var j = 0; j < numParsers; j += 1) {\n var name;\n var parser;\n if (isArray(parsers[j])) {\n name = parsers[j][0];\n parser = parsers[j][1];\n } else {\n name = null;\n parser = parsers[j];\n }\n result = mergeReplies(parser._(input, i), result);\n if (!result.status) {\n return result;\n }\n if (name) {\n accum[name] = result.value;\n }\n i = result.index;\n }\n return mergeReplies(makeSuccess(i, accum), result);\n });\n}\n\nfunction seqMap() {\n var args = [].slice.call(arguments);\n if (args.length === 0) {\n throw new Error(\"seqMap needs at least one argument\");\n }\n var mapper = args.pop();\n assertFunction(mapper);\n return seq.apply(null, args).map(function(results) {\n return mapper.apply(null, results);\n });\n}\n\n// TODO[ES5]: Revisit this with Object.keys and .bind.\nfunction createLanguage(parsers) {\n var language = {};\n for (var key in parsers) {\n if ({}.hasOwnProperty.call(parsers, key)) {\n (function(key) {\n var func = function() {\n return parsers[key](language);\n };\n language[key] = lazy(func);\n })(key);\n }\n }\n return language;\n}\n\nfunction alt() {\n var parsers = [].slice.call(arguments);\n var numParsers = parsers.length;\n if (numParsers === 0) {\n return fail(\"zero alternates\");\n }\n for (var j = 0; j < numParsers; j += 1) {\n assertParser(parsers[j]);\n }\n return Parsimmon(function(input, i) {\n var result;\n for (var j = 0; j < parsers.length; j += 1) {\n result = mergeReplies(parsers[j]._(input, i), result);\n if (result.status) {\n return result;\n }\n }\n return result;\n });\n}\n\nfunction sepBy(parser, separator) {\n // Argument asserted by sepBy1\n return sepBy1(parser, separator).or(succeed([]));\n}\n\nfunction sepBy1(parser, separator) {\n assertParser(parser);\n assertParser(separator);\n var pairs = separator.then(parser).many();\n return seqMap(parser, pairs, function(r, rs) {\n return [r].concat(rs);\n });\n}\n\n// -*- Core Parsing Methods -*-\n\n_.parse = function(input) {\n if (typeof input !== \"string\" && !isBuffer(input)) {\n throw new Error(\n \".parse must be called with a string or Buffer as its argument\"\n );\n }\n var parseResult = this.skip(eof)._(input, 0);\n\n var result;\n if (parseResult.status) {\n result = {\n status: true,\n value: parseResult.value\n };\n } else {\n result = {\n status: false,\n index: makeLineColumnIndex(input, parseResult.furthest),\n expected: parseResult.expected\n };\n }\n\n // release memory from lineColumnIndex now we are done parsing\n delete lineColumnIndex[input];\n\n return result;\n};\n\n// -*- Other Methods -*-\n\n_.tryParse = function(str) {\n var result = this.parse(str);\n if (result.status) {\n return result.value;\n } else {\n var msg = formatError(str, result);\n var err = new Error(msg);\n err.type = \"ParsimmonError\";\n err.result = result;\n throw err;\n }\n};\n\n_.assert = function(condition, errorMessage) {\n return this.chain(function(value) {\n return condition(value) ? succeed(value) : fail(errorMessage);\n });\n};\n\n_.or = function(alternative) {\n return alt(this, alternative);\n};\n\n_.trim = function(parser) {\n return this.wrap(parser, parser);\n};\n\n_.wrap = function(leftParser, rightParser) {\n return seqMap(leftParser, this, rightParser, function(left, middle) {\n return middle;\n });\n};\n\n_.thru = function(wrapper) {\n return wrapper(this);\n};\n\n_.then = function(next) {\n assertParser(next);\n return seq(this, next).map(function(results) {\n return results[1];\n });\n};\n\n_.many = function() {\n var self = this;\n\n return Parsimmon(function(input, i) {\n var accum = [];\n var result = undefined;\n\n for (;;) {\n result = mergeReplies(self._(input, i), result);\n if (result.status) {\n if (i === result.index) {\n throw new Error(\n \"infinite loop detected in .many() parser --- calling .many() on \" +\n \"a parser which can accept zero characters is usually the cause\"\n );\n }\n i = result.index;\n accum.push(result.value);\n } else {\n return mergeReplies(makeSuccess(i, accum), result);\n }\n }\n });\n};\n\n_.tieWith = function(separator) {\n assertString(separator);\n return this.map(function(args) {\n assertArray(args);\n if (args.length) {\n assertString(args[0]);\n var s = args[0];\n for (var i = 1; i < args.length; i++) {\n assertString(args[i]);\n s += separator + args[i];\n }\n return s;\n } else {\n return \"\";\n }\n });\n};\n\n_.tie = function() {\n return this.tieWith(\"\");\n};\n\n_.times = function(min, max) {\n var self = this;\n if (arguments.length < 2) {\n max = min;\n }\n assertNumber(min);\n assertNumber(max);\n return Parsimmon(function(input, i) {\n var accum = [];\n var result = undefined;\n var prevResult = undefined;\n for (var times = 0; times < min; times += 1) {\n result = self._(input, i);\n prevResult = mergeReplies(result, prevResult);\n if (result.status) {\n i = result.index;\n accum.push(result.value);\n } else {\n return prevResult;\n }\n }\n for (; times < max; times += 1) {\n result = self._(input, i);\n prevResult = mergeReplies(result, prevResult);\n if (result.status) {\n i = result.index;\n accum.push(result.value);\n } else {\n break;\n }\n }\n return mergeReplies(makeSuccess(i, accum), prevResult);\n });\n};\n\n_.result = function(res) {\n return this.map(function() {\n return res;\n });\n};\n\n_.atMost = function(n) {\n return this.times(0, n);\n};\n\n_.atLeast = function(n) {\n return seqMap(this.times(n), this.many(), function(init, rest) {\n return init.concat(rest);\n });\n};\n\n_.map = function(fn) {\n assertFunction(fn);\n var self = this;\n return Parsimmon(function(input, i) {\n var result = self._(input, i);\n if (!result.status) {\n return result;\n }\n return mergeReplies(makeSuccess(result.index, fn(result.value)), result);\n });\n};\n\n_.contramap = function(fn) {\n assertFunction(fn);\n var self = this;\n return Parsimmon(function(input, i) {\n var result = self.parse(fn(input.slice(i)));\n if (!result.status) {\n return result;\n }\n return makeSuccess(i + input.length, result.value);\n });\n};\n\n_.promap = function(f, g) {\n assertFunction(f);\n assertFunction(g);\n return this.contramap(f).map(g);\n};\n\n_.skip = function(next) {\n return seq(this, next).map(function(results) {\n return results[0];\n });\n};\n\n_.mark = function() {\n return seqMap(index, this, index, function(start, value, end) {\n return {\n start: start,\n value: value,\n end: end\n };\n });\n};\n\n_.node = function(name) {\n return seqMap(index, this, index, function(start, value, end) {\n return {\n name: name,\n value: value,\n start: start,\n end: end\n };\n });\n};\n\n_.sepBy = function(separator) {\n return sepBy(this, separator);\n};\n\n_.sepBy1 = function(separator) {\n return sepBy1(this, separator);\n};\n\n_.lookahead = function(x) {\n return this.skip(lookahead(x));\n};\n\n_.notFollowedBy = function(x) {\n return this.skip(notFollowedBy(x));\n};\n\n_.desc = function(expected) {\n if (!isArray(expected)) {\n expected = [expected];\n }\n var self = this;\n return Parsimmon(function(input, i) {\n var reply = self._(input, i);\n if (!reply.status) {\n reply.expected = expected;\n }\n return reply;\n });\n};\n\n_.fallback = function(result) {\n return this.or(succeed(result));\n};\n\n_.ap = function(other) {\n return seqMap(other, this, function(f, x) {\n return f(x);\n });\n};\n\n_.chain = function(f) {\n var self = this;\n return Parsimmon(function(input, i) {\n var result = self._(input, i);\n if (!result.status) {\n return result;\n }\n var nextParser = f(result.value);\n return mergeReplies(nextParser._(input, result.index), result);\n });\n};\n\n// -*- Constructors -*-\n\nfunction string(str) {\n assertString(str);\n var expected = \"'\" + str + \"'\";\n return Parsimmon(function(input, i) {\n var j = i + str.length;\n var head = input.slice(i, j);\n if (head === str) {\n return makeSuccess(j, head);\n } else {\n return makeFailure(i, expected);\n }\n });\n}\n\nfunction byte(b) {\n ensureBuffer();\n assertNumber(b);\n if (b > 0xff) {\n throw new Error(\n \"Value specified to byte constructor (\" +\n b +\n \"=0x\" +\n b.toString(16) +\n \") is larger in value than a single byte.\"\n );\n }\n var expected = (b > 0xf ? \"0x\" : \"0x0\") + b.toString(16);\n return Parsimmon(function(input, i) {\n var head = get(input, i);\n if (head === b) {\n return makeSuccess(i + 1, head);\n } else {\n return makeFailure(i, expected);\n }\n });\n}\n\nfunction regexp(re, group) {\n assertRegexp(re);\n if (arguments.length >= 2) {\n assertNumber(group);\n } else {\n group = 0;\n }\n var anchored = anchoredRegexp(re);\n var expected = \"\" + re;\n return Parsimmon(function(input, i) {\n var match = anchored.exec(input.slice(i));\n if (match) {\n if (0 <= group && group <= match.length) {\n var fullMatch = match[0];\n var groupMatch = match[group];\n return makeSuccess(i + fullMatch.length, groupMatch);\n }\n var message =\n \"valid match group (0 to \" + match.length + \") in \" + expected;\n return makeFailure(i, message);\n }\n return makeFailure(i, expected);\n });\n}\n\nfunction succeed(value) {\n return Parsimmon(function(input, i) {\n return makeSuccess(i, value);\n });\n}\n\nfunction fail(expected) {\n return Parsimmon(function(input, i) {\n return makeFailure(i, expected);\n });\n}\n\nfunction lookahead(x) {\n if (isParser(x)) {\n return Parsimmon(function(input, i) {\n var result = x._(input, i);\n result.index = i;\n result.value = \"\";\n return result;\n });\n } else if (typeof x === \"string\") {\n return lookahead(string(x));\n } else if (x instanceof RegExp) {\n return lookahead(regexp(x));\n }\n throw new Error(\"not a string, regexp, or parser: \" + x);\n}\n\nfunction notFollowedBy(parser) {\n assertParser(parser);\n return Parsimmon(function(input, i) {\n var result = parser._(input, i);\n var text = input.slice(i, result.index);\n return result.status\n ? makeFailure(i, 'not \"' + text + '\"')\n : makeSuccess(i, null);\n });\n}\n\nfunction test(predicate) {\n assertFunction(predicate);\n return Parsimmon(function(input, i) {\n var char = get(input, i);\n if (i < input.length && predicate(char)) {\n return makeSuccess(i + 1, char);\n } else {\n return makeFailure(i, \"a character/byte matching \" + predicate);\n }\n });\n}\n\nfunction oneOf(str) {\n var expected = str.split(\"\");\n for (var idx = 0; idx < expected.length; idx++) {\n expected[idx] = \"'\" + expected[idx] + \"'\";\n }\n return test(function(ch) {\n return str.indexOf(ch) >= 0;\n }).desc(expected);\n}\n\nfunction noneOf(str) {\n return test(function(ch) {\n return str.indexOf(ch) < 0;\n }).desc(\"none of '\" + str + \"'\");\n}\n\nfunction custom(parsingFunction) {\n return Parsimmon(parsingFunction(makeSuccess, makeFailure));\n}\n\n// TODO[ES5]: Improve error message using JSON.stringify eventually.\nfunction range(begin, end) {\n return test(function(ch) {\n return begin <= ch && ch <= end;\n }).desc(begin + \"-\" + end);\n}\n\nfunction takeWhile(predicate) {\n assertFunction(predicate);\n\n return Parsimmon(function(input, i) {\n var j = i;\n while (j < input.length && predicate(get(input, j))) {\n j++;\n }\n return makeSuccess(j, input.slice(i, j));\n });\n}\n\nfunction lazy(desc, f) {\n if (arguments.length < 2) {\n f = desc;\n desc = undefined;\n }\n\n var parser = Parsimmon(function(input, i) {\n parser._ = f()._;\n return parser._(input, i);\n });\n\n if (desc) {\n return parser.desc(desc);\n } else {\n return parser;\n }\n}\n\n// -*- Fantasy Land Extras -*-\n\nfunction empty() {\n return fail(\"fantasy-land/empty\");\n}\n\n_.concat = _.or;\n_.empty = empty;\n_.of = succeed;\n_[\"fantasy-land/ap\"] = _.ap;\n_[\"fantasy-land/chain\"] = _.chain;\n_[\"fantasy-land/concat\"] = _.concat;\n_[\"fantasy-land/empty\"] = _.empty;\n_[\"fantasy-land/of\"] = _.of;\n_[\"fantasy-land/map\"] = _.map;\n\n// -*- Base Parsers -*-\n\nvar index = Parsimmon(function(input, i) {\n return makeSuccess(i, makeLineColumnIndex(input, i));\n});\n\nvar any = Parsimmon(function(input, i) {\n if (i >= input.length) {\n return makeFailure(i, \"any character/byte\");\n }\n return makeSuccess(i + 1, get(input, i));\n});\n\nvar all = Parsimmon(function(input, i) {\n return makeSuccess(input.length, input.slice(i));\n});\n\nvar eof = Parsimmon(function(input, i) {\n if (i < input.length) {\n return makeFailure(i, \"EOF\");\n }\n return makeSuccess(i, null);\n});\n\nvar digit = regexp(/[0-9]/).desc(\"a digit\");\nvar digits = regexp(/[0-9]*/).desc(\"optional digits\");\nvar letter = regexp(/[a-z]/i).desc(\"a letter\");\nvar letters = regexp(/[a-z]*/i).desc(\"optional letters\");\nvar optWhitespace = regexp(/\\s*/).desc(\"optional whitespace\");\nvar whitespace = regexp(/\\s+/).desc(\"whitespace\");\nvar cr = string(\"\\r\");\nvar lf = string(\"\\n\");\nvar crlf = string(\"\\r\\n\");\nvar newline = alt(crlf, lf, cr).desc(\"newline\");\nvar end = alt(newline, eof);\n\nParsimmon.all = all;\nParsimmon.alt = alt;\nParsimmon.any = any;\nParsimmon.cr = cr;\nParsimmon.createLanguage = createLanguage;\nParsimmon.crlf = crlf;\nParsimmon.custom = custom;\nParsimmon.digit = digit;\nParsimmon.digits = digits;\nParsimmon.empty = empty;\nParsimmon.end = end;\nParsimmon.eof = eof;\nParsimmon.fail = fail;\nParsimmon.formatError = formatError;\nParsimmon.index = index;\nParsimmon.isParser = isParser;\nParsimmon.lazy = lazy;\nParsimmon.letter = letter;\nParsimmon.letters = letters;\nParsimmon.lf = lf;\nParsimmon.lookahead = lookahead;\nParsimmon.makeFailure = makeFailure;\nParsimmon.makeSuccess = makeSuccess;\nParsimmon.newline = newline;\nParsimmon.noneOf = noneOf;\nParsimmon.notFollowedBy = notFollowedBy;\nParsimmon.of = succeed;\nParsimmon.oneOf = oneOf;\nParsimmon.optWhitespace = optWhitespace;\nParsimmon.Parser = Parsimmon;\nParsimmon.range = range;\nParsimmon.regex = regexp;\nParsimmon.regexp = regexp;\nParsimmon.sepBy = sepBy;\nParsimmon.sepBy1 = sepBy1;\nParsimmon.seq = seq;\nParsimmon.seqMap = seqMap;\nParsimmon.seqObj = seqObj;\nParsimmon.string = string;\nParsimmon.succeed = succeed;\nParsimmon.takeWhile = takeWhile;\nParsimmon.test = test;\nParsimmon.whitespace = whitespace;\nParsimmon[\"fantasy-land/empty\"] = empty;\nParsimmon[\"fantasy-land/of\"] = succeed;\n\nParsimmon.Binary = {\n bitSeq: bitSeq,\n bitSeqObj: bitSeqObj,\n byte: byte,\n buffer: parseBuffer,\n encodedString: encodedString,\n uintBE: uintBE,\n uint8BE: uintBE(1),\n uint16BE: uintBE(2),\n uint32BE: uintBE(4),\n uintLE: uintLE,\n uint8LE: uintLE(1),\n uint16LE: uintLE(2),\n uint32LE: uintLE(4),\n intBE: intBE,\n int8BE: intBE(1),\n int16BE: intBE(2),\n int32BE: intBE(4),\n intLE: intLE,\n int8LE: intLE(1),\n int16LE: intLE(2),\n int32LE: intLE(4),\n floatBE: floatBE(),\n floatLE: floatLE(),\n doubleBE: doubleBE(),\n doubleLE: doubleLE()\n};\n\nmodule.exports = Parsimmon;\n", "/**\n * Contains various utility functions related to converting BibleVerse's to and\n * from \"Verse Indexes\" (IE: VIDX)\n */\n\nimport { Versification } from './Versification';\nimport { BibleRef, BibleVerse, BibleRange } from './BibleRef';\n\n/**\n * Converts a BibleVerse to a \"Verse Index\" which would be the 0-based index of\n * the verse in an array contain all verse of the bible\n */\nexport function toVidx(versification: Versification, verse: BibleVerse) : number {\n\treturn versification.book[verse.book][verse.chapter].cumulative_verse + verse.verse - 1;\n}\n\nexport function fromVidx(versification: Versification, vidx: number) : BibleVerse {\n\t// First identify the book containing the CVID by linear search of the books\n\t// only 66 books so performance is fine\n\tlet b_idx;\n\tfor(b_idx = 1; b_idx < versification.order.length; ++b_idx){\n\t\tif(versification.order[b_idx].chapters[0].cumulative_verse > vidx){\n\t\t\tbreak;\n\t\t}\n\t}\n\t--b_idx;\n\n\tlet book = versification.order[b_idx];\n\n\t// Now find the chapter, again by linear search, but even longest book (Psalms)\n\t// has only 150 chapters, so performance is fine\n\tlet c_idx;\n\tfor(c_idx = 1; c_idx < book.chapters.length; ++c_idx){\n\t\tif(book.chapters[c_idx].cumulative_verse > vidx){\n\t\t\tbreak;\n\t\t}\n\t}\n\t--c_idx;\n\n\t// +1's here since chapters and verses not indexed from 0\n\treturn { book : book.id,\n\t\t\t\t\t chapter : c_idx + 1,\n\t\t\t\t\t verse : vidx - book.chapters[c_idx].cumulative_verse + 1,\n\t\t\t\t };\n}\n\n\n/**\n * Counts the total number of verses expressed by a BibleRef\n */\nexport function countVerses(v : Versification, ref : BibleRef) : number {\n\tif(ref.is_range){\n\t\t// +1 since we include both start and end, and adjacent verses have vidx's\n\t\t// differing by 1\n\t\treturn toVidx(v, ref.end) - toVidx(v, ref.start) + 1;\n\t} else {\n\t\treturn 1;\n\t}\n}\n\n/**\n * Takes a list of BibleRefs and returns a new list containing just the first N\n * verses from the set\n */\nexport function firstNVerses(v : Versification,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t refs : BibleRef[],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t n : number\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : BibleRef[] {\n\tif(n === 0 || refs.length === 0){\n\t\treturn [];\n\t}\n\n\tif (refs.length === 1){\n\t\tlet ref = refs[0];\n\n\t\tlet count = countVerses(v, ref);\n\t\tif(count <= n){ return refs; }\n\n\t\t// Logically we MUST be left with a range now, since if n === 0 we would\n\t\t// have already returned, hence n > 0\n\t\t// If ref is a BibleVerse then count = 1, hence count >= n, hence\n\t\t// we have just returned\n\t\t// But typescript can't do that sort of inference, so we force it here\n\t\tlet range : BibleRange = ref as BibleRange;\n\n\t\tif(n == 1){ return [range.start]; }\n\n\t\t// Now we simply get the v_idx of the start, advance by n, and convert\n\t\t// back to a verse\n\t\treturn [{ is_range : true,\n\t\t\t\t\t\t\tstart : range.start,\n\t\t\t\t\t\t\tend : fromVidx(v, toVidx(v, range.start) + n - 1),\n\t\t\t\t\t\t}];\n\t}\n\n\t// If we're still going then we have multiple BibleRef instances to consider\n\t// We can simply go thorugh the list, adding each ref in turn until we run\n\t// out of verses\n\t// The final entry is trimmed to the length of the remaining allowance\n\t// using firstNVerses (although the refs.length === 1 case will be hit)\n\tlet result : BibleRef[] = [];\n\tlet used = 0;\n\tfor(let i = 0; i < refs.length && used < n; ++i){\n\t\tlet more = firstNVerses(v, [refs[i]], n - used);\n\t\tused += countVerses(v, more[0]);\n\t\tresult.push(more[0]);\n\t}\n\treturn result;\n}\n", "/**\n * Various utility functions for treating [[BibleRef]]s as geometry, IE:\n * BibleVerse's as points, and BibleRange's as line segments, and then finding intersections/etc\n */\n\nimport { BibleRef, BibleVerse, BibleRange, BibleRefLibData } from './BibleRef';\nimport { Versification, BookMeta } from './Versification'\nimport * as Vidx from './vidx';\n\nexport interface GeometryFunctions {\n\tgetIntersection(a: BibleRef | BibleRef[], b: BibleRef | BibleRef[]): BibleRef[];\n\tintersects(a: BibleRef | BibleRef[], b: BibleRef | BibleRef[]): boolean,\n\tcontains(a: BibleRef, b: BibleRef): boolean,\n\tgetUnion(a: BibleRef | BibleRef[], b: BibleRef | BibleRef[]): BibleRef[];\n\tgetDifference(a: BibleRef | BibleRef[], b: BibleRef | BibleRef[]): BibleRef[];\n\tindexOf(a: BibleRef | BibleRef[], b: BibleVerse): number;\n\tverseAtIndex(a: BibleRef | BibleRef[], idx: number): BibleVerse | undefined;\n\tcreateIntersectionSet(a: BibleRef | BibleRef[]) : IntersectionSet;\n\tcombineRanges(refs: BibleRef[]) : BibleRef[];\n};\n\n/**\n * Represents a 1d line segment (we map [[BibleRange]]'s to vidx ranges to do maths on them)\n *\n * @private\n */\nexport interface LineSegment {\n\tmin: number;\n\tmax: number;\n}\n\n/**\n * Generates the most compressed representation possible of some set of\n * [[BibleVerse]]s/[[BibleRange]]s by combining adjacent or overlapping ranges into\n * larger ones\n *\n * For example, an input list of \"Gen 1\", \"Gen 2\", \"Gen 3\", would produce a\n * single [[BibleRange]] for \"Gen 1-3\"\n *\n * Order of input ranges in unimportant, since this functional will interally\n * call [[sort]] first\n */\nexport function combineRanges(this: BibleRefLibData, refs: BibleRef[]) : BibleRef[]{\n\tlet v = this.versification;\n\n\t// Convert BibleRefs into vidx pairs representing the range\n\tlet ranges = _toLineSegmentsUnsorted(this, refs);\n\tranges = ranges.sort((a,b) => a.min - b.min);\n\n\t// Combine all ranges\n\tlet out_ranges : LineSegment[] = [];\n\tlet cur_r : LineSegment | null = null;\n\tfor(let new_r of ranges){\n\t\tif(cur_r == null){\n\t\t\tcur_r = new_r;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif(new_r.min > cur_r.max+1){\n\t\t\t// then no overlap\n\t\t\tout_ranges.push(cur_r);\n\t\t\tcur_r = new_r;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// expand the current cur_r to end at the end of the new one\n\t\tif(new_r.max > cur_r.max){ cur_r.max = new_r.max };\n\t}\n\n\tif(cur_r){ out_ranges.push(cur_r); }\n\n\t// Convert vidx pairs back into BibleRefs\n\treturn out_ranges.map(x => _fromLineSegment(this, x));\n}\n\n/**\n * Opaque type containing data for use by `getIntersection` or `intersects`\n */\nexport type IntersectionSet = { segments: LineSegment[] };\n\n/**\n * Precomputes data regarding BibleRef list as used by `getIntersection` and `intersects`\n *\n * This is more performant if you call either of these functions multiple times where one of the two\n * inputs remains constant\n */\nexport function createIntersectionSet(this: BibleRefLibData, x: BibleRef | BibleRef[]) : IntersectionSet {\n\treturn { segments: _toLineSegments(this, x) };\n}\n\n/**\n * Creates a new array of [[BibleRef]]s which represents the intersection between two other sets\n *\n * @param this - Any object with a `versification` field\n * @param x - First set of [[BibleRef]] instances\n * @param y - Second set of [[BibleRef]] instances\n *\n * @return Simplified and sorted list of [[BibleRef]]s which appear in both input sets. Will\n * return empty array if there are no verses in common between the inputs\n */\nexport function getIntersection(this: BibleRefLibData, x: BibleRef | BibleRef[] | IntersectionSet, y: BibleRef | BibleRef[] | IntersectionSet) : BibleRef[] {\n\tlet a = _toLineSegments(this, x);\n\tlet b = _toLineSegments(this, y);\n\n\tlet ai = 0; let bi = 0;\n\tlet out : BibleRef[] = [];\n\twhile(ai < a.length && bi < b.length){\n\t\t// Find intersections between current list heads\n\t\tlet intersection = _intersectLineSegment(a[ai], b[bi]);\n\n\t\tif(intersection === null){\n\t\t\t// If no intersection between current heads, skip past the one which ends soonest\n\t\t\tif(a[ai].max < b[bi].max){\n\t\t\t\t++ai;\n\t\t\t} else {\n\t\t\t\t++bi;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// If intersection found, add to result set\n\t\tout.push(_fromLineSegment(this, intersection));\n\n\t\t// Alter the ranges to exclude that which we've just found\n\t\ta[ai].min = intersection.max+1;\n\t\tb[bi].min = intersection.max+1;\n\n\t\t// Skip past list heads if they now have length 0\n\t\tif(a[ai].min >= a[ai].max){ ++ai; }\n\t\tif(b[bi].min >= b[bi].max){ ++bi; }\n\t}\n\n\treturn out;\n}\n\n/**\n * Determines whether two sets of [[BibleRef]]s have any verses in common\n *\n * This is much faster on large data sets than `getIntersection` when just a boolean result is\n * required\n */\nexport function intersects(this: BibleRefLibData, x: BibleRef | BibleRef[] | IntersectionSet, y: BibleRef | BibleRef[] | IntersectionSet) : boolean {\n\tlet a = _toLineSegments(this, x);\n\tlet b = _toLineSegments(this, y);\n\n\t// apply the O(log(n)) search to the larger haystack and the O(n) search to the smaller needles\n\tlet [ needles, haystack ] = a.length > b.length ? [ b, a ] : [ a, b ];\n\n\tfor(let needle of needles) {\n\t\t// haystack is sorted by min, so find the first possible item that could possibly intersect\n\t\t// using a binary search (IE: O(log(n)) rather than O(n) performance)\n\t\tlet minLo = 0;\n\t\tlet minHi = haystack.length-1;\n\t\twhile(minLo < minHi-1) {\n\t\t\tlet center = minLo + Math.ceil((minHi - minLo)/2);\n\t\t\twhile(minLo < minHi-1 && haystack[center].min < needle.min) {\n\t\t\t\tminLo = center;\n\t\t\t\tcenter = minLo + Math.ceil((minHi - minLo)/2);\n\t\t\t}\n\t\t\tminHi = center;\n\t\t}\n\n\t\t// now do a linear search from minLow to end of haystack\n\t\t// in reality, we can bail out much sooner\n\t\t// (as soon as the haystack's min is greater than needle's max)\n\t\tfor(let i = minLo; i < haystack.length; ++i) {\n\t\t\tif(needle.max < haystack[i].min) { break; }\n\t\t\tif(_intersectLineSegment(needle, haystack[i])){ return true; }\n\t\t}\n\n\t}\n\n\t// if still going, we obviously don't have an intersection\n\treturn false;\n}\n\n/**\n * Determines whether `outer` fully contains all verses present within `inner`\n */\nexport function contains(this: BibleRefLibData, outer: BibleRef | BibleRef[], inner: BibleRef | BibleRef[]) : boolean {\n\tlet a = _toLineSegments(this, outer);\n\tlet b = _toLineSegments(this, inner);\n\n\tlet ai = 0, bi = 0;\n\twhile(bi < b.length){\n\t\t// Consume head of a while segment is before start of head of b\n\t\twhile(a[ai].max < b[bi].min){\n\t\t\t++ai;\n\t\t\tif(ai >= a.length){ return false; }\n\t\t}\n\n\t\t// Check that b falls within the head of a\n\t\twhile(bi < b.length && b[bi].min <= a[ai].max){\n\t\t\tif(a[ai].min > b[bi].min || b[bi].max > a[ai].max){\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t++bi;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Returns the union of two sets of [[BibleRef]]s, IE: the combined and simpified set of verses\n * which are in one or the other or both input sets\n */\nexport function getUnion(this: BibleRefLibData, a: BibleRef | BibleRef[], b: BibleRef | BibleRef[]) : BibleRef[] {\n\tlet x = 'length' in a ? a : [a];\n\tlet y = 'length' in b ? b : [b];\n\treturn combineRanges.bind(this)([...x, ...y]);\n}\n\n/**\n * Computes the subtraction of two sets of [[BibleRef]]s, returing a new list of [[BibleRef]]\n * instances containing all verses in set `x` but not in set `y`\n *\n * @param x - The left hand set\n * @param y - The right hand set\n * @return Set operation `x \\ y` -> IE: all verses in `x` but not in `y`\n */\nexport function getDifference(this: BibleRefLibData, x: BibleRef | BibleRef[], y: BibleRef | BibleRef[]): BibleRef[] {\n\tlet a = _toLineSegments(this, x);\n\tlet b = _toLineSegments(this, y);\n\n\tlet result : BibleRef[] = [];\n\tlet ai = 0, bi = 0\n\twhile(ai < a.length && bi < b.length){\n\t\tlet inter = _intersectLineSegment(a[ai], b[bi]);\n\t\tif(inter){\n\t\t\tif(a[ai].min < inter.min){\n\t\t\t\tresult.push(_fromLineSegment(this, { min: a[ai].min, max: inter.min-1 }));\n\t\t\t}\n\t\t\ta[ai].min = inter.max+1;\n\t\t\tb[bi].min = inter.max+1;\n\t\t\tif(a[ai].min > a[ai].max){ ++ai; }\n\t\t\tif(b[bi].min > b[bi].max){ ++bi; }\n\t\t} else {\n\t\t\tif(a[ai].min < b[bi].min){\n\t\t\t\tresult.push(_fromLineSegment(this, a[ai]));\n\t\t\t\t++ai;\n\t\t\t} else {\n\t\t\t\t++bi;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Consume any remaining elements of a now that b has been exhausted\n\twhile(ai < a.length){\n\t\tresult.push(_fromLineSegment(this, a[ai]));\n\t\t++ai;\n\t}\n\n\treturn result;\n}\n\n/**\n * Given a (potentially non-continous) set of [[BibleRef]]'s, computes the index of some\n * [[BibleVerse]] within the set.\n *\n * For example, given the input set \"Revelation 1:1; Exodus 1:2-4; Genesis 10:5\" the following\n * verses appear at each index:\n * - 0: Revelation 1:1\n * - 1: Exodus 1:2\n * - 2: Exodus 1:3\n * - 3: Exodus 1:4\n * - 4: Genesis 10:5\n *\n * @param array - The array of input verses you wish to search (aka, the haystack)\n * @param verse - The verse whose index you wish to determnine (aka, the needle)\n * @return The zero based index at which `verse` can be found, or -1 if the `verse` does not appear\n * within the input `array`\n *\n * @note If the same verse appears at multiple positions within the input array then only the\n * first index is returned\n *\n * @note The inverse of this function is [[verseAtIndex]]\n */\nexport function indexOf(this: BibleRefLibData, array: BibleRef | BibleRef[], verse: BibleVerse): number {\n\tlet blocks = _toLineSegmentsUnsorted(this, array);\n\tlet idx = Vidx.toVidx(this.versification, verse);\n\n\tlet offset = 0;\n\tfor(let b of blocks){\n\t\tif(idx >= b.min && idx <= b.max){\n\t\t\t// then target verse falls within this\n\t\t\treturn offset + (idx - b.min);\n\t\t} else {\n\t\t\toffset += (b.max - b.min) + 1;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\n/**\n * Given a (potentially non-continous) set of [[BibleRef]]'s, finds the [[BibleVerse]] at the\n * specified index. This is the inverse of [[indexOf]]\n *\n * @param array - The set of [[BibleRef]] instances (or singular instance) to extract a verse from\n * @param index - The zero based index of the verse you wish to extract from the input set\n *\n * @return BibleVerse i