Brace expansion using ranges: Difference between revisions

→‎{{header|JavaScript}}: Thanks – JS withdrawn for review.
(Mark javascript incorrect)
(→‎{{header|JavaScript}}: Thanks – JS withdrawn for review.)
Line 418:
Same as Wren entry.
</pre>
 
=={{header|JavaScript}}==
{{incorrect|JavaScript|using simpleNumberRising{1..6..-2}.txt as input produces simpleNumberRising{1..6..-2}.txt ->. If it thinks the string is invalid it should produce simpleNumberRising{1..6..-2}.txt -> simpleNumberRising{1..6..-2}.txt. Otherwise it should produce the expansion}}
<lang javascript>(() => {
'use strict';
 
// --------------- BRACE-RANGE EXPANSION ---------------
 
// braceExpandWithRange :: String -> [String]
const braceExpandWithRange = s => {
// A list containing either the expansions
// of s, if there are any, or s itself.
const
expansions = parse(some(
braceRangeExpansion()
))(s);
return 0 < expansions.length ? (() => {
const [parsed, residue] = Array.from(
expansions[0]
);
return suffixAdd(
parsed.reduce(
uncurry(suffixMultiply),
['']
)
)([residue.join('')])
})() : [s];
};
 
 
// ----------- BRACE-RANGE EXPANSION PARSER ------------
 
// braceRangeExpansion :: [String]
const braceRangeExpansion = () =>
// List of strings expanded from a
// a unix shell {<START>..<END>} or
// {<START>..<END>..<INCR>} expression.
// See https://wiki.bash-hackers.org/syntax/expansion/brace
fmapP(([preamble, amble, postscript]) =>
suffixAdd(
suffixMultiply(preamble)(amble)
)(postscript)
)(sequenceP([
affixLeaf(),
fmapP(xs => [xs])(
between(char('{'))(char('}'))(
altP(
numericSequence()
)(
characterSequence()
)
)
),
affixLeaf()
]));
 
 
// ----------------------- TESTS -----------------------
// main :: IO ()
const main = () => {
const tests = [
'simpleNumberRising{1..3}.txt',
'simpleAlphaDescending-{Z..X}.txt',
'steppedDownAndPadded-{10..00..5}.txt',
'minusSignFlipsSequence {030..20..-5}.txt',
'combined-{Q..P}{2..1}.txt',
'emoji{🌵..🌶}{🌽..🌾}etc',
'li{teral',
'rangeless{}empty',
'rangeless{random}string'
];
 
return tests.map(
s => s + ' -> ' + '\n\t' + (
braceExpandWithRange(s).join('\n\t')
)
).join('\n\n');
};
 
 
// ----------- BRACE-RANGE COMPONENT PARSERS -----------
 
// affixLeaf :: () -> Parser String
const affixLeaf = () =>
// A sequence of literal (non-syntactic)
// characters before or after a pair of braces.
fmapP(cs => [
[cs.join('')]
])(
many(choice([noneOf('{\\'), escape()]))
);
 
 
// characterSequence :: () -> Parser [Char]
const characterSequence = () =>
// A rising or descending alphabetic
// sequence of characters.
fmapP(ab => {
const [from, to] = ab;
return from !== to ? (
enumFromThenToChar(from)(
(from < to ? succ : pred)(from)
)(to)
) : [from];
})(
ordinalRange(satisfy(
c => !'0123456789'.includes(c)
))
);
 
 
// enumerationList :: ((Bool, String), String) ->
// ((Bool, String), String) ->
// ((Bool, String), String) -> [String]
const enumerationList = triple => {
// An ordered list of numeric strings either
// rising or descending, in numeric order, and
// possibly prefixed with zeros.
const
w = padWidth(triple[0][1])(triple[1][1]),
[from, to, by] = triple.map(
sn => (sn[0] ? negate : identity)(
parseInt(sn[1])
)
);
return map(
compose(justifyRight(w)('0'), str)
)(
0 > by ? (
enumFromThenTo(to)(to - by)(from)
) : enumFromThenTo(from)(
from + (to >= from ? by : -by)
)(to)
);
};
 
 
// numericPart :: () -> Parser (Bool, String)
const numericPart = () =>
// The Bool is True if the string is
// negated by a leading '-'
// The String component contains the digits.
bindP(
option('')(char('-'))
)(sign => bindP(
some(digit())
)(ds => pureP(
Tuple(Boolean(sign))(concat(ds))
)));
 
 
// numericSequence :: () -> Parser [String]
const numericSequence = () =>
// An ascending or descending sequence
// of numeric strings, possibly
// left-padded with zeros.
fmapP(enumerationList)(sequenceP([
ordinalRange(numericPart()),
numericStep()
]));
 
 
// numericStep :: () -> Parser (Bool, Int)
const numericStep = () =>
// The size of increment for a numeric
// series. Descending if the Bool is True.
// Defaults to (False, 1).
option(Tuple(false)(1))(
bindP(
string('..')
)(_ => bindP(
numericPart()
)(pureP))
);
 
 
// ordinalRange :: Enum a =>
// Parser a -> Parser (a, a)
const ordinalRange = p =>
// A pair of enumerable values of the same
// type, representing the start and end of
// a range.
bindP(
p
)(from => bindP(
string('..')
)(_ => bindP(
p
)(compose(pureP, append([from])))));
 
 
// padWidth :: String -> String -> Int
const padWidth = cs =>
// The length of the first of cs and cs1 to
// start with a zero. Otherwise (if neither
// starts with a zero) then 0.
cs1 => [cs, cs1].reduce(
(a, x) => (0 < a) || (1 > x.length) ? (
a
) : '0' !== x[0] ? a : x.length,
0
);
 
 
// suffixAdd :: [String] -> [String] -> [String]
const suffixAdd = xs =>
ys => xs.flatMap(
flip(append)(ys)
);
 
 
// suffixMultiply :: [String] -> [String] -> [String]
const suffixMultiply = xs =>
apList(xs.map(append));
 
 
// ------------ GENERIC PARSER COMBINATORS -------------
 
// Parser :: String -> [(a, String)] -> Parser a
const Parser = f =>
// A function lifted into a Parser object.
({
type: 'Parser',
parser: f
});
 
 
// altP (<|>) :: Parser a -> Parser a -> Parser a
const altP = p =>
// p, or q if p doesn't match.
q => Parser(s => {
const xs = parse(p)(s);
return 0 < xs.length ? (
xs
) : parse(q)(s);
});
 
 
// apP <*> :: Parser (a -> b) -> Parser a -> Parser b
const apP = pf =>
// A new parser obtained by the application
// of a Parser-wrapped function,
// to a Parser-wrapped value.
p => Parser(
s => parse(pf)(s).flatMap(
vr => parse(
fmapP(vr[0])(p)
)(vr[1])
)
);
 
 
// between :: Parser open -> Parser close ->
// Parser a -> Parser a
const between = pOpen =>
// A version of p which matches between
// pOpen and pClose (both discarded).
pClose => p => bindP(
pOpen
)(_ => bindP(
p
)(x => bindP(
pClose
)(_ => pureP(x))));
 
 
// bindP (>>=) :: Parser a ->
// (a -> Parser b) -> Parser b
const bindP = p =>
// A new parser obtained by the application of
// a function to a Parser-wrapped value.
// The function must enrich its output, lifting it
// into a new Parser.
// Allows for the nesting of parsers.
f => Parser(
s => parse(p)(s).flatMap(
tpl => parse(f(tpl[0]))(tpl[1])
)
);
 
 
// char :: Char -> Parser Char
const char = x =>
// A particular single character.
satisfy(c => x == c);
 
 
// choice :: [Parser a] -> Parser a
const choice = ps =>
// A parser constructed from a
// (left to right) list of alternatives.
ps.reduce(uncurry(altP), emptyP());
 
 
// digit :: Parser Char
const digit = () =>
// A single digit.
satisfy(isDigit);
 
 
// emptyP :: () -> Parser a
const emptyP = () =>
// The empty list.
Parser(_ => []);
 
 
// escape :: Parser String
const escape = () =>
fmapP(xs => xs.join(''))(
sequenceP([char('\\'), item()])
);
 
 
// fmapP :: (a -> b) -> Parser a -> Parser b
const fmapP = f =>
// A new parser derived by the structure-preserving
// application of f to the value in p.
p => Parser(
s => parse(p)(s).flatMap(
vr => Tuple(f(vr[0]))(vr[1])
)
);
 
 
// item :: () -> Parser Char
const item = () =>
// A single character.
Parser(
s => 0 < s.length ? [
Tuple(s[0])(
s.slice(1)
)
] : []
);
 
 
// liftA2P :: (a -> b -> c) ->
// Parser a -> Parser b -> Parser c
const liftA2P = op =>
// The binary function op, lifted
// to a function over two parsers.
p => apP(fmapP(op)(p));
 
 
// many :: Parser a -> Parser [a]
const many = p => {
// Zero or more instances of p.
// Lifts a parser for a simple type of value
// to a parser for a list of such values.
const some_p = p =>
liftA2P(
x => xs => [x].concat(xs)
)(p)(many(p));
return Parser(
s => parse(
0 < s.length ? (
altP(some_p(p))(pureP([]))
) : pureP([])
)(s)
);
};
 
 
// noneOf :: String -> Parser Char
const noneOf = s =>
// Any character not found in the
// exclusion string.
satisfy(c => !s.includes(c));
 
 
// option :: a -> Parser a -> Parser a
const option = x =>
// Either p or the default value x.
p => altP(p)(pureP(x));
 
 
// parse :: Parser a -> String -> [(a, String)]
const parse = p =>
// The result of parsing s with p.
s => {
//showLog('s', s)
return p.parser([...s]);
};
 
 
// pureP :: a -> Parser a
const pureP = x =>
// The value x lifted, unchanged,
// into the Parser monad.
Parser(s => [Tuple(x)(s)]);
 
 
// satisfy :: (Char -> Bool) -> Parser Char
const satisfy = test =>
// Any character for which the
// given predicate returns true.
Parser(
s => 0 < s.length ? (
test(s[0]) ? [
Tuple(s[0])(s.slice(1))
] : []
) : []
);
 
 
// sepBy1 :: Parser a -> Parser b -> Parser [a]
const sepBy1 = p =>
// One or more occurrences of p, as
// separated by (discarded) instances of sep.
sep => bindP(
p
)(x => bindP(
many(bindP(
sep
)(_ => bindP(
p
)(pureP))))(
xs => pureP([x].concat(xs))));
 
 
// sequenceP :: [Parser a] -> Parser [a]
const sequenceP = ps =>
// A single parser for a list of values, derived
// from a list of parsers for single values.
Parser(
s => ps.reduce(
(a, q) => a.flatMap(
vr => parse(q)(snd(vr)).flatMap(
first(xs => fst(vr).concat(xs))
)
),
[Tuple([])(s)]
)
);
 
 
// some :: Parser a -> Parser [a]
const some = p => {
// One or more instances of p.
// Lifts a parser for a simple type of value
// to a parser for a list of such values.
const many_p = p =>
altP(some(p))(pureP([]));
return Parser(
s => parse(
liftA2P(
x => xs => [x].concat(xs)
)(p)(many_p(p))
)(s)
);
};
 
 
// string :: String -> Parser String
const string = s =>
// A particular string.
fmapP(cs => cs.join(''))(
sequenceP([...s].map(char))
);
 
 
// ----------------- GENERAL FUNCTIONS -----------------
 
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
b => ({
type: 'Tuple',
'0': a,
'1': b,
length: 2
});
 
 
// apList (<*>) :: [(a -> b)] -> [a] -> [b]
const apList = fs =>
// The sequential application of each of a list
// of functions to each of a list of values.
// apList([x => 2 * x, x => 20 + x])([1, 2, 3])
// -> [2, 4, 6, 21, 22, 23]
xs => fs.flatMap(f => xs.map(f));
 
 
// append (++) :: [a] -> [a] -> [a]
const append = xs =>
// A list obtained by the
// concatenation of two others.
ys => xs.concat(ys);
 
 
// 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
);
 
 
// concat :: [[a]] -> [a]
// concat :: [String] -> String
const concat = xs => (
ys => 0 < ys.length ? (
ys.every(Array.isArray) ? (
[]
) : ''
).concat(...ys) : ys
)(list(xs));
 
 
// enumFromThenTo :: Int -> Int -> Int -> [Int]
const enumFromThenTo = x1 =>
x2 => y => {
const d = x2 - x1;
return Array.from({
length: Math.floor(y - x2) / d + 2
}, (_, i) => x1 + (d * i));
};
 
 
// enumFromThenToChar :: Char -> Char -> Char -> [Char]
const enumFromThenToChar = x1 =>
x2 => y => {
const [i1, i2, iY] = Array.from([x1, x2, y])
.map(x => x.codePointAt(0)),
d = i2 - i1;
return Array.from({
length: (Math.floor(iY - i2) / d) + 2
}, (_, i) => String.fromCodePoint(i1 + (d * i)));
};
 
 
// 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 => Tuple(f(xy[0]))(
xy[1]
);
 
 
// flip :: (a -> b -> c) -> b -> a -> c
const flip = op =>
// The binary function op with
// its arguments reversed.
1 < op.length ? (
(a, b) => op(b, a)
) : (x => y => op(y)(x));
 
 
// fst :: (a, b) -> a
const fst = tpl =>
// First member of a pair.
tpl[0];
 
 
// fromEnum :: Enum a => a -> Int
const fromEnum = x =>
typeof x !== 'string' ? (
x.constructor === Object ? (
x.value
) : parseInt(Number(x))
) : x.codePointAt(0);
 
 
// identity :: a -> a
const identity = x =>
// The identity function. (`id`, in Haskell)
x;
 
 
// isAlpha :: Char -> Bool
const isAlpha = c =>
/[A-Za-z\u00C0-\u00FF]/.test(c);
 
 
// isDigit :: Char -> Bool
const isDigit = c => {
const n = c.codePointAt(0);
return 48 <= n && 57 >= n;
};
 
 
// justifyRight :: Int -> Char -> String -> String
const justifyRight = n =>
// The string s, preceded by enough padding (with
// the character c) to reach the string length n.
c => s => n > s.length ? (
s.padStart(n, c)
) : s;
 
 
// 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 || []);
 
 
// map :: (a -> b) -> [a] -> [b]
const map = f =>
// The list obtained by applying f
// to each element of xs.
// (The image of xs under f).
xs => [...xs].map(f);
 
 
// maxBound :: a -> a
const maxBound = x => {
const e = x.enum;
return Boolean(e) ? (
e[e[x.max]]
) : {
'number': Number.MAX_SAFE_INTEGER,
'string': String.fromCodePoint(0x10FFFF),
'boolean': true
} [typeof x];
};
 
