functionalscript
Version:
FunctionalScript is a purely functional subset of JavaScript
99 lines (98 loc) • 3.26 kB
JavaScript
import { codePointListToString, stringToCodePointList } from "../text/utf16/module.f.js";
import { isArray2 } from "../types/array/module.f.js";
import { map, toArray, repeat as listRepeat } from "../types/list/module.f.js";
// Internals:
const { fromEntries, values } = Object;
const { fromCodePoint } = String;
/**
* Two 24 bit numbers can be fit into one JS number (53 bit).
*/
const offset = 24;
const mask = (1 << offset) - 1;
const isValid = (r) => r >= 0 && r <= mask;
export const max = codePointListToString([0x10FFFF]);
export const rangeEncode = (a, b) => {
if (!isValid(a) || !isValid(b) || a > b) {
throw `Invalid range ${a} ${b}.`;
}
return (a << offset) | b;
};
export const oneEncode = (a) => rangeEncode(a, a);
export const rangeDecode = (r) => [r >> offset, r & mask];
const mapOneEncode = map(oneEncode);
export const toSequence = (s) => toArray(mapOneEncode(stringToCodePointList(s)));
export const str = (s) => {
const x = toSequence(s);
return x.length === 1 ? x[0] : x;
};
const mapEntry = map((v) => [fromCodePoint(v), oneEncode(v)]);
export const set = (s) => fromEntries(toArray(mapEntry(stringToCodePointList(s))));
export const range = (ab) => {
const a = toArray(stringToCodePointList(ab));
if (!isArray2(a)) {
throw `Invalid range ${ab}.`;
}
return rangeEncode(...a);
};
export const rangeToId = (r) => {
const ab = rangeDecode(r);
const [a, b] = ab;
const cp = a === b ? [a] : ab;
return fromCodePoint(...cp);
};
const rangeToEntry = (r) => [rangeToId(r), r];
const toVariantRangeSet = (r) => fromEntries(r.map(rangeToEntry));
const removeOne = (list, ab) => {
const [a, b] = rangeDecode(ab);
let result = [];
for (const ab0 of list) {
const [a0, b0] = rangeDecode(ab0);
if (a0 < a) {
// [a0
// ]a
result = [...result, rangeEncode(a0, Math.min(b0, a - 1))];
}
if (b < b0) {
// b0]
// b[
result = [...result, rangeEncode(Math.max(b + 1, a0), b0)];
}
}
return result;
};
export const remove = (range, removeSet) => {
let result = [range];
for (const r of values(removeSet)) {
result = removeOne(result, r);
}
return toVariantRangeSet(result);
};
export const none = [];
export const option = (some) => ({
none,
some,
});
/**
* Repeat zero or more times.
*
* https://english.stackexchange.com/questions/506480/single-word-quantifiers-for-zero-or-more-like-cardinalities
* - zero or more - any, 0Plus
* - one or more - several, 1Plus
*
* Also see: https://arbs.nzcer.org.nz/types-numbers
*/
export const repeat0Plus = (some) => {
const r = () => option([some, r]);
return r;
};
/**
* Repeat one or more times.
*/
export const repeat1Plus = (some) => [some, repeat0Plus(some)];
export const join1Plus = (some, separator) => [some, repeat0Plus([separator, some])];
export const join0Plus = (some, separator) => option(join1Plus(some, separator));
export const repeat = (n) => (some) => toArray(listRepeat(some)(n));
export const isEmpty = (rule) => {
const d = typeof rule === 'function' ? rule() : rule;
return d === '' || (d instanceof Array && d.length === 0);
};