RosettaCodeData/Task/Abbreviations-simple/JavaScript/abbreviations-simple.js

244 lines
7.1 KiB
JavaScript

(() => {
'use strict';
// withExpansions :: [(String, Int)] -> String -> String
const withExpansions = tbl => s =>
unwords(map(expanded(tbl), words(s)));
// expanded :: [(String, Int)] -> String -> String
const expanded = tbl => s => {
const
lng = s.length,
u = toUpper(s),
p = wn => {
const [w, n] = Array.from(wn);
return lng >= n && isPrefixOf(u, w);
}
return maybe(
'*error*',
fst,
0 < lng ? (
find(p, tbl)
) : Just(Tuple([], 0))
);
};
// cmdsFromString :: String -> [(String, Int)]
const cmdsFromString = s =>
fst(foldr(
(w, a) => {
const [xs, n] = Array.from(a);
return isDigit(head(w)) ? (
Tuple(xs, parseInt(w, 10))
) : Tuple(
[Tuple(toUpper(w), n)].concat(xs),
0
);
},
Tuple([], 0),
words(s)
));
// TEST -----------------------------------------------
const main = () => {
// table :: [(String, Int)]
const table = cmdsFromString(
`add 1 alter 3 backup 2 bottom 1 Cappend 2 change 1
Schange Cinsert 2 Clast 3 compress 4 copy 2 count 3 Coverlay 3
cursor 3 delete 3 Cdelete 2 down 1 duplicate 3 xEdit 1 expand 3
extract 3 find 1 Nfind 2 Nfindup 6 NfUP 3 Cfind 2 findUP 3 fUP 2
forward 2 get help 1 hexType 4 input 1 powerInput 3 join 1
split 2 spltJOIN load locate 1 Clocate 2 lowerCase 3 upperCase 3
Lprefix 2 macro merge 2 modify 3 move 2 msg next 1 overlay 1
parse preserve 4 purge 3 put putD query 1 quit read recover 3
refresh renum 3 repeat 3 replace 1 Creplace 2 reset 3 restore 4
rgtLEFT right 2 left 2 save set shift 2 si sort sos stack 3
status 4 top transfer 3 type 1 up 1`
);
return fTable(
'Abbreviation tests:\n',
s => "'" + s + "'",
s => "\n\t'" + s + "'",
withExpansions(table),
[
'riG rePEAT copies put mo rest types fup. 6 poweRin',
''
]
);
};
// GENERIC FUNCTIONS ----------------------------------
// 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
});
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (f, g) => x => f(g(x));
// find :: (a -> Bool) -> [a] -> Maybe a
const find = (p, xs) => {
for (let i = 0, lng = xs.length; i < lng; i++) {
if (p(xs[i])) return Just(xs[i]);
}
return Nothing();
};
// flip :: (a -> b -> c) -> b -> a -> c
const flip = f =>
1 < f.length ? (
(a, b) => f(b, a)
) : (x => y => f(y)(x));
// foldl1 :: (a -> a -> a) -> [a] -> a
const foldl1 = (f, xs) =>
1 < xs.length ? xs.slice(1)
.reduce(f, xs[0]) : xs[0];
// foldr :: (a -> b -> b) -> b -> [a] -> b
const foldr = (f, a, xs) => xs.reduceRight(flip(f), a);
// fst :: (a, b) -> a
const fst = tpl => tpl[0];
// fTable :: String -> (a -> String) ->
// (b -> String) -> (a -> b) -> [a] -> String
const fTable = (s, xShow, fxShow, f, xs) => {
// Heading -> x display function ->
// fx display function ->
// f -> values -> tabular string
const
ys = map(xShow, xs),
w = maximum(map(length, ys)),
rows = zipWith(
(a, b) => justifyRight(w, ' ', a) + ' -> ' + b,
ys,
map(compose(fxShow, f), xs)
);
return s + '\n' + unlines(rows);
};
// head :: [a] -> a
const head = xs => xs.length ? xs[0] : undefined;
// isDigit :: Char -> Bool
const isDigit = c => {
const n = ord(c);
return 48 <= n && 57 >= n;
};
// isPrefixOf takes two lists or strings and returns
// true iff the first is a prefix of the second.
// isPrefixOf :: [a] -> [a] -> Bool
// isPrefixOf :: String -> String -> Bool
const isPrefixOf = (xs, ys) => {
const go = (xs, ys) => {
const intX = xs.length;
return 0 < intX ? (
ys.length >= intX ? xs[0] === ys[0] && go(
xs.slice(1), ys.slice(1)
) : false
) : true;
};
return 'string' !== typeof xs ? (
go(xs, ys)
) : ys.startsWith(xs);
};
// justifyRight :: Int -> Char -> String -> String
const justifyRight = (n, cFiller, s) =>
n > s.length ? (
s.padStart(n, cFiller)
) : s;
// 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
// length :: [a] -> Int
const length = xs =>
(Array.isArray(xs) || 'string' === typeof xs) ? (
xs.length
) : Infinity;
// map :: (a -> b) -> [a] -> [b]
const map = (f, xs) =>
(Array.isArray(xs) ? (
xs
) : xs.split('')).map(f);
// maximum :: Ord a => [a] -> a
const maximum = xs =>
0 < xs.length ? (
foldl1((a, x) => x > a ? x : a, xs)
) : undefined;
// maybe :: b -> (a -> b) -> Maybe a -> b
const maybe = (v, f, m) =>
m.Nothing ? v : f(m.Just);
// ord :: Char -> Int
const ord = c => c.codePointAt(0);
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = (n, xs) =>
'GeneratorFunction' !== xs.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat.apply([], Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));
// toUpper :: String -> String
const toUpper = s => s.toLocaleUpperCase();
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// unwords :: [String] -> String
const unwords = xs => xs.join(' ');
// words :: String -> [String]
const words = s => s.split(/\s+/);
// Use of `take` and `length` here allows zipping with non-finite lists
// i.e. generators like cycle, repeat, iterate.
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = (f, xs, ys) => {
const
lng = Math.min(length(xs), length(ys)),
as = take(lng, xs),
bs = take(lng, ys);
return Array.from({
length: lng
}, (_, i) => f(as[i], bs[i], i));
};
// MAIN ---
return main();
})();