// minBound :: a -> a
const minBound = x => {
const e = x.enum;
return Boolean(e) ? (
e[e[0]]
) : {
'number': Number.MIN_SAFE_INTEGER,
'string': String.fromCodePoint(0),
'boolean': false
} [typeof x];
};
 
 
// negate :: Num -> Num
const negate = n =>
-n;
 
 
// pred :: Enum a => a -> a
const pred = x => {
const t = typeof x;
return 'number' !== t ? (() => {
const [i, mn] = [x, minBound(x)].map(fromEnum);
return i > mn ? (
toEnum(x)(i - 1)
) : Error('succ :: enum out of range.');
})() : x > Number.MIN_SAFE_INTEGER ? (
x - 1
) : Error('succ :: Num out of range.');
};
 
 
// showLog :: a -> IO ()
const showLog = (...args) =>
console.log(
args
.map(JSON.stringify)
.join(' -> ')
);
 
 
// snd :: (a, b) -> b
const snd = tpl =>
// Second member of a pair.
tpl[1];
 
 
// str :: a -> String
const str = x =>
Array.isArray(x) && x.every(
v => ('string' === typeof v) && (1 === v.length)
) ? (
x.join('')
) : x.toString();
 
 
// succ :: Enum a => a -> a
const succ = x => {
const t = typeof x;
return 'number' !== t ? (() => {
const [i, mx] = [x, maxBound(x)].map(fromEnum);
return i < mx ? (
toEnum(x)(1 + i)
) : Error('succ :: enum out of range.')
})() : x < Number.MAX_SAFE_INTEGER ? (
1 + x
) : Error('succ :: Num out of range.')
};
 
 
// toEnum :: a -> Int -> a
const toEnum = e =>
// The first argument is a sample of the type
// allowing the function to make the right mapping
x => ({
'number': Number,
'string': String.fromCodePoint,
'boolean': Boolean,
'object': v => e.min + v
} [typeof e])(x);
 
 
// uncurry :: (a -> b -> c) -> ((a, b) -> c)
const uncurry = f =>
// A function over a pair, derived
// from a curried function.
function () {
const
args = arguments,
xy = Boolean(args.length % 2) ? (
args[0]
) : args;
return f(xy[0])(xy[1]);
};
 
