UNPKG

functionalscript

Version:

FunctionalScript is a purely functional subset of JavaScript

196 lines (195 loc) 6.42 kB
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()); };