functionalscript
Version:
FunctionalScript is a purely functional subset of JavaScript
196 lines (195 loc) • 6.42 kB
JavaScript
import { fold } from "../../types/list/module.f.js";
import * as string from "../../types/string/module.f.js";
const { concat } = string;
import { flat, flatMap, map, concat as listConcat } from "../../types/list/module.f.js";
const { entries } = Object;
import * as f from "../../types/function/module.f.js";
const { compose, fn } = f;
import { serialize as bigintSerialize } from "../../types/bigint/module.f.js";
import * as serializer from "../../json/serializer/module.f.js";
const { objectWrap, arrayWrap, stringSerialize, numberSerialize, nullSerialize, boolSerialize } = serializer;
const colon = [':'];
export const undefinedSerialize = ['undefined'];
const getConstantsOp = djs => state => {
switch (typeof djs) {
case 'boolean': {
return state;
}
case 'number':
case 'string':
case 'bigint': {
return getConstantSelf(djs)(state);
}
default: {
if (djs === null) {
return state;
}
if (djs === undefined) {
return state;
}
if (djs instanceof Array) {
return getConstantSelf(djs)(fold(getConstantsOp)(state)(djs));
}
return getConstantSelf(djs)(fold(getConstantsOp)(state)(map(entryValue)(entries(djs))));
}
}
};
const getConstantSelf = djs => state => {
const refs = state.refs;
const refCounter = refs.get(djs);
if (refCounter !== undefined && refCounter[1] > 1 && !refCounter[2]) {
refCounter[2] = true;
refs.set(djs, refCounter);
return { refs, consts: { head: state.consts, tail: [djs] } };
}
return state;
};
const getConstants = djs => refs => {
return getConstantsOp(djs)(refs);
};
const entryValue = kv => kv[1];
export const serializeWithoutConst = sort => {
const propertySerialize = ([k, v]) => flat([
stringSerialize(k),
colon,
f(v)
]);
const mapPropertySerialize = map(propertySerialize);
const objectSerialize = fn(entries)
.then(sort)
.then(mapPropertySerialize)
.then(objectWrap)
.result;
const f = value => {
switch (typeof value) {
case 'boolean': {
return boolSerialize(value);
}
case 'number': {
return numberSerialize(value);
}
case 'string': {
return stringSerialize(value);
}
case 'bigint': {
return [bigintSerialize(value)];
}
default: {
if (value === null) {
return nullSerialize;
}
if (value === undefined) {
return undefinedSerialize;
}
if (value instanceof Array) {
return arraySerialize(value);
}
return objectSerialize(value);
}
}
};
const arraySerialize = compose(map(f))(arrayWrap);
return f;
};
const serializeWithConst = sort => refs => root => {
const propertySerialize = ([k, v]) => flat([
stringSerialize(k),
colon,
f(v)
]);
const mapPropertySerialize = map(propertySerialize);
const objectSerialize = fn(entries)
.then(sort)
.then(mapPropertySerialize)
.then(objectWrap)
.result;
const f = value => {
if (value !== root) {
const refCounter = refs.get(value);
if (refCounter !== undefined && refCounter[1] > 1) {
return [`c${refCounter[0]}`];
}
}
switch (typeof value) {
case 'boolean': {
return boolSerialize(value);
}
case 'number': {
return numberSerialize(value);
}
case 'string': {
return stringSerialize(value);
}
case 'bigint': {
return [bigintSerialize(value)];
}
default: {
if (value === null) {
return nullSerialize;
}
if (value === undefined) {
return undefinedSerialize;
}
if (value instanceof Array) {
return arraySerialize(value);
}
return objectSerialize(value);
}
}
};
const arraySerialize = compose(map(f))(arrayWrap);
return f;
};
const countRefsOp = djs => refs => {
switch (typeof djs) {
case 'boolean':
case 'number': {
return refs;
}
case 'string':
case 'bigint': {
return addRef(djs)(refs);
}
default: {
switch (djs) {
case null:
case undefined: {
return refs;
}
}
if (djs instanceof Array) {
if (refs.has(djs))
return addRef(djs)(refs);
return addRef(djs)(fold(countRefsOp)(refs)(djs));
}
if (refs.has(djs))
return addRef(djs)(refs);
return addRef(djs)(fold(countRefsOp)(refs)(map(entryValue)(entries(djs))));
}
}
};
const addRef = djs => refs => {
const refCounter = refs.get(djs);
if (refCounter === undefined) {
return refs.set(djs, [refs.size, 1, false]);
}
return refs.set(djs, [refCounter[0], refCounter[1] + 1, false]);
};
export const stringify = sort => djs => {
const refs = countRefs(djs);
const consts = getConstants(djs)({ refs, consts: [] }).consts;
const constSerialize = entry => {
const refCounter = refs.get(entry);
if (refCounter === undefined) {
throw 'unexpected behaviour';
}
return flat([['const c'], numberSerialize(refCounter[0]), [' = '], serializeWithConst(sort)(refs)(entry)(entry), ['\n']]);
};
const constStrings = flatMap(constSerialize)(consts);
const rootStrings = listConcat(['export default '])(serializeWithConst(sort)(refs)(djs)(djs));
return concat(listConcat(constStrings)(rootStrings));
};
export const stringifyAsTree = sort => compose(serializeWithoutConst(sort))(concat);
export const countRefs = djs => {
return countRefsOp(djs)(new Map());
};