// MAIN ---
return main();
})();</lang>
{{Out}}
<pre>simpleNumberRising{1..3}.txt ->
simpleNumberRising1.txt
simpleNumberRising2.txt
simpleNumberRising3.txt
 
simpleAlphaDescending-{Z..X}.txt ->
simpleAlphaDescending-Z.txt
simpleAlphaDescending-Y.txt
simpleAlphaDescending-X.txt
 
steppedDownAndPadded-{10..00..5}.txt ->
steppedDownAndPadded-10.txt
steppedDownAndPadded-05.txt
steppedDownAndPadded-00.txt
 
minusSignFlipsSequence {030..20..-5}.txt ->
minusSignFlipsSequence 020.txt
minusSignFlipsSequence 025.txt
minusSignFlipsSequence 030.txt
 
combined-{Q..P}{2..1}.txt ->
combined-Q2.txt
combined-Q1.txt
combined-P2.txt
combined-P1.txt
 
emoji{🌵..🌶}{🌽..🌾}etc ->
emoji🌵🌽etc
emoji🌵🌾etc
emoji🌶🌽etc
emoji🌶🌾etc
 
li{teral ->
li{teral
 
rangeless{}empty ->
rangeless{}empty
 
rangeless{random}string ->
rangeless{random}string</pre>
 
=={{header|Julia}}==
9,655

edits