RosettaCodeData/Task/Display-an-outline-as-a-nes.../JavaScript/display-an-outline-as-a-nes...

547 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(() => {
"use strict";
// ----------- NESTED TABLES FROM OUTLINE ------------
// wikiTablesFromOutline :: [String] -> String -> String
const wikiTablesFromOutline = colorSwatch =>
outline => forestFromIndentedLines(
indentLevelsFromLines(lines(outline))
)
.map(wikiTableFromTree(colorSwatch))
.join("\n\n");
// wikiTableFromTree :: [String] -> Tree String -> String
const wikiTableFromTree = colorSwatch =>
compose(
wikiTableFromRows,
levels,
paintedTree(colorSwatch),
widthLabelledTree,
ap(paddedTree(""))(treeDepth)
);
// ---------------------- TEST -----------------------
// main :: IO ()
const main = () => {
const outline = `Display an outline as a nested table.
Parse the outline to a tree,
measuring the indent of each line,
translating the indentation to a nested structure,
and padding the tree to even depth.
count the leaves descending from each node,
defining the width of a leaf as 1,
and the width of a parent node as a sum.
(The sum of the widths of its children)
and write out a table with 'colspan' values
either as a wiki table,
or as HTML.`;
return wikiTablesFromOutline([
"#ffffe6",
"#ffebd2",
"#f0fff0",
"#e6ffff",
"#ffeeff"
])(outline);
};
// --------- TREE STRUCTURE FROM NESTED TEXT ---------
// forestFromIndentedLines :: [(Int, String)] ->
// [Tree String]
const forestFromIndentedLines = tuples => {
const go = xs =>
0 < xs.length ? (() => {
// First line and its sub-tree,
const [indented, body] = Array.from(
xs[0]
),
[tree, rest] = Array.from(
span(compose(lt(indented), fst))(
tail(xs)
)
);
// followed by the rest.
return [
Node(body)(go(tree))
].concat(go(rest));
})() : [];
return go(tuples);
};
// indentLevelsFromLines :: [String] -> [(Int, String)]
const indentLevelsFromLines = xs => {
const
pairs = xs.map(
x => bimap(length)(cs => cs.join(""))(
span(isSpace)(list(x))
)
),
indentUnit = pairs.reduce(
(a, tpl) => {
const i = tpl[0];
return 0 < i ? (
i < a ? i : a
) : a;
},
Infinity
);
return [Infinity, 0].includes(indentUnit) ? (
pairs
) : pairs.map(first(n => n / indentUnit));
};
// ------------ TREE PADDED TO EVEN DEPTH ------------
// paddedTree :: a -> Tree a -> Int -> Tree a
const paddedTree = padValue =>
// All descendants expanded to same depth
// with empty nodes where needed.
node => depth => {
const go = n => tree =>
1 < n ? (() => {
const children = nest(tree);
return Node(root(tree))(
(
0 < children.length ? (
children
) : [Node(padValue)([])]
).map(go(n - 1))
);
})() : tree;
return go(depth)(node);
};
// treeDepth :: Tree a -> Int
const treeDepth = tree =>
foldTree(
() => xs => 0 < xs.length ? (
1 + maximum(xs)
) : 1
)(tree);
// ------------- SUBTREE WIDTHS MEASURED -------------
// widthLabelledTree :: Tree a -> Tree (a, Int)
const widthLabelledTree = tree =>
// A tree in which each node is labelled with
// the width of its own subtree.
foldTree(x => xs =>
0 < xs.length ? (
Node(Tuple(x)(
xs.reduce(
(a, node) => a + snd(root(node)),
0
)
))(xs)
) : Node(Tuple(x)(1))([])
)(tree);
// -------------- COLOR SWATCH APPLIED ---------------
// paintedTree :: [String] -> Tree a -> Tree (String, a)
const paintedTree = colorSwatch =>
tree => 0 < colorSwatch.length ? (
Node(
Tuple(colorSwatch[0])(root(tree))
)(
zipWith(compose(fmapTree, Tuple))(
cycle(colorSwatch.slice(1))
)(
nest(tree)
)
)
) : fmapTree(Tuple(""))(tree);
// --------------- WIKITABLE RENDERED ----------------
// wikiTableFromRows ::
// [[(String, (String, Int))]] -> String
const wikiTableFromRows = rows => {
const
cw = color => width => {
const go = w =>
1 < w ? (
`colspan=${w} `
) : "";
return `style="background:${color}; "` + (
` ${go(width)}`
);
},
cellText = ctw => {
const [color, tw] = Array.from(ctw);
const [txt, width] = Array.from(tw);
return 0 < txt.length ? (
`| ${cw(color)(width)}| ${txt}`
) : "| |";
},
classText = "class=\"wikitable\"",
styleText = "style=\"text-align:center;\"",
header = `{| ${classText} ${styleText}\n|-`,
tableBody = rows.map(
cells => cells.map(cellText).join("\n")
).join("\n|-\n");
return `${header}\n${tableBody}\n|}`;
};
// ------------------ GENERIC TREES ------------------
// Node :: a -> [Tree a] -> Tree a
const Node = v =>
// Constructor for a Tree node which connects a
// value of some kind to a list of zero or
// more child trees.
xs => ({
type: "Node",
root: v,
nest: xs || []
});
// fmapTree :: (a -> b) -> Tree a -> Tree b
const fmapTree = f => {
// A new tree. The result of a
// structure-preserving application of f
// to each root in the existing tree.
const go = t => Node(
f(t.root)
)(
t.nest.map(go)
);
return go;
};
// foldTree :: (a -> [b] -> b) -> Tree a -> b
const foldTree = f => {
// The catamorphism on trees. A summary
// value obtained by a depth-first fold.
const go = tree => f(
root(tree)
)(
nest(tree).map(go)
);
return go;
};
// levels :: Tree a -> [[a]]
const levels = tree => {
// A list of lists, grouping the root
// values of each level of the tree.
const go = (a, node) => {
const [h, ...t] = 0 < a.length ? (
a
) : [
[],
[]
];
return [
[node.root, ...h],
...node.nest.slice(0)
.reverse()
.reduce(go, t)
];
};
return go([], tree);
};
// nest :: Tree a -> [a]
const nest = tree => {
// Allowing for lazy (on-demand) evaluation.
// If the nest turns out to be a function
// rather than a list that function is applied
// here to the root, and returns a list.
const xs = tree.nest;
return "function" !== typeof xs ? (
xs
) : xs(root(tree));
};
// root :: Tree a -> a
const root = tree =>
// The value attached to a tree node.
tree.root;
// --------------------- GENERIC ---------------------
// Just :: a -> Maybe a
const Just = x => ({
type: "Maybe",
Nothing: false,
Just: x
});
// Nothing :: Maybe a
const Nothing = () => ({
type: "Maybe",
Nothing: true
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
b => ({
type: "Tuple",
"0": a,
"1": b,
length: 2
});
// apFn :: (a -> b -> c) -> (a -> b) -> (a -> c)
const ap = f =>
// Applicative instance for functions.
// f(x) applied to g(x).
g => x => f(x)(
g(x)
);
// bimap :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
const bimap = f =>
// Tuple instance of bimap.
// A tuple of the application of f and g to the
// first and second values respectively.
g => tpl => Tuple(f(tpl[0]))(
g(tpl[1])
);
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
// A function defined by the right-to-left
// composition of all the functions in fs.
fs.reduce(
(f, g) => x => f(g(x)),
x => x
);
// cycle :: [a] -> Generator [a]
const cycle = function* (xs) {
// An infinite repetition of xs,
// from which an arbitrary prefix
// may be taken.
const lng = xs.length;
let i = 0;
while (true) {
yield xs[i];
i = (1 + i) % lng;
}
};
// first :: (a -> b) -> ((a, c) -> (b, c))
const first = f =>
// A simple function lifted to one which applies
// to a tuple, transforming only its first item.
xy => {
const tpl = Tuple(f(xy[0]))(xy[1]);
return Array.isArray(xy) ? (
Array.from(tpl)
) : tpl;
};
// fst :: (a, b) -> a
const fst = tpl =>
// First member of a pair.
tpl[0];
// isSpace :: Char -> Bool
const isSpace = c =>
// True if c is a white space character.
(/\s/u).test(c);
// length :: [a] -> Int
const length = xs =>
// Returns Infinity over objects without finite
// length. This enables zip and zipWith to choose
// the shorter argument when one is non-finite,
// like cycle, repeat etc
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
xs.length
) : Infinity;
// lines :: String -> [String]
const lines = s =>
// A list of strings derived from a single
// string delimited by newline and or CR.
0 < s.length ? (
s.split(/[\r\n]+/u)
) : [];
// list :: StringOrArrayLike b => b -> [a]
const list = xs =>
// xs itself, if it is an Array,
// or an Array derived from xs.
Array.isArray(xs) ? (
xs
) : Array.from(xs || []);
// lt (<) :: Ord a => a -> a -> Bool
const lt = a =>
b => a < b;
// maximum :: Ord a => [a] -> a
const maximum = xs => (
// The largest value in a non-empty list.
ys => 0 < ys.length ? (
ys.slice(1).reduce(
(a, y) => y > a ? (
y
) : a, ys[0]
)
) : undefined
)(list(xs));
// snd :: (a, b) -> b
const snd = tpl =>
// Second member of a pair.
tpl[1];
// span :: (a -> Bool) -> [a] -> ([a], [a])
const span = p =>
// Longest prefix of xs consisting of elements which
// all satisfy p, tupled with the remainder of xs.
xs => {
const i = xs.findIndex(x => !p(x));
return -1 !== i ? (
Tuple(xs.slice(0, i))(
xs.slice(i)
)
) : Tuple(xs)([]);
};
// tail :: [a] -> [a]
const tail = xs =>
// A new list consisting of all
// items of xs except the first.
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
(ys => 0 < ys.length ? ys.slice(1) : [])(
xs
)
) : (take(1)(xs), xs);
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = n =>
// The first n elements of a list,
// string of characters, or stream.
xs => "GeneratorFunction" !== xs
.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat(...Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));
// uncons :: [a] -> Maybe (a, [a])
const uncons = xs => {
// Just a tuple of the head of xs and its tail,
// Or Nothing if xs is an empty list.
const lng = length(xs);
return (0 < lng) ? (
Infinity > lng ? (
// Finite list
Just(Tuple(xs[0])(xs.slice(1)))
) : (() => {
// Lazy generator
const nxt = take(1)(xs);
return 0 < nxt.length ? (
Just(Tuple(nxt[0])(xs))
) : Nothing();
})()
) : Nothing();
};
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
// A list with the length of the shorter of
// xs and ys, defined by zipping with a
// custom function, rather than with the
// default tuple constructor.
xs => ys => {
const n = Math.min(length(xs), length(ys));
return Infinity > n ? (
(([as, bs]) => Array.from({
length: n
}, (_, i) => f(as[i])(
bs[i]
)))([xs, ys].map(
take(n)
))
) : zipWithGen(f)(xs)(ys);
};
// zipWithGen :: (a -> b -> c) ->
// Gen [a] -> Gen [b] -> Gen [c]
const zipWithGen = f => ga => gb => {
const go = function* (ma, mb) {
let
a = ma,
b = mb;
while (!a.Nothing && !b.Nothing) {
const
ta = a.Just,
tb = b.Just;
yield f(fst(ta))(fst(tb));
a = uncons(snd(ta));
b = uncons(snd(tb));
}
};
return go(uncons(ga), uncons(gb));
};
// MAIN ---
return main();
})();