Jump to content

Brace expansion using ranges: Difference between revisions

→‎{{header|JavaScript}}: Restored JS with fixed zsh (zsh 5.8 (x86_64-apple-darwin20.0)) expansion of {1..6..-2} -> [5,3,1]
(→‎{{header|JavaScript}}: Thanks – JS withdrawn for review.)
(→‎{{header|JavaScript}}: Restored JS with fixed zsh (zsh 5.8 (x86_64-apple-darwin20.0)) expansion of {1..6..-2} -> [5,3,1])
Line 418:
Same as Wren entry.
</pre>
 
=={{header|JavaScript}}==
<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 => {
const
expanded = braceExpandWithRange(s)
.join("\n\t");
 
return `${s} -> \n\t${expanded}`;
}
)
.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], 10)
)
);
 
return map(
compose(justifyRight(w)("0"), str)
)(
// 0 > by ? (
// enumFromThenTo(to)(to - by)(from)
// ) : enumFromThenTo(from)(
// from + (to >= from ? by : -by)
// )(to)
(
0 > by ? (
reverse
) : x => x
)(
enumFromThenTo(from)(
from + (to >= from ? abs(by) : -abs(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 someP = q =>
liftA2P(
x => xs => [x].concat(xs)
)(q)(many(q));
 
return Parser(
s => parse(
0 < s.length ? (
altP(someP(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 => 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))
] : []
) : []
);
 
// 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 manyP = q =>
altP(some(q))(pureP([]));
 
return Parser(
s => parse(
liftA2P(
x => xs => [x].concat(xs)
)(p)(manyP(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
});
 
// abs :: Num -> Num
const abs =
// Absolute value of a given number
// without the sign.
x => 0 > x ? (
-x
) : x;
 
 
// 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 = m =>
// Integer values enumerated from m to n
// with a step defined by (nxt - m).
nxt => n => {
const d = nxt - m;
 
return Array.from({
length: (Math.floor(n - nxt) / d) + 2
}, (_, i) => m + (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), 10)
) : x.codePointAt(0);
 
 
// identity :: a -> a
const identity = x =>
// The identity function. (`id`, in Haskell)
x;
 
 
// 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.");
};
 
 
// reverse :: [a] -> [a]
const reverse = xs =>
xs.slice(0).reverse();
 
 
// 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 ? (
"bigint" !== t ? (
(() => {
const [i, mx] = [x, maxBound(x)].map(fromEnum);
 
return i < mx ? (
toEnum(x)(1 + i)
) : Error("succ :: enum out of range.");
})()
) : BigInt(1) + x
) : 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.
(...args) => {
const
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

Cookies help us deliver our services. By using our services, you agree to our use of cookies.