import { BitArray, List$Empty, List$NonEmpty, List$isEmpty, List$isNonEmpty, Result$Ok, Result$Error, Result$isOk, Result$isError, UtfCodepoint, stringBits, toBitArray, bitArraySlice, CustomType, } from "./gleam.mjs"; import { Some, None } from "./gleam/option.mjs"; import { default as Dict, fold as dict_fold, get as dict_get, from as dict_from_iterable, } from "./dict.mjs"; import { classify } from "./gleam/dynamic.mjs"; import { DecodeError$DecodeError } from "./gleam/dynamic/decode.mjs"; const Nil = undefined; export function identity(x) { return x; } export function parse_int(value) { if (/^[-+]?(\d+)$/.test(value)) { return Result$Ok(parseInt(value)); } else { return Result$Error(Nil); } } export function parse_float(value) { if (/^[-+]?(\d+)\.(\d+)([eE][-+]?\d+)?$/.test(value)) { return Result$Ok(parseFloat(value)); } else { return Result$Error(Nil); } } export function to_string(term) { return term.toString(); } export function int_to_base_string(int, base) { return int.toString(base).toUpperCase(); } const int_base_patterns = { 2: /[^0-1]/, 3: /[^0-2]/, 4: /[^0-3]/, 5: /[^0-4]/, 6: /[^0-5]/, 7: /[^0-6]/, 8: /[^0-7]/, 9: /[^0-8]/, 10: /[^0-9]/, 11: /[^0-9a]/, 12: /[^0-9a-b]/, 13: /[^0-9a-c]/, 14: /[^0-9a-d]/, 15: /[^0-9a-e]/, 16: /[^0-9a-f]/, 17: /[^0-9a-g]/, 18: /[^0-9a-h]/, 19: /[^0-9a-i]/, 20: /[^0-9a-j]/, 21: /[^0-9a-k]/, 22: /[^0-9a-l]/, 23: /[^0-9a-m]/, 24: /[^0-9a-n]/, 25: /[^0-9a-o]/, 26: /[^0-9a-p]/, 27: /[^0-9a-q]/, 28: /[^0-9a-r]/, 29: /[^0-9a-s]/, 30: /[^0-9a-t]/, 31: /[^0-9a-u]/, 32: /[^0-9a-v]/, 33: /[^0-9a-w]/, 34: /[^0-9a-x]/, 35: /[^0-9a-y]/, 36: /[^0-9a-z]/, }; export function int_from_base_string(string, base) { if (int_base_patterns[base].test(string.replace(/^-/, "").toLowerCase())) { return Result$Error(Nil); } const result = parseInt(string, base); if (isNaN(result)) { return Result$Error(Nil); } return Result$Ok(result); } export function string_replace(string, target, substitute) { return string.replaceAll(target, substitute); } export function string_reverse(string) { return [...string].reverse().join(""); } export function string_length(string) { if (string === "") { return 0; } const iterator = graphemes_iterator(string); if (iterator) { let i = 0; for (const _ of iterator) { i++; } return i; } else { return string.match(/./gsu).length; } } export function graphemes(string) { const iterator = graphemes_iterator(string); if (iterator) { return arrayToList(Array.from(iterator).map((item) => item.segment)); } else { return arrayToList(string.match(/./gsu)); } } let segmenter = undefined; function graphemes_iterator(string) { if (globalThis.Intl && Intl.Segmenter) { segmenter ||= new Intl.Segmenter(); return segmenter.segment(string)[Symbol.iterator](); } } export function pop_grapheme(string) { let first; const iterator = graphemes_iterator(string); if (iterator) { first = iterator.next().value?.segment; } else { first = string.match(/./su)?.[0]; } if (first) { return Result$Ok([first, string.slice(first.length)]); } else { return Result$Error(Nil); } } export function pop_codeunit(str) { return [str.charCodeAt(0) | 0, str.slice(1)]; } export function lowercase(string) { return string.toLowerCase(); } export function uppercase(string) { return string.toUpperCase(); } export function less_than(a, b) { return a < b; } export function add(a, b) { return a + b; } export function split(xs, pattern) { return arrayToList(xs.split(pattern)); } export function concat(xs) { let result = ""; for (const x of xs) { result = result + x; } return result; } export function length(data) { return data.length; } export function string_byte_slice(string, index, length) { return string.slice(index, index + length); } export function string_grapheme_slice(string, idx, len) { if (len <= 0 || idx >= string.length) { return ""; } const iterator = graphemes_iterator(string); if (iterator) { while (idx-- > 0) { iterator.next(); } let result = ""; while (len-- > 0) { const v = iterator.next().value; if (v === undefined) { break; } result += v.segment; } return result; } else { return string .match(/./gsu) .slice(idx, idx + len) .join(""); } } export function string_codeunit_slice(str, from, length) { return str.slice(from, from + length); } export function crop_string(string, substring) { return string.substring(string.indexOf(substring)); } export function contains_string(haystack, needle) { return haystack.indexOf(needle) >= 0; } export function starts_with(haystack, needle) { return haystack.startsWith(needle); } export function ends_with(haystack, needle) { return haystack.endsWith(needle); } export function split_once(haystack, needle) { const index = haystack.indexOf(needle); if (index >= 0) { const before = haystack.slice(0, index); const after = haystack.slice(index + needle.length); return Result$Ok([before, after]); } else { return Result$Error(Nil); } } const unicode_whitespaces = [ "\u0020", // Space "\u0009", // Horizontal tab "\u000A", // Line feed "\u000B", // Vertical tab "\u000C", // Form feed "\u000D", // Carriage return "\u0085", // Next line "\u2028", // Line separator "\u2029", // Paragraph separator ].join(""); const trim_start_regex = /* @__PURE__ */ new RegExp( `^[${unicode_whitespaces}]*`, ); const trim_end_regex = /* @__PURE__ */ new RegExp(`[${unicode_whitespaces}]*$`); export function trim_start(string) { return string.replace(trim_start_regex, ""); } export function trim_end(string) { return string.replace(trim_end_regex, ""); } export function bit_array_from_string(string) { return toBitArray([stringBits(string)]); } export function bit_array_bit_size(bit_array) { return bit_array.bitSize; } export function bit_array_byte_size(bit_array) { return bit_array.byteSize; } export function bit_array_pad_to_bytes(bit_array) { const trailingBitsCount = bit_array.bitSize % 8; // If the bit array is a whole number of bytes it can be returned unchanged if (trailingBitsCount === 0) { return bit_array; } const finalByte = bit_array.byteAt(bit_array.byteSize - 1); // The required final byte has its unused trailing bits set to zero const unusedBitsCount = 8 - trailingBitsCount; const correctFinalByte = (finalByte >> unusedBitsCount) << unusedBitsCount; // If the unused bits in the final byte are already set to zero then the // existing buffer can be re-used, avoiding a copy if (finalByte === correctFinalByte) { return new BitArray( bit_array.rawBuffer, bit_array.byteSize * 8, bit_array.bitOffset, ); } // Copy the bit array into a new aligned buffer and set the correct final byte const buffer = new Uint8Array(bit_array.byteSize); for (let i = 0; i < buffer.length - 1; i++) { buffer[i] = bit_array.byteAt(i); } buffer[buffer.length - 1] = correctFinalByte; return new BitArray(buffer); } export function bit_array_concat(bit_arrays) { return toBitArray(bit_arrays.toArray()); } export function console_log(term) { console.log(term); } export function console_error(term) { console.error(term); } export function crash(message) { throw new globalThis.Error(message); } export function bit_array_to_string(bit_array) { // If the bit array isn't a whole number of bytes then return an error if (bit_array.bitSize % 8 !== 0) { return Result$Error(Nil); } try { const decoder = new TextDecoder("utf-8", { fatal: true }); if (bit_array.bitOffset === 0) { return Result$Ok(decoder.decode(bit_array.rawBuffer)); } else { // The input data isn't aligned, so copy it into a new aligned buffer so // that TextDecoder can be used const buffer = new Uint8Array(bit_array.byteSize); for (let i = 0; i < buffer.length; i++) { buffer[i] = bit_array.byteAt(i); } return Result$Ok(decoder.decode(buffer)); } } catch { return Result$Error(Nil); } } export function print(string) { if (typeof process === "object" && process.stdout?.write) { process.stdout.write(string); // We can write without a trailing newline } else if (typeof Deno === "object") { Deno.stdout.writeSync(new TextEncoder().encode(string)); // We can write without a trailing newline } else { console.log(string); // We're in a browser. Newlines are mandated } } export function print_error(string) { if (typeof process === "object" && process.stderr?.write) { process.stderr.write(string); // We can write without a trailing newline } else if (typeof Deno === "object") { Deno.stderr.writeSync(new TextEncoder().encode(string)); // We can write without a trailing newline } else { console.error(string); // We're in a browser. Newlines are mandated } } export function print_debug(string) { if (typeof process === "object" && process.stderr?.write) { process.stderr.write(string + "\n"); // If we're in Node.js, use `stderr` } else if (typeof Deno === "object") { Deno.stderr.writeSync(new TextEncoder().encode(string + "\n")); // If we're in Deno, use `stderr` } else { console.log(string); // Otherwise, use `console.log` (so that it doesn't look like an error) } } export function ceiling(float) { return Math.ceil(float); } export function floor(float) { return Math.floor(float); } export function round(float) { return Math.round(float); } export function truncate(float) { return Math.trunc(float); } export function power(base, exponent) { // It is checked in Gleam that: // - The base is non-negative and that the exponent is not fractional. // - The base is non-zero and the exponent is non-negative (otherwise // the result will essentially be division by zero). // It can thus be assumed that valid input is passed to the Math.pow // function and a NaN or Infinity value will not be produced. return Math.pow(base, exponent); } export function random_uniform() { const random_uniform_result = Math.random(); // With round-to-nearest-even behavior, the ranges claimed for the functions below // (excluding the one for Math.random() itself) aren't exact. // If extremely large bounds are chosen (2^53 or higher), // it's possible in extremely rare cases to calculate the usually-excluded upper bound. // Note that as numbers in JavaScript are IEEE 754 floating point numbers // See: // Because of this, we just loop 'until' we get a valid result where 0.0 <= x < 1.0: if (random_uniform_result === 1.0) { return random_uniform(); } return random_uniform_result; } export function bit_array_slice(bits, position, length) { const start = Math.min(position, position + length); const end = Math.max(position, position + length); if (start < 0 || end * 8 > bits.bitSize) { return Result$Error(Nil); } return Result$Ok(bitArraySlice(bits, start * 8, end * 8)); } export function codepoint(int) { return new UtfCodepoint(int); } export function string_to_codepoint_integer_list(string) { return arrayToList(Array.from(string).map((item) => item.codePointAt(0))); } export function utf_codepoint_list_to_string(utf_codepoint_integer_list) { return utf_codepoint_integer_list .toArray() .map((x) => String.fromCodePoint(x.value)) .join(""); } export function utf_codepoint_to_int(utf_codepoint) { return utf_codepoint.value; } function unsafe_percent_decode(string) { return decodeURIComponent(string || ""); } function unsafe_percent_decode_query(string) { return decodeURIComponent((string || "").replaceAll("+", " ")); } export function percent_decode(string) { try { return Result$Ok(unsafe_percent_decode(string)); } catch { return Result$Error(Nil); } } export function percent_encode(string) { return encodeURIComponent(string).replaceAll("%2B", "+"); } export function parse_query(query) { try { const pairs = []; for (const section of query.split("&")) { const [key, value] = section.split("="); if (!key) continue; const decodedKey = unsafe_percent_decode_query(key); const decodedValue = unsafe_percent_decode_query(value); pairs.push([decodedKey, decodedValue]); } return Result$Ok(arrayToList(pairs)); } catch { return Result$Error(Nil); } } const b64EncodeLookup = [ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47, ]; let b64TextDecoder; // Implementation based on https://github.com/mitschabaude/fast-base64/blob/main/js.js export function base64_encode(bit_array, padding) { b64TextDecoder ??= new TextDecoder(); bit_array = bit_array_pad_to_bytes(bit_array); const m = bit_array.byteSize; const k = m % 3; const n = Math.floor(m / 3) * 4 + (k && k + 1); const N = Math.ceil(m / 3) * 4; const encoded = new Uint8Array(N); for (let i = 0, j = 0; j < m; i += 4, j += 3) { const y = (bit_array.byteAt(j) << 16) + (bit_array.byteAt(j + 1) << 8) + (bit_array.byteAt(j + 2) | 0); encoded[i] = b64EncodeLookup[y >> 18]; encoded[i + 1] = b64EncodeLookup[(y >> 12) & 0x3f]; encoded[i + 2] = b64EncodeLookup[(y >> 6) & 0x3f]; encoded[i + 3] = b64EncodeLookup[y & 0x3f]; } let base64 = b64TextDecoder.decode(new Uint8Array(encoded.buffer, 0, n)); if (padding) { if (k === 1) { base64 += "=="; } else if (k === 2) { base64 += "="; } } return base64; } // From https://developer.mozilla.org/en-US/docs/Glossary/Base64 export function base64_decode(sBase64) { try { const binString = atob(sBase64); const length = binString.length; const array = new Uint8Array(length); for (let i = 0; i < length; i++) { array[i] = binString.charCodeAt(i); } return Result$Ok(new BitArray(array)); } catch { return Result$Error(Nil); } } export function classify_dynamic(data) { if (typeof data === "string") { return "String"; } else if (typeof data === "boolean") { return "Bool"; } else if (isResult(data)) { return "Result"; } else if (isList(data)) { return "List"; } else if (data instanceof BitArray) { return "BitArray"; } else if (data instanceof Dict) { return "Dict"; } else if (Number.isInteger(data)) { return "Int"; } else if (Array.isArray(data)) { return `Array`; } else if (typeof data === "number") { return "Float"; } else if (data === null) { return "Nil"; } else if (data === undefined) { return "Nil"; } else { const type = typeof data; return type.charAt(0).toUpperCase() + type.slice(1); } } export function byte_size(string) { return new TextEncoder().encode(string).length; } // In JavaScript, bitwise operations convert numbers to a sequence of 32 bits, // while Erlang uses arbitrary precision integers. // // To get around this, every function here follows this pattern: // // 1. If both values fit in the 32-bit signed integer range, use the standard // JavaScript bitwise operators directly. // // Note: For bitwise_shift_left, the result also needs to fit in 32 bits, // so we use floating-point multiplication instead. // // 2. If either value falls outside the safe integer range (-2^53, 2^53), // fall back to BigInt arithmetic, then downcast the result back to a Number. // // 3. Otherwise (safe integers outside the 32-bit range), we split the operation // across the high 21 bits and low 32 bits individually: // // x1 $ x2 = ((hi(x1) $ hi(x2)) << 32) | (lo(x1) $ lo(x2)) // // where `$` is a bitwise operator. // // We split both values into a `hi` and a `lo` part: // // hi(x) = Math.floor(x / 2^32) the upper 21 bits // lo(x) = x >>> 0 the lower 32 bits (as unsigned) // // For `hi`, we use that shifts are equal to multiplication/division with // powers of two to get around the 32-bit range limitation. Math.floor is // used instead of truncation since arithmetic right shift fills with the // sign bit. For negative numbers, the discarded bits were non-zero // (representing a positive fractional part), so discarding them makes the // result strictly more negative, i.e. rounding away from 0. // // This works because bitwise operators are distributive across bit ranges: // // x1 $ x2 = (hi(x1) $ hi(x2)) << 32 | (lo(x1) $ lo(x2)) // = (hi(x1) $ hi(x2)) * 2^32 + (lo(x1) $ lo(x2)) // // JavaScript bitwise operators truncate inputs to signed 32-bit integers, // so `x1 $ x2` already computes `lo(x1) $ lo(x2)` — we just need to // reinterpret the signed result as unsigned using `>>> 0`: // // lo(x1) $ lo(x2) = (x1 $ x2) >>> 0 = lo(x1 $ x2) // => x1 $ x2 = (hi(x1) $ hi(x2)) * 2^32 + lo(x1 $ x2). // const MIN_I32 = -(2 ** 31); // -2147483648 const MAX_I32 = 2 ** 31 - 1; // 2147483647 const U32 = 2 ** 32; const MAX_SAFE = Number.MAX_SAFE_INTEGER; const MIN_SAFE = Number.MIN_SAFE_INTEGER; export function bitwise_and(x, y) { if (x >= MIN_I32 && x <= MAX_I32 && y >= MIN_I32 && y <= MAX_I32) return x & y; if (x < MIN_SAFE || x > MAX_SAFE || y < MIN_SAFE || y > MAX_SAFE) return Number(BigInt(x) & BigInt(y)); return (Math.floor(x / U32) & Math.floor(y / U32)) * U32 + ((x & y) >>> 0); } export function bitwise_or(x, y) { if (x >= MIN_I32 && x <= MAX_I32 && y >= MIN_I32 && y <= MAX_I32) return x | y; if (x < MIN_SAFE || x > MAX_SAFE || y < MIN_SAFE || y > MAX_SAFE) return Number(BigInt(x) | BigInt(y)); return (Math.floor(x / U32) | Math.floor(y / U32)) * U32 + ((x | y) >>> 0); } export function bitwise_exclusive_or(x, y) { if (x >= MIN_I32 && x <= MAX_I32 && y >= MIN_I32 && y <= MAX_I32) return x ^ y; if (x < MIN_SAFE || x > MAX_SAFE || y < MIN_SAFE || y > MAX_SAFE) return Number(BigInt(x) ^ BigInt(y)); return (Math.floor(x / U32) ^ Math.floor(y / U32)) * U32 + ((x ^ y) >>> 0); } export function bitwise_not(x) { if (x >= MIN_I32 && x <= MAX_I32) return ~x; if (x < MIN_SAFE || x > MAX_SAFE) return Number(~BigInt(x)); return ~Math.floor(x / U32) * U32 + (~x >>> 0); } export function bitwise_shift_right(x, y) { if (y === 0) return x; if (y < 0) return bitwise_shift_left(x, -y); if (y < 32 && x >= MIN_I32 && x <= MAX_I32) return x >> y; if (x < MIN_SAFE || x > MAX_SAFE) return Number(BigInt(x) >> BigInt(y)); const ahi = Math.floor(x / U32); // Shifting right by y < 32 moves bits across the hi/lo boundary: // // before: [ hi (21 bits) | lo (32 bits) ] // after: [ hi >> y | (hi's low y bits) ++ (lo >> y) ] // // The new low word has two sources: // - lo's bits shifted down: x >>> y (>>> treats x as unsigned 32-bit) // - hi's bottom y bits shifted up: ahi << (32 - y). if (y < 32) return (ahi >> y) * U32 + (((x >>> y) | (ahi << (32 - y))) >>> 0); // Shifting by >= 32 wipes out the entire low word. The result is just the // high word shifted right by the remaining amount. return ahi >> (y - 32); } export function bitwise_shift_left(x, y) { if (y === 0) return x; if (y < 0) return bitwise_shift_right(x, -y); if (y < 31) return x * (1 << y); return x * 2 ** y; } export function inspect(v) { return new Inspector().inspect(v); } export function float_to_string(float) { const string = float.toString().replace("+", ""); if (string.indexOf(".") >= 0) { return string; } else { const index = string.indexOf("e"); if (index >= 0) { return string.slice(0, index) + ".0" + string.slice(index); } else { return string + ".0"; } } } class Inspector { #references = new Set(); inspect(v) { const t = typeof v; if (v === true) return "True"; if (v === false) return "False"; if (v === null) return "//js(null)"; if (v === undefined) return "Nil"; if (t === "string") return this.#string(v); if (t === "bigint" || Number.isInteger(v)) return v.toString(); if (t === "number") return float_to_string(v); if (v instanceof UtfCodepoint) return this.#utfCodepoint(v); if (v instanceof BitArray) return this.#bit_array(v); if (v instanceof RegExp) return `//js(${v})`; if (v instanceof Date) return `//js(Date("${v.toISOString()}"))`; if (v instanceof globalThis.Error) return `//js(${v.toString()})`; if (v instanceof Function) { const args = []; for (const i of Array(v.length).keys()) args.push(String.fromCharCode(i + 97)); return `//fn(${args.join(", ")}) { ... }`; } if (this.#references.size === this.#references.add(v).size) { return "//js(circular reference)"; } let printed; if (Array.isArray(v)) { printed = `#(${v.map((v) => this.inspect(v)).join(", ")})`; } else if (isList(v)) { printed = this.#list(v); } else if (v instanceof CustomType) { printed = this.#customType(v); } else if (v instanceof Dict) { printed = this.#dict(v); } else if (v instanceof Set) { return `//js(Set(${[...v].map((v) => this.inspect(v)).join(", ")}))`; } else { printed = this.#object(v); } this.#references.delete(v); return printed; } #object(v) { const name = Object.getPrototypeOf(v)?.constructor?.name || "Object"; const props = []; for (const k of Object.keys(v)) { props.push(`${this.inspect(k)}: ${this.inspect(v[k])}`); } const body = props.length ? " " + props.join(", ") + " " : ""; const head = name === "Object" ? "" : name + " "; return `//js(${head}{${body}})`; } #dict(map) { let body = "dict.from_list(["; let first = true; body = dict_fold(map, body, (body, key, value) => { if (!first) body = body + ", "; first = false; return body + "#(" + this.inspect(key) + ", " + this.inspect(value) + ")"; }); return body + "])"; } #customType(record) { const props = Object.keys(record) .map((label) => { const value = this.inspect(record[label]); return isNaN(parseInt(label)) ? `${label}: ${value}` : value; }) .join(", "); return props ? `${record.constructor.name}(${props})` : record.constructor.name; } #list(list) { if (List$isEmpty(list)) { return "[]"; } let char_out = 'charlist.from_string("'; let list_out = "["; let current = list; while (List$isNonEmpty(current)) { let element = current.head; current = current.tail; if (list_out !== "[") { list_out += ", "; } list_out += this.inspect(element); if (char_out) { if (Number.isInteger(element) && element >= 32 && element <= 126) { char_out += String.fromCharCode(element); } else { char_out = null; } } } if (char_out) { return char_out + '")'; } else { return list_out + "]"; } } #string(str) { let new_str = '"'; for (let i = 0; i < str.length; i++) { const char = str[i]; switch (char) { case "\n": new_str += "\\n"; break; case "\r": new_str += "\\r"; break; case "\t": new_str += "\\t"; break; case "\f": new_str += "\\f"; break; case "\\": new_str += "\\\\"; break; case '"': new_str += '\\"'; break; default: if (char < " " || (char > "~" && char < "\u{00A0}")) { new_str += "\\u{" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + "}"; } else { new_str += char; } } } new_str += '"'; return new_str; } #utfCodepoint(codepoint) { return `//utfcodepoint(${String.fromCodePoint(codepoint.value)})`; } #bit_array(bits) { if (bits.bitSize === 0) { return "<<>>"; } let acc = "<<"; for (let i = 0; i < bits.byteSize - 1; i++) { acc += bits.byteAt(i).toString(); acc += ", "; } if (bits.byteSize * 8 === bits.bitSize) { acc += bits.byteAt(bits.byteSize - 1).toString(); } else { const trailingBitsCount = bits.bitSize % 8; acc += bits.byteAt(bits.byteSize - 1) >> (8 - trailingBitsCount); acc += `:size(${trailingBitsCount})`; } acc += ">>"; return acc; } } export function base16_encode(bit_array) { const trailingBitsCount = bit_array.bitSize % 8; let result = ""; for (let i = 0; i < bit_array.byteSize; i++) { let byte = bit_array.byteAt(i); if (i === bit_array.byteSize - 1 && trailingBitsCount !== 0) { const unusedBitsCount = 8 - trailingBitsCount; byte = (byte >> unusedBitsCount) << unusedBitsCount; } result += byte.toString(16).padStart(2, "0").toUpperCase(); } return result; } export function base16_decode(string) { const bytes = new Uint8Array(string.length / 2); for (let i = 0; i < string.length; i += 2) { const a = parseInt(string[i], 16); const b = parseInt(string[i + 1], 16); if (isNaN(a) || isNaN(b)) return Result$Error(Nil); bytes[i / 2] = a * 16 + b; } return Result$Ok(new BitArray(bytes)); } export function bit_array_to_int_and_size(bits) { const trailingBitsCount = bits.bitSize % 8; const unusedBitsCount = trailingBitsCount === 0 ? 0 : 8 - trailingBitsCount; return [bits.byteAt(0) >> unusedBitsCount, bits.bitSize]; } export function bit_array_starts_with(bits, prefix) { if (prefix.bitSize > bits.bitSize) { return false; } // Check any whole bytes const byteCount = Math.trunc(prefix.bitSize / 8); for (let i = 0; i < byteCount; i++) { if (bits.byteAt(i) !== prefix.byteAt(i)) { return false; } } // Check any trailing bits at the end of the prefix if (prefix.bitSize % 8 !== 0) { const unusedBitsCount = 8 - (prefix.bitSize % 8); if ( bits.byteAt(byteCount) >> unusedBitsCount !== prefix.byteAt(byteCount) >> unusedBitsCount ) { return false; } } return true; } export function log(x) { // It is checked in Gleam that: // - The input is strictly positive (x > 0) // - This ensures that Math.log will never return NaN or -Infinity // The function can thus safely pass the input to Math.log // and a valid finite float will always be produced. return Math.log(x); } export function exp(x) { return Math.exp(x); } export function list_to_array(list) { let current = list; let array = []; while (List$isNonEmpty(current)) { array.push(current.head); current = current.tail; } return array; } export function index(data, key) { // Dictionaries and dictionary-like objects can be indexed if (data instanceof Dict) { const result = dict_get(data, key); return Result$Ok(result.isOk() ? new Some(result[0]) : new None()); } if (data instanceof WeakMap || data instanceof Map) { const token = {}; const entry = data.get(key, token); if (entry === token) return Result$Ok(new None()); return Result$Ok(new Some(entry)); } const key_is_int = Number.isInteger(key); // Only elements 0-7 of lists can be indexed, negative indices are not allowed if (key_is_int && key >= 0 && key < 8 && isList(data)) { let i = 0; for (const value of data) { if (i === key) return Result$Ok(new Some(value)); i++; } return Result$Error("Indexable"); } // Arrays and objects can be indexed if ( (key_is_int && Array.isArray(data)) || (data && typeof data === "object") || (data && Object.getPrototypeOf(data) === Object.prototype) ) { if (key in data) return Result$Ok(new Some(data[key])); return Result$Ok(new None()); } return Result$Error(key_is_int ? "Indexable" : "Dict"); } export function list(data, decode, pushPath, index, emptyList) { if (!(isList(data) || Array.isArray(data))) { const error = DecodeError$DecodeError("List", classify(data), emptyList); return [emptyList, arrayToList([error])]; } const decoded = []; for (const element of data) { const layer = decode(element); const [out, errors] = layer; if (List$isNonEmpty(errors)) { const [_, errors] = pushPath(layer, index.toString()); return [emptyList, errors]; } decoded.push(out); index++; } return [arrayToList(decoded), emptyList]; } export function dict(data) { if (data instanceof Dict) { return Result$Ok(data); } if (data instanceof Map || data instanceof WeakMap) { return Result$Ok(dict_from_iterable(data)); } if (data == null) { return Result$Error("Dict"); } if (typeof data !== "object") { return Result$Error("Dict"); } const proto = Object.getPrototypeOf(data); if (proto === Object.prototype || proto === null) { return Result$Ok(dict_from_iterable(Object.entries(data))); } return Result$Error("Dict"); } export function bit_array(data) { if (data instanceof BitArray) return Result$Ok(data); if (data instanceof Uint8Array) return Result$Ok(new BitArray(data)); return Result$Error(new BitArray(new Uint8Array())); } export function float(data) { if (typeof data === "number") return Result$Ok(data); return Result$Error(0.0); } export function int(data) { if (Number.isInteger(data)) return Result$Ok(data); return Result$Error(0); } export function string(data) { if (typeof data === "string") return Result$Ok(data); return Result$Error(""); } export function is_null(data) { return data === null || data === undefined; } function arrayToList(array) { let list = List$Empty(); let i = array.length; while (i--) { list = List$NonEmpty(array[i], list); } return list; } function isList(data) { return List$isEmpty(data) || List$isNonEmpty(data); } function isResult(data) { return Result$isOk(data) || Result$isError(data); } export function string_remove_prefix(string, prefix) { if (string.startsWith(prefix)) { return string.slice(prefix.length); } return string; } export function string_remove_suffix(string, suffix) { if (string.endsWith(suffix)) { return string.slice(0, string.length - suffix.length); } return string; }