functionalscript
Version:
FunctionalScript is a purely functional subset of JavaScript
205 lines (204 loc) • 5.98 kB
JavaScript
/**
* Bit vectors that normalize the most-significant bit using signed `bigint` values.
*
* A value whose top bit is already set remains positive, while other values are
* negated after toggling the leading bit so the stop bit is always `1`. The sign bit
* therefore acts as the stop bit that encodes the logical length of the vector.
*
* MSb is most-significant bit first.
*
* ```
* - byte: 0x53 = 0b0101_0011
* - 0123_4567
* ```
*
* LSb is least-significant bit first.
*
* ```
* - byte: 0x53 = 0b0101_0011
* - 7654_3210
* ```
*
* @module
*/
import { abs, bitLength, mask, max, xor } from "../bigint/module.f.js";
import { flip } from "../function/module.f.js";
import { fold } from "../list/module.f.js";
import { asBase, asNominal } from "../nominal/module.f.js";
import { repeat as mRepeat } from "../monoid/module.f.js";
/**
* An empty vector of bits.
*/
export const empty = asNominal(0n);
/**
* Calculates the length of the given vector of bits.
*/
export const length = (v) => bitLength(asBase(v));
const lazyEmpty = () => empty;
/**
* Creates a vector of bits of the given `len` and the provided unsigned integer.
*
* @example
*
* ```js
* const vec4 = vec(4n)
* const v0 = vec4(5n) // -0xDn = -0b1101
* const v1 = vec4(0x5FEn) // 0xEn = 0b1110
* ```
*/
export const vec = (len) => {
if (len <= 0n) {
return lazyEmpty;
}
const m = mask(len);
const last = len - 1n;
const lastBit = 1n << last;
return ui => {
// normalize `u`
const u = m & abs(ui);
//
const sign = u >> last;
const x = sign !== 0n ? u : -(u ^ lastBit);
return asNominal(x);
};
};
/**
* Creates an 8-bit vector from an unsigned integer.
*/
export const vec8 = vec(8n);
/**
* Returns the unsigned integer representation of the vector by clearing the stop bit.
*
* @example
*
* ```js
* const vector = vec(8n)(0x5n) // -0x85n
* const result = uint(vector); // result is 0x5n
* ```
*/
export const uint = (v) => {
const b = asBase(v);
if (b >= 0n) {
return b;
}
const u = -b;
const len = bitLength(u);
return u ^ (1n << (len - 1n));
};
/**
* Extracts the logical length and unsigned integer from the vector.
*/
export const unpack = (v) => ({
length: length(v),
uint: uint(v),
});
/**
* Packs an unpacked representation back into a vector.
*/
export const pack = ({ length, uint }) => vec(length)(uint);
const lsbNorm = ({ length: al, uint: a }) => ({ length: bl, uint: b }) => (len) => ({ a, b });
const msbNorm = ({ length: al, uint: a }) => ({ length: bl, uint: b }) => (len) => ({ a: a << (len - al), b: b << (len - bl) });
/**
* Normalizes two vectors to the same length before applying a bigint reducer.
*/
const op = (norm) => (op) => ap => bp => {
const au = unpack(ap);
const bu = unpack(bp);
const len = max(au.length)(bu.length);
const { a, b } = norm(au)(bu)(len);
return vec(len)(op(a)(b));
};
/**
* Implements operations for handling vectors in a least-significant-bit (LSb) first order.
*
* https://en.wikipedia.org/wiki/Bit_numbering#LSb_0_bit_numbering
*
* Usually associated with Little-Endian (LE) byte order.
*/
export const lsb = {
front: len => {
const m = mask(len);
return v => uint(v) & m;
},
removeFront: len => v => {
const { length, uint } = unpack(v);
return vec(length - len)(uint >> len);
},
popFront: len => {
const m = mask(len);
return v => {
const { length, uint } = unpack(v);
return [uint & m, vec(length - len)(uint >> len)];
};
},
concat: (a) => (b) => {
const { length: al, uint: au } = unpack(a);
const { length: bl, uint: bu } = unpack(b);
return vec(al + bl)((bu << al) | au);
},
xor: op(lsbNorm)(xor)
};
/**
* Implements operations for handling vectors in a most-significant-bit (MSb) first order.
*
* https://en.wikipedia.org/wiki/Bit_numbering#MSb_0_bit_numbering
*
* Usually associated with Big-Endian (BE) byte order.
*/
export const msb = {
front: len => {
const m = mask(len);
return v => {
const { length, uint } = unpack(v);
return (uint >> (length - len)) & m;
};
},
removeFront: len => v => {
const { length, uint } = unpack(v);
return vec(length - len)(uint);
},
popFront: len => {
const m = mask(len);
return v => {
const { length, uint } = unpack(v);
const d = length - len;
return [(uint >> d) & m, vec(d)(uint)];
};
},
concat: flip(lsb.concat),
xor: op(msbNorm)(xor)
};
const appendU8 = ({ concat }) => (u8) => (a) => concat(a)(vec8(BigInt(u8)));
/**
* Converts a list of unsigned 8-bit integers to a bit vector using the provided bit order.
*
* @param bo The bit order for the conversion
* @param list The list of unsigned 8-bit integers to be converted.
* @returns The resulting vector based on the provided bit order.
*/
export const u8ListToVec = (bo) => fold(appendU8(bo))(empty);
/**
* Converts a bit vector to a list of unsigned 8-bit integers based on the provided bit order.
*
* @param bitOrder The bit order for the conversion.
* @param v The vector to be converted.
* @returns A thunk that produces a list of unsigned 8-bit integers.
*/
export const u8List = ({ popFront }) => {
const f = (v) => () => {
if (v === empty) {
return null;
}
const [first, tail] = popFront(8n)(v);
return { first: Number(first), tail: f(tail) };
};
return f;
};
/**
* Concatenates a list of vectors using the provided bit order.
*/
export const listToVec = ({ concat }) => fold(flip(concat))(empty);
/**
* Repeats a vector to create a padded block of the desired length.
*/
export const repeat = mRepeat({ identity: empty, operation: lsb.concat });