Packages
gleam_stdlib
0.61.0
1.0.3
1.0.2
1.0.1
1.0.0
0.71.0
0.70.0
0.69.0
0.68.1
0.68.0
0.67.1
0.67.0
0.65.0
0.64.0
0.63.2
0.63.1
0.63.0
0.62.1
0.62.0
0.61.0
0.60.0
0.59.0
0.58.0
0.57.0
0.56.0
0.55.0
0.54.0
0.53.0
0.52.0
0.51.0
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.43.0
0.42.0
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.1
0.35.0
0.34.0
0.33.1
0.33.0
0.32.1
0.32.0
0.31.0
0.30.2
0.30.1
0.30.0
0.29.2
0.29.1
0.29.0
0.28.2
0.28.1
0.28.0
0.27.0
0.26.1
0.26.0
0.25.0
0.24.0
0.23.0
0.22.3
0.22.2
0.22.1
0.22.0
0.21.0
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.18.0-rc1
0.17.1
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.4.0-rc1
0.3.1
0.3.0
0.2.0
retired
A standard library for the Gleam programming language
Current section
Files
Jump to
Current section
Files
src/gleam_stdlib.mjs
import {
BitArray,
Error,
List,
Ok,
Result,
UtfCodepoint,
stringBits,
toBitArray,
bitArraySlice,
NonEmpty,
Empty,
CustomType,
} from "./gleam.mjs";
import { Some, None } from "./gleam/option.mjs";
import Dict from "./dict.mjs";
import { classify } from "./gleam/dynamic.mjs";
import { DecodeError } from "./gleam/dynamic/decode.mjs";
const Nil = undefined;
const NOT_FOUND = {};
export function identity(x) {
return x;
}
export function parse_int(value) {
if (/^[-+]?(\d+)$/.test(value)) {
return new Ok(parseInt(value));
} else {
return new Error(Nil);
}
}
export function parse_float(value) {
if (/^[-+]?(\d+)\.(\d+)([eE][-+]?\d+)?$/.test(value)) {
return new Ok(parseFloat(value));
} else {
return new 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 new Error(Nil);
}
const result = parseInt(string, base);
if (isNaN(result)) {
return new Error(Nil);
}
return new 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 List.fromArray(Array.from(iterator).map((item) => item.segment));
} else {
return List.fromArray(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 new Ok([first, string.slice(first.length)]);
} else {
return new 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 List.fromArray(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_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 new Ok([before, after]);
} else {
return new 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 new Error(Nil);
}
try {
const decoder = new TextDecoder("utf-8", { fatal: true });
if (bit_array.bitOffset === 0) {
return new 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 new Ok(decoder.decode(buffer));
}
} catch {
return new 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: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random>
// 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 new Error(Nil);
}
return new Ok(bitArraySlice(bits, start * 8, end * 8));
}
export function codepoint(int) {
return new UtfCodepoint(int);
}
export function string_to_codepoint_integer_list(string) {
return List.fromArray(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;
}
export function new_map() {
return Dict.new();
}
export function map_size(map) {
return map.size;
}
export function map_to_list(map) {
return List.fromArray(map.entries());
}
export function map_remove(key, map) {
return map.delete(key);
}
export function map_get(map, key) {
const value = map.get(key, NOT_FOUND);
if (value === NOT_FOUND) {
return new Error(Nil);
}
return new Ok(value);
}
export function map_insert(key, value, map) {
return map.set(key, value);
}
function unsafe_percent_decode(string) {
return decodeURIComponent(string || "");
}
function unsafe_percent_decode_query(string) {
return decodeURIComponent((string || "").replace("+", " "));
}
export function percent_decode(string) {
try {
return new Ok(unsafe_percent_decode(string));
} catch {
return new Error(Nil);
}
}
export function percent_encode(string) {
return encodeURIComponent(string).replace("%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 new Ok(List.fromArray(pairs));
} catch {
return new 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 encode64(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 decode64(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 new Ok(new BitArray(array));
} catch {
return new Error(Nil);
}
}
export function classify_dynamic(data) {
if (typeof data === "string") {
return "String";
} else if (typeof data === "boolean") {
return "Bool";
} else if (data instanceof Result) {
return "Result";
} else if (data instanceof List) {
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.
// To get around this problem and get consistent results use BigInt and then
// downcast the value back to a Number value.
export function bitwise_and(x, y) {
return Number(BigInt(x) & BigInt(y));
}
export function bitwise_not(x) {
return Number(~BigInt(x));
}
export function bitwise_or(x, y) {
return Number(BigInt(x) | BigInt(y));
}
export function bitwise_exclusive_or(x, y) {
return Number(BigInt(x) ^ BigInt(y));
}
export function bitwise_shift_left(x, y) {
return Number(BigInt(x) << BigInt(y));
}
export function bitwise_shift_right(x, y) {
return Number(BigInt(x) >> BigInt(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 (v instanceof List) {
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;
map.forEach((value, key) => {
if (!first) body = body + ", ";
body = body + "#(" + this.inspect(key) + ", " + this.inspect(value) + ")";
first = false;
});
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 instanceof Empty) {
return "[]";
}
let char_out = 'charlist.from_string("';
let list_out = "[";
let current = list;
while (current instanceof NonEmpty) {
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 new Error(Nil);
bytes[i / 2] = a * 16 + b;
}
return new 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 (current instanceof NonEmpty) {
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 || data instanceof WeakMap || data instanceof Map) {
const token = {};
const entry = data.get(key, token);
if (entry === token) return new Ok(new None());
return new 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 && data instanceof List) {
let i = 0;
for (const value of data) {
if (i === key) return new Ok(new Some(value));
i++;
}
return new 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 new Ok(new Some(data[key]));
return new Ok(new None());
}
return new Error(key_is_int ? "Indexable" : "Dict");
}
export function list(data, decode, pushPath, index, emptyList) {
if (!(data instanceof List || Array.isArray(data))) {
const error = new DecodeError("List", classify(data), emptyList);
return [emptyList, List.fromArray([error])];
}
const decoded = [];
for (const element of data) {
const layer = decode(element);
const [out, errors] = layer;
if (errors instanceof NonEmpty) {
const [_, errors] = pushPath(layer, index.toString());
return [emptyList, errors];
}
decoded.push(out);
index++;
}
return [List.fromArray(decoded), emptyList];
}
export function dict(data) {
if (data instanceof Dict) {
return new Ok(data);
}
if (data instanceof Map || data instanceof WeakMap) {
return new Ok(Dict.fromMap(data));
}
if (data == null) {
return new Error("Dict");
}
if (typeof data !== "object") {
return new Error("Dict");
}
const proto = Object.getPrototypeOf(data);
if (proto === Object.prototype || proto === null) {
return new Ok(Dict.fromObject(data));
}
return new Error("Dict");
}
export function bit_array(data) {
if (data instanceof BitArray) return new Ok(data);
if (data instanceof Uint8Array) return new Ok(new BitArray(data));
return new Error(new BitArray(new Uint8Array()));
}
export function float(data) {
if (typeof data === "number") return new Ok(data);
return new Error(0.0);
}
export function int(data) {
if (Number.isInteger(data)) return new Ok(data);
return new Error(0);
}
export function string(data) {
if (typeof data === "string") return new Ok(data);
return new Error("");
}
export function is_null(data) {
return data === null || data === undefined;
}