Brace expansion using ranges: Difference between revisions

→‎{{header|jq}}: right-most-first
m (→‎{{header|Raku}}: Now I'm just twiddling because I can)
(→‎{{header|jq}}: right-most-first)
 
(46 intermediate revisions by 12 users not shown)
Line 1:
{{draft task}}{{clarified-review}}
 
;Task
{{task heading}}
 
Write and test a function which expands one or more Unix-style '''numeric and alphabetic range braces''' embedded in a larger string.<br><br>
Line 54:
{{task heading|Tests}}
 
Generate and display here the expansion of (at least) each of the nineten example lines shown abovebelow.
 
The JavaScript implementation below uses parser combinators, aiming to encode a more or less full and legible description of the <pre><PREAMBLE><AMBLE><POSTSCRIPT></pre> range brace grammar, but you should use any resource that suggests itself in your language, including parser libraries.
Line 60:
(The grammar of range expansion, unlike that of nested list expansion, is not recursive, so even regular expressions should prove serviceable here).
 
The output of the JS implementation, which aims to match the brace expansion behaviour of the default '''zsh''' shell on macOScurrent Catalinaversions isof macOS:
 
<pre>simpleNumberRising{1..3}.txt ->
Line 81:
minusSignFlipsSequence 025.txt
minusSignFlipsSequence 030.txt
 
reverseSteppedNumberRising{1..6..-2}.txt ->
reverseSteppedNumberRising5.txt
reverseSteppedNumberRising3.txt
reverseSteppedNumberRising1.txt
 
combined-{Q..P}{2..1}.txt ->
Line 103 ⟶ 108:
rangeless{random}string</pre>
 
{{Template:Strings}}
;Related tasks:
* &nbsp; [[Range_expansion|range expansion]]
 
:* &nbsp; [[Brace_expansion]]
:* &nbsp; [[Range_expansion]]
<br><br>
=={{header|11l}}==
{{trans|Nim}}
 
<syntaxhighlight lang="11l">F intFromString(s) -> Int?
X.try
R Int(s)
X.catch ValueError
R N
 
F parseRange(r)
I r.empty {R [‘{}’]}
V sp = r.split(‘..’)
I sp.len == 1 {R [‘{’r‘}’]}
V first = sp[0]
V last = sp[1]
V incr = I sp.len == 2 {‘1’} E sp[2]
 
Int? val1 = intFromString(first)
Int? val2 = intFromString(last)
Int? val3 = intFromString(incr)
 
I val3 == N {R [‘{’r‘}’]}
V n3 = val3
V numeric = val1 != N & val2 != N
 
Int n1
Int n2
I numeric
n1 = val1
n2 = val2
E
I (val1 != N & val2 == N) | (val1 == N & val2 != N)
R [‘{’r‘}’]
I first.len != 1 | last.len != 1
R [‘{’r‘}’]
n1 = first[0].code
n2 = last[0].code
 
V width = 1
I numeric
width = max(first.len, last.len)
 
I n3 == 0
R I numeric {[String(n1).zfill(width)]} E [first]
 
V asc = n1 < n2
I n3 < 0
asc = !asc
swap(&n1, &n2)
n3 = -n3
 
[String] result
V i = n1
I asc
L i <= n2
result.append(I numeric {String(i).zfill(width)} E Char(code' i))
i += n3
E
L i >= n2
result.append(I numeric {String(i).zfill(width)} E Char(code' i))
i -= n3
R result
 
F rangeExpand(s)
V result = [‘’]
V rng = ‘’
V inRng = 0B
 
L(c) s
I c == ‘{’ & !inRng
inRng = 1B
rng = ‘’
E I c == ‘}’ & inRng
V rngRes = parseRange(rng)
[String] res
L(r) result
L(rr) rngRes
res.append(r‘’rr)
result = move(res)
inRng = 0B
E I inRng
rng ‘’= c
E
L(&s) result
s ‘’= c
 
I inRng
L(&s) result
s ‘’= ‘{’rng
R result
 
-V examples = [‘simpleNumberRising{1..3}.txt’,
‘simpleAlphaDescending-{Z..X}.txt’,
‘steppedDownAndPadded-{10..00..5}.txt’,
‘minusSignFlipsSequence {030..20..-5}.txt’,
‘combined-{Q..P}{2..1}.txt’,
‘li{teral’,
‘rangeless{}empty’,
‘rangeless{random}string’,
‘mixedNumberAlpha{5..k}’,
‘steppedAlphaRising{P..Z..2}.txt’,
‘stops after endpoint-{02..10..3}.txt’]
 
L(s) examples
print(s" ->\n ", end' ‘’)
V res = rangeExpand(s)
print(res.join("\n "))
print()</syntaxhighlight>
 
{{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
 
li{teral ->
li{teral
 
rangeless{}empty ->
rangeless{}empty
 
rangeless{random}string ->
rangeless{random}string
 
mixedNumberAlpha{5..k} ->
mixedNumberAlpha{5..k}
 
steppedAlphaRising{P..Z..2}.txt ->
steppedAlphaRisingP.txt
steppedAlphaRisingR.txt
steppedAlphaRisingT.txt
steppedAlphaRisingV.txt
steppedAlphaRisingX.txt
steppedAlphaRisingZ.txt
 
stops after endpoint-{02..10..3}.txt ->
stops after endpoint-02.txt
stops after endpoint-05.txt
stops after endpoint-08.txt
 
</pre>
=={{header|AutoHotkey}}==
<syntaxhighlight lang="autohotkey">Brace_expansion_using_ranges(line){
needle := "^.*\K{(?P<Start>[^{}]+?)\..(?P<End>[^{}]+?)(?:\..(?P<Incr>[^{}]+?))?}"
while true
{
while pos := RegExMatch(line, needle, m, A_Index=1?1:pos+StrLen(m))
{
char := false, step := "", output := ""
reverse := InStr(mIncr, "-") ? true : false
if mStart is number
pad1 := pad(mStart), pad2 := pad(mEnd), pad := StrLen(pad1)>=StrLen(pad2) ? pad1 : pad2
else
mStart := Ord(mStart), mEnd := Ord(mEnd), char := true
mIncr := (mIncr?Abs(mIncr):1) * (mStart>mEnd?-1:1)
loop % Abs((mStart-mEnd)/mIncr) + 1
{
step := mStart + (A_Index-1) * mIncr
step := pad <> "" ? SubStr(pad . step, 1-StrLen(pad)) : step
step := char ? Chr(step) : step
Rep := StrReplace(line, m, step)
output := reverse ? rep "`n" output : output .= Rep "`n"
}
output := Trim(Output, "`n")
}
if RegExMatch(output, needle)
line := output
else
break
}
return output ? output : line
}
pad(num){
if RegExMatch(num, "`am)^(0+)(?=[1-9]|0$)", m)
loop % StrLen(num)
pad .= "0"
return pad
}</syntaxhighlight>
Examples:<syntaxhighlight lang="autohotkey">data=
(
simpleNumberRising{1..3}.txt
simpleAlphaDescending-{Z..X}.txt
steppedDownAndPadded-{10..00..5}.txt
minusSignFlipsSequence {030..20..-5}.txt
reverseSteppedNumberRising{1..6..-2}.txt
combined-{Q..P}{2..1}.txt
emoji{🌵..🌶}{🌽..🌾}etc
li{teral
rangeless{}empty
rangeless{random}string
)
for i, line in StrSplit(data, "`n", "`r")
result .= line " ->`n" RegExReplace(Brace_expansion_using_ranges(line), "`am)^", "`t") "`n`n"
 
MsgBox, 262144, , % result
return</syntaxhighlight>
{{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
 
reverseSteppedNumberRising{1..6..-2}.txt ->
reverseSteppedNumberRising5.txt
reverseSteppedNumberRising3.txt
reverseSteppedNumberRising1.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|F_Sharp|F#}}==
<syntaxhighlight lang="fsharp">
// Brace expansion using ranges. Nigel Galloway: October 6th., 2021
let fUC, fUR=System.Text.Rune.GetUnicodeCategory,(fun n->System.Text.Rune.GetRuneAt(n,0))
let fV(n,i,g,e,l,s)=let l=if l="" then 1 else int l in match l with 0->None |_->Some(n,i,g,e,int l,s)
let(|Valid|_|)(n:System.Text.RegularExpressions.Match)=let fN(g:string)=n.Groups.[g].Value in if n.Success then fV(fN "n",fN "i",fN "g",fN "e",fN "l",fN "s") else None
let fN(g:string)=let mutable g=g.EnumerateRunes() in if g.MoveNext() && not(g.MoveNext()) then true else false
let(|I|_|)(n,g)=if fN n && fN g then (let n,g=fUR n,fUR g in if fUC n=fUC g then Some(n,g) else None) else None
let(|G|_|)(n:string,g:string)=try let n,g=(int n,int g) in Some(n,g) with _->None
let(|E|_|)(n:string,g:string)=if n.[0]='0' || g.[0]='0' then match (n,g) with G(e,l)->Some(e,l,max n.Length g.Length) |_->None else None
let fL n=let fN i g e l=let n=[i..(if i>g then -l else l)..g] in if e="-" then List.rev n else n
let fG n g=let n,buf=string n, System.Text.StringBuilder() in (for _ in 1..g-n.Length do buf.Append 0); buf.Append n; buf.ToString()
match System.Text.RegularExpressions.Regex.Match(n,@"^(?<n>.*?){(?<i>.*?)\.\.(?<g>.*?)(\.\.(?<e>[-]+)?(?<l>[0-9]*?))?}(?<s>.*)$") with
Valid(n,i,g,e,l,s)->match (i,g) with I(i,g)->Some(fN i.Value g.Value e l|>Seq.map(fun g->sprintf "%s%A%s" n (System.Text.Rune(g)) s))
|E(i,g,z)->Some(fN i g e l|>Seq.map(fun g->sprintf "%s%s%s" n (fG g z) s))
|G(i,g)->Some(fN i g e l|>Seq.map(fun g->sprintf "%s%A%s" n (string g) s)) |_->None
|_->None
let rec expBraces n=seq{match fL n with Some n->yield!(n|>Seq.collect(expBraces)) |_->yield n}
let tests=["simpleNumberRising{1..3}.txt";"steppedNumberRising{1..6..2}.txt";"reverseSteppedNumberRising{1..6..-2}.txt";"steppedNumberDescending{20..9..2}.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{random}string";"rangeless{}empty";"steppedAlphaDescending-{Z..M..2}.txt";"reversedSteppedAlphaDescending-{Z..M..-2}.txt"]
tests|>List.iter(fun g->printfn $"%s{g}->"; for n in expBraces g do printfn $" %s{n}")
</syntaxhighlight>
{{out}}
<pre>
simpleNumberRising{1..3}.txt->
simpleNumberRising1.txt
simpleNumberRising2.txt
simpleNumberRising3.txt
steppedNumberRising{1..6..2}.txt->
steppedNumberRising1.txt
steppedNumberRising3.txt
steppedNumberRising5.txt
reverseSteppedNumberRising{1..6..-2}.txt->
reverseSteppedNumberRising5.txt
reverseSteppedNumberRising3.txt
reverseSteppedNumberRising1.txt
steppedNumberDescending{20..9..2}.txt->
steppedNumberDescending"20".txt
steppedNumberDescending"18".txt
steppedNumberDescending"16".txt
steppedNumberDescending"14".txt
steppedNumberDescending"12".txt
steppedNumberDescending"10".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{random}string->
rangeless{random}string
rangeless{}empty->
rangeless{}empty
steppedAlphaDescending-{Z..M..2}.txt->
steppedAlphaDescending-Z.txt
steppedAlphaDescending-X.txt
steppedAlphaDescending-V.txt
steppedAlphaDescending-T.txt
steppedAlphaDescending-R.txt
steppedAlphaDescending-P.txt
steppedAlphaDescending-N.txt
reversedSteppedAlphaDescending-{Z..M..-2}.txt->
reversedSteppedAlphaDescending-N.txt
reversedSteppedAlphaDescending-P.txt
reversedSteppedAlphaDescending-R.txt
reversedSteppedAlphaDescending-T.txt
reversedSteppedAlphaDescending-V.txt
reversedSteppedAlphaDescending-X.txt
reversedSteppedAlphaDescending-Z.txt
</pre>
=={{header|Go}}==
{{trans|Wren}}
<syntaxhighlight lang="go">package main
 
import (
"fmt"
"strconv"
"strings"
"unicode/utf8"
)
 
func sign(n int) int {
switch {
case n < 0:
return -1
case n > 0:
return 1
}
return 0
}
 
func abs(n int) int {
if n < 0 {
return -n
}
return n
}
 
func parseRange(r string) []string {
if r == "" {
return []string{"{}"} // rangeless, empty
}
sp := strings.Split(r, "..")
if len(sp) == 1 {
return []string{"{" + r + "}"} // rangeless, random value
}
sta := sp[0]
end := sp[1]
inc := "1"
if len(sp) > 2 {
inc = sp[2]
}
n1, ok1 := strconv.Atoi(sta)
n2, ok2 := strconv.Atoi(end)
n3, ok3 := strconv.Atoi(inc)
if ok3 != nil {
return []string{"{" + r + "}"} // increment isn't a number
}
numeric := (ok1 == nil) && (ok2 == nil)
if !numeric {
if (ok1 == nil && ok2 != nil) || (ok1 != nil && ok2 == nil) {
return []string{"{" + r + "}"} // mixed numeric/alpha not expanded
}
if utf8.RuneCountInString(sta) != 1 || utf8.RuneCountInString(end) != 1 {
return []string{"{" + r + "}"} // start/end are not both single alpha
}
n1 = int(([]rune(sta))[0])
n2 = int(([]rune(end))[0])
}
width := 1
if numeric {
if len(sta) < len(end) {
width = len(end)
} else {
width = len(sta)
}
}
if n3 == 0 { // zero increment
if numeric {
return []string{fmt.Sprintf("%0*d", width, n1)}
} else {
return []string{sta}
}
}
var res []string
asc := n1 < n2
if n3 < 0 {
asc = !asc
t := n1
d := abs(n1-n2) % (-n3)
n1 = n2 - d*sign(n2-n1)
n2 = t
n3 = -n3
}
i := n1
if asc {
for ; i <= n2; i += n3 {
if numeric {
res = append(res, fmt.Sprintf("%0*d", width, i))
} else {
res = append(res, string(rune(i)))
}
}
} else {
for ; i >= n2; i -= n3 {
if numeric {
res = append(res, fmt.Sprintf("%0*d", width, i))
} else {
res = append(res, string(rune(i)))
}
}
}
return res
}
 
func rangeExpand(s string) []string {
res := []string{""}
rng := ""
inRng := false
for _, c := range s {
if c == '{' && !inRng {
inRng = true
rng = ""
} else if c == '}' && inRng {
rngRes := parseRange(rng)
rngLen := len(rngRes)
var res2 []string
for i := 0; i < len(res); i++ {
for j := 0; j < rngLen; j++ {
res2 = append(res2, res[i]+rngRes[j])
}
}
res = res2
inRng = false
} else if inRng {
rng += string(c)
} else {
for i := 0; i < len(res); i++ {
res[i] += string(c)
}
}
}
if inRng {
for i := 0; i < len(res); i++ {
res[i] += "{" + rng // unmatched braces
}
}
return res
}
 
func main() {
examples := []string{
"simpleNumberRising{1..3}.txt",
"simpleAlphaDescending-{Z..X}.txt",
"steppedDownAndPadded-{10..00..5}.txt",
"minusSignFlipsSequence {030..20..-5}.txt",
"reverseSteppedNumberRising{1..6..-2}.txt",
"combined-{Q..P}{2..1}.txt",
"emoji{🌵..🌶}{🌽..🌾}etc",
"li{teral",
"rangeless{}empty",
"rangeless{random}string",
"mixedNumberAlpha{5..k}",
"steppedAlphaRising{P..Z..2}.txt",
"stops after endpoint-{02..10..3}.txt",
"steppedNumberRising{1..6..2}.txt",
"steppedNumberDescending{20..9..2}",
"steppedAlphaDescending-{Z..M..2}.txt",
"reversedSteppedAlphaDescending-{Z..M..-2}.txt",
}
for _, s := range examples {
fmt.Print(s, "->\n ")
res := rangeExpand(s)
fmt.Println(strings.Join(res, "\n "))
fmt.Println()
}
}</syntaxhighlight>
 
{{out}}
<pre>
Same as Wren entry.
</pre>
=={{header|JavaScript}}==
<langsyntaxhighlight lang="javascript">(() => {
'"use strict'";
 
// --------------- BRACE-RANGE EXPANSION ---------------
 
// braceExpandWithRange :: String -> [String]
Line 123 ⟶ 650:
braceRangeExpansion()
))(s);
 
return 0 < expansions.length ? (() => {
const [parsed, residue] = Array.from(expansions[0];
 
expansions[0]
);
return suffixAdd(
parsed.reduce(
uncurry(suffixMultiply),
[''""]
)
)([residue.join(''"")]);
})() : [s];
};
 
 
// ----------- BRACE-RANGE EXPANSION PARSER ------------
 
// braceRangeExpansion :: [String]
Line 152 ⟶ 679:
affixLeaf(),
fmapP(xs => [xs])(
between(char('"{'"))(char('"}'"))(
altP(
numericSequence()
Line 164 ⟶ 691:
 
 
// ----------------------- 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-"reverseSteppedNumberRising{Q1..P}{26..1-2}.txt'",
'emoji"combined-{🌵Q..🌶P}{🌽2..🌾1}etc'.txt",
'li"emoji{teral'🌵..🌶}{🌽..🌾}etc",
'rangeless"li{}empty'teral",
'"rangeless{random}string'empty",
"rangeless{random}string"
];
 
return tests.map(s => {
s => s + ' -> ' + '\n\t' + (const
expanded = braceExpandWithRange(s).join('\n\t')
.join("\n\t");
 
).join('\n\n');
return `${s} -> \n\t${expanded}`;
})
.join("\n\n");
};
 
 
// ----------- BRACE-RANGE COMPONENT PARSERS -----------
 
// affixLeaf :: () -> Parser String
Line 194 ⟶ 725:
// characters before or after a pair of braces.
fmapP(cs => [
[cs.join(''"")]
])(
many(choice([noneOf('"{\\'"), escape()]))
);
 
Line 206 ⟶ 737:
fmapP(ab => {
const [from, to] = ab;
 
return from !== to ? (
enumFromThenToChar(from)(
Line 213 ⟶ 745:
})(
ordinalRange(satisfy(
c => !'"0123456789'".includes(c)
))
);
Line 229 ⟶ 761:
[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)(to0 -> by) ? (from)
) : enumFromThenTo(from)( reverse
from + (to >= from ? by) : -by)identity
)(to)
enumFromThenTo(from)(
from + (
to < from ? (
-abs(by)
) : abs(by)
)
)(to)
)
);
};
Line 250 ⟶ 791:
// The String component contains the digits.
bindP(
option(''"")(char('"-'"))
)(sign => bindP(
some(digit())
Line 276 ⟶ 817:
option(Tuple(false)(1))(
bindP(
string('"..'")
)(_() => bindP(
numericPart()
)(pureP))
Line 292 ⟶ 833:
p
)(from => bindP(
string('"..'")
)(_() => bindP(
p
)(compose(pureP, append([from])))));
Line 306 ⟶ 847:
(a, x) => (0 < a) || (1 > x.length) ? (
a
) : '"0'" !== x[0] ? a : x.length,
0
);
Line 323 ⟶ 864:
 
 
// ------------ GENERIC PARSER COMBINATORS -------------
 
// Parser :: String -> [(a, String)] -> Parser a
Line 329 ⟶ 870:
// A function lifted into a Parser object.
({
type: '"Parser'",
parser: f
});
Line 339 ⟶ 880:
q => Parser(s => {
const xs = parse(p)(s);
 
return 0 < xs.length ? (
xs
Line 352 ⟶ 894:
p => Parser(
s => parse(pf)(s).flatMap(
vr([v, r]) => parse(
fmapP(vr[0]v)(p)
)(vr[1]r)
)
);
Line 366 ⟶ 908:
pClose => p => bindP(
pOpen
)(_() => bindP(
p
)(x => bindP(
pClose
)(_() => pureP(x))));
 
 
Line 383 ⟶ 925:
f => Parser(
s => parse(p)(s).flatMap(
tpl([x, r]) => parse(f(tpl[0]x))(tpl[1]r)
)
);
Line 391 ⟶ 933:
const char = x =>
// A particular single character.
satisfy(c => x === c);
 
 
Line 410 ⟶ 952:
const emptyP = () =>
// The empty list.
Parser(_() => []);
 
 
// escape :: Parser String
const escape = () =>
fmapP(xs => xs.join(''""))(
sequenceP([char('"\\'"), item()])
);
 
Line 426 ⟶ 968:
p => Parser(
s => parse(p)(s).flatMap(
vr => Tuplefirst(f(vr[0]))(vr[1])
)
);
Line 434 ⟶ 976:
const item = () =>
// A single character.
Parser(s => {
sconst =>[h, 0...t] <= s.length ? [;
 
Tuple(s[0])(
return Boolean(h) ? s.slice(1)[
Tuple(h)(t)
] : [];
});
 
 
Line 456 ⟶ 998:
// Lifts a parser for a simple type of value
// to a parser for a list of such values.
const some_psomeP = pq =>
liftA2P(
x => xs => [x].concat(xs)
)(pq)(many(pq));
 
return Parser(
s => parse(
0 < s.length ? (
altP(some_psomeP(p))(pureP([]))
) : pureP([])
)(s)
Line 486 ⟶ 1,029:
const parse = p =>
// The result of parsing s with p.
s => {p.parser([...s]);
//showLog('s', s)
return p.parser([...s]);
};
 
 
Line 503 ⟶ 1,043:
// Any character for which the
// given predicate returns true.
Parser(s => {
sconst =>[h, 0...t] <= s.length ? (;
 
test(s[0]) ? [
return Boolean(h) ? Tuple(s[0])(s.slice(1))
test(h) ? [
Tuple(h)(t)
] : []
) : [];
});
 
 
// 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]
Line 534 ⟶ 1,060:
s => ps.reduce(
(a, q) => a.flatMap(
vr([v, r]) => parse(q)(snd(vr)r).flatMap(
first(xs => fst(vr)v.concat(xs))
)
),
Line 548 ⟶ 1,074:
// Lifts a parser for a simple type of value
// to a parser for a list of such values.
const many_pmanyP = pq =>
altP(some(pq))(pureP([]));
 
return Parser(
s => parse(
liftA2P(
x => xs => [x].concat(xs)
)(p)(many_pmanyP(p))
)(s)
);
Line 563 ⟶ 1,090:
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,
*[Symbol.iterator]() {
for (const k in this) {
if (!isNaN(k)) {
yield this[k];
}
}
}
});
 
 
// abs :: Num -> Num
const abs =
// Absolute value of a given number
// without the sign.
x => 0 > x ? (
-x
) : x;
 
 
Line 607 ⟶ 1,150:
 
// concat :: [[a]] -> [a]
//const concat ::= [String]xs -=> String
const concat = xs => .flat(1);
ys => 0 < ys.length ? (
ys.every(Array.isArray) ? (
[]
) : ''
).concat(...ys) : ys
)(list(xs));
 
 
// enumFromThenTo :: Int -> Int -> Int -> [Int]
const enumFromThenTo = x1m =>
x2// =>Integer yvalues =>enumerated {from m to n
// with a step constdefined dby = x2(nxt - x1;m).
nxt => n => {
const d = nxt - m;
 
return Array.from({
length: (Math.floor(yn - x2nxt) / d) + 2
}, (_, i) => x1m + (d * i));
};
 
Line 633 ⟶ 1,173:
.map(x => x.codePointAt(0)),
d = i2 - i1;
 
return Array.from({
length: (Math.floor(iY - i2) / d) + 2
Line 643 ⟶ 1,184:
// A simple function lifted to one which applies
// to a tuple, transforming only its first item.
xy([x, y]) => Tuple(f(xy[0]x))(y);
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);
 
Line 676 ⟶ 1,209:
// The identity function. (`id`, in Haskell)
x;
 
 
// isAlpha :: Char -> Bool
const isAlpha = c =>
/[A-Za-z\u00C0-\u00FF]/.test(c);
 
 
Line 686 ⟶ 1,214:
const isDigit = c => {
const n = c.codePointAt(0);
 
return 48 <= n && 57 >= n;
};
Line 697 ⟶ 1,226:
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 || []);
 
 
Line 719 ⟶ 1,239:
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];
};
Line 731 ⟶ 1,252:
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];
};
Line 749 ⟶ 1,271:
const pred = x => {
const t = typeof x;
 
return 'number' !== t ? (() => {
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.'");
};
 
 
// showLogreverse :: [a] -> IO ()[a]
const showLogreverse = (...args)xs =>
consolexs.logslice(0).reverse();
args
.map(JSON.stringify)
.join(' -> ')
);
 
 
// snd :: (a, b) -> b
const snd = tpl =>
// Second member of a pair.
tpl[1];
 
 
Line 778 ⟶ 1,292:
const str = x =>
Array.isArray(x) && x.every(
v => ('"string'" === typeof v) && (1 === v.length)
) ? (
x.join(''"")
) : x.toString();
 
Line 787 ⟶ 1,301:
const succ = x => {
const t = typeof x;
 
return 'number' !== t ? (() => {
return "number" !== t ? (
const [i, mx] = [x, maxBound(x)].map(fromEnum);
return(() i=> < mx ? ({
toEnumconst [i, mx] = [x, maxBound(x)].map(1 + i)
) : Error('succ :: enum out of range.') fromEnum
})() : x < Number.MAX_SAFE_INTEGER ? ( );
 
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.'");
};
 
Line 803 ⟶ 1,323:
// allowing the function to make the right mapping
x => ({
'"number'": Number,
'"string'": String.fromCodePoint,
'"boolean'": Boolean,
'"object'": v => e.min + v
} [typeof e])(x);
 
Line 814 ⟶ 1,334:
// A function over a pair, derived
// from a curried function.
function (...args) => {
const
args = arguments,
xy = Boolean(args.length % 2) ? (
args[0]
) : args;
 
return f(xy[0])(xy[1]);
};
Line 825 ⟶ 1,345:
// MAIN ---
return main();
})();</langsyntaxhighlight>
{{Out}}
<pre>simpleNumberRising{1..3}.txt ->
Line 846 ⟶ 1,366:
minusSignFlipsSequence 025.txt
minusSignFlipsSequence 030.txt
 
reverseSteppedNumberRising{1..6..-2}.txt ->
reverseSteppedNumberRising5.txt
reverseSteppedNumberRising3.txt
reverseSteppedNumberRising1.txt
 
combined-{Q..P}{2..1}.txt ->
Line 867 ⟶ 1,392:
rangeless{random}string ->
rangeless{random}string</pre>
=={{header|jq}}==
'''Works with jq, the C implementation of jq'''
 
'''Works with gojq, the Go implementation of jq'''
 
This implementation relies on "reluctant" regex parsing.
 
Range expressions of the form {x..y}, where x and y are single
characters, are allowed, even if exactly one of them is a digit.
 
When expanding an expression with more than one range, the program
as given below produces an ordering
based on expansion of the left-most range first.
A trivial change in two places is sufficient to produce the alternative ordering.
<syntaxhighlight lang="jq">
# Left-pad with 0s
def lpad($len): tostring | ($len - length) as $l | ("0" * $l) + .;
 
def expand:
# The key to success here is reluctance (".*?")
def cap:
capture("(?<head>^.*?)[{](?<from>[0-9]+|.)[.][.](?<to>[0-9]+|.)"
+ "([.][.](?<sign>-)?(?<increment>[0-9]))?[}](?<tail>.*)$");
 
def ton: if . == null then . else tonumber end;
 
# Produce a stream of integers, handling implicit descent.
# $i and $j should be integers.
# If $i and $j are distinct, then expand($i;$j;null;null) will include both,
# otherwise just $i.
def expand($i; $j; $sign; $increment):
(if $increment == null then 1 else $increment end) as $inc
| if $sign == null
then if $i <= $j
then range($i; $j + 1; $inc)
else range($i; $j - 1; - $inc)
end
else [expand($i; $j; null; $increment)] | reverse[]
end ;
 
# Produce a stream of single characters, handling implicit descent
def explode($x; $y; $sign; $increment):
($x|explode[0]) as $x
| ($y|explode[0]) as $y
| expand($x; $y; $sign; $increment)
| [.] | implode;
 
# The number of leading 0s of the input string
def leadingZeros: match("^0*") | .string | length;
def padding($x; $y):
($x | leadingZeros) as $a
| ($y | leadingZeros) as $b
| [if $a > 0 then ($x|length) else 0 end,
if $b > 0 then ($y|length) else 0 end]
| max;
( cap as $c
| if ($c.from|test("[0-9]+")) and ($c.to|test("[0-9]+"))
then padding($c.from; $c.to) as $padding
| $c.head
+ ( expand($c.from|tonumber;
$c.to|tonumber;
$c.sign;
$c.increment | ton) | lpad($padding))
+ ($c.tail | expand)
elif ($c.from|length == 1) and ($c.to|length == 1)
then $c.head + explode($c.from; $c.to; $c.sign; $c.increment|ton)
+ ($c.tail | expand)
else ""
end )
// . ;
 
def examples:
"simpleNumberRising{1..3}.txt",
"simpleAlphaDescending-{Z..X}.txt",
"steppedDownAndPadded-{10..00..5}.txt",
"minusSignFlipsSequence {030..20..-5}.txt",
"reverseSteppedNumberRising{1..6..-2}.txt",
"combined-{Q..P}{2..1}.txt",
"emoji{🌵..🌶}{🌽..🌾}etc",
"li{teral",
"rangeless{}empty",
"rangeless{random}string",
"mixedNumberAlpha{5..k}",
"steppedAlphaRising{P..Z..2}.txt",
"stops after endpoint-{02..10..3}.txt",
"steppedNumberRising{1..6..2}.txt",
"steppedNumberDescending{20..9..2}",
"steppedAlphaDescending-{Z..M..2}.txt",
"reversedSteppedAlphaDescending-{Z..M..-2}.txt"
;
 
examples
| "\(.) ->",
" \(expand)", ""
</syntaxhighlight>
{{output}}
<pre style="height:20lh;overflow:auto>
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
 
reverseSteppedNumberRising{1..6..-2}.txt ->
reverseSteppedNumberRising5.txt
reverseSteppedNumberRising3.txt
reverseSteppedNumberRising1.txt
 
combined-{Q..P}{2..1}.txt ->
combined-Q2.txt
combined-P2.txt
combined-Q1.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
 
mixedNumberAlpha{5..k} ->
mixedNumberAlpha5
mixedNumberAlpha6
mixedNumberAlpha7
mixedNumberAlpha8
mixedNumberAlpha9
mixedNumberAlpha:
mixedNumberAlpha;
mixedNumberAlpha<
mixedNumberAlpha=
mixedNumberAlpha>
mixedNumberAlpha?
mixedNumberAlpha@
mixedNumberAlphaA
mixedNumberAlphaB
mixedNumberAlphaC
mixedNumberAlphaD
mixedNumberAlphaE
mixedNumberAlphaF
mixedNumberAlphaG
mixedNumberAlphaH
mixedNumberAlphaI
mixedNumberAlphaJ
mixedNumberAlphaK
mixedNumberAlphaL
mixedNumberAlphaM
mixedNumberAlphaN
mixedNumberAlphaO
mixedNumberAlphaP
mixedNumberAlphaQ
mixedNumberAlphaR
mixedNumberAlphaS
mixedNumberAlphaT
mixedNumberAlphaU
mixedNumberAlphaV
mixedNumberAlphaW
mixedNumberAlphaX
mixedNumberAlphaY
mixedNumberAlphaZ
mixedNumberAlpha[
mixedNumberAlpha\
mixedNumberAlpha]
mixedNumberAlpha^
mixedNumberAlpha_
mixedNumberAlpha`
mixedNumberAlphaa
mixedNumberAlphab
mixedNumberAlphac
mixedNumberAlphad
mixedNumberAlphae
mixedNumberAlphaf
mixedNumberAlphag
mixedNumberAlphah
mixedNumberAlphai
mixedNumberAlphaj
mixedNumberAlphak
 
steppedAlphaRising{P..Z..2}.txt ->
steppedAlphaRisingP.txt
steppedAlphaRisingR.txt
steppedAlphaRisingT.txt
steppedAlphaRisingV.txt
steppedAlphaRisingX.txt
steppedAlphaRisingZ.txt
 
stops after endpoint-{02..10..3}.txt ->
stops after endpoint-02.txt
stops after endpoint-05.txt
stops after endpoint-08.txt
 
steppedNumberRising{1..6..2}.txt ->
steppedNumberRising1.txt
steppedNumberRising3.txt
steppedNumberRising5.txt
 
steppedNumberDescending{20..9..2} ->
steppedNumberDescending20
steppedNumberDescending18
steppedNumberDescending16
steppedNumberDescending14
steppedNumberDescending12
steppedNumberDescending10
 
steppedAlphaDescending-{Z..M..2}.txt ->
steppedAlphaDescending-Z.txt
steppedAlphaDescending-X.txt
steppedAlphaDescending-V.txt
steppedAlphaDescending-T.txt
steppedAlphaDescending-R.txt
steppedAlphaDescending-P.txt
steppedAlphaDescending-N.txt
 
reversedSteppedAlphaDescending-{Z..M..-2}.txt ->
reversedSteppedAlphaDescending-N.txt
reversedSteppedAlphaDescending-P.txt
reversedSteppedAlphaDescending-R.txt
reversedSteppedAlphaDescending-T.txt
reversedSteppedAlphaDescending-V.txt
reversedSteppedAlphaDescending-X.txt
reversedSteppedAlphaDescending-Z.txt
</pre>
 
=={{header|Julia}}==
<syntaxhighlight lang="julia">padzeros(str) = (len = length(str)) > 1 && str[1] == '0' ? len : 0
 
function ranged(str)
rang = filter(!isempty, split(str, r"\{|\}|\.\."))
delta = length(rang) > 2 ? parse(Int, rang[3]) : 1
if delta < 0
rang[1], rang[2], delta = rang[2], rang[1], -delta
end
if '0' <= rang[1][1] <= '9' || rang[1][1] == '-'
try x, y = parse(Int, rang[1]), parse(Int, rang[2]) catch; return [str] end
pad = max(padzeros(rang[1]), padzeros(rang[2]))
return [string(x, pad=pad) for x in range(x, step=(x < y) ? delta : -delta, stop=y)]
else
x, y, z = rang[1][end], rang[2][end], rang[1][1:end-1]
return [z * string(x) for x in range(x, step=(x < y) ? delta : -delta, stop=y)]
end
end
 
function splatrange(s)
m = match(r"([^\{]*)(\{[^}]+\.\.[^\}]+\})(.*)", s)
m == nothing && return [s]
c = m.captures
return vec([a * b for b in splatrange(c[3]), a in [c[1] * x for x in ranged(c[2])]])
end
 
for test in [
"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",
"mixedNumberAlpha{5..k}",
"steppedAlphaRising{P..Z..2}.txt",
"stops after endpoint-{02..10..3}.txt",
]
println(test, "->\n", [" " * x * "\n" for x in splatrange(test)]...)
end
</syntaxhighlight>{{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
 
mixedNumberAlpha{5..k}->
mixedNumberAlpha{5..k}
 
steppedAlphaRising{P..Z..2}.txt->
steppedAlphaRisingP.txt
steppedAlphaRisingR.txt
steppedAlphaRisingT.txt
steppedAlphaRisingV.txt
steppedAlphaRisingX.txt
steppedAlphaRisingZ.txt
 
stops after endpoint-{02..10..3}.txt->
stops after endpoint-02.txt
stops after endpoint-05.txt
stops after endpoint-08.txt
</pre>
 
=={{header|Nim}}==
{{trans|Wren}}
<syntaxhighlight lang="nim">import options, strutils, unicode
 
 
func intFromString(s: string): Option[int] =
## Try to parse an int. Return some(int) if parsing
## was successful, return none(int) if it failed.
try:
let n = s.parseInt()
result = some(n)
except ValueError:
result = none(int)
 
 
func parseRange(r: string): seq[string] =
 
if r.len == 0: return @["{}"] # rangeless, empty.
let sp = r.split("..")
if sp.len == 1: return @['{' & r & '}']
let first = sp[0]
let last = sp[1]
let incr = if sp.len == 2: "1" else: sp[2]
 
let val1 = intFromString(first)
let val2 = intFromString(last)
let val3 = intFromString(incr)
 
if val3.isNone(): return @['{' & r & '}'] # increment isn't a number.
var n3 = val3.get()
let numeric = val1.isSome and val2.isSome
 
var n1, n2: int
if numeric:
n1 = val1.get()
n2 = val2.get()
else:
if val1.isSome and val2.isNone or val1.isNone and val2.isSome:
return @['{' & r & '}'] # mixed numeric/alpha not expanded.
if first.runeLen != 1 or last.runeLen != 1:
return @['{' & r & '}'] # start/end are not both single alpha.
n1 = first.toRunes[0].int
n2 = last.toRunes[0].int
 
var width = 1
if numeric:
width = if first.len < last.len: last.len else: first.len
 
if n3 == 0:
# Zero increment.
return if numeric: @[n1.intToStr(width)] else: @[first]
 
var asc = n1 < n2
if n3 < 0:
asc = not asc
swap n1, n2
n3 = -n3
 
var i = n1
if asc:
while i <= n2:
result.add if numeric: i.intToStr(width) else: $Rune(i)
inc i, n3
else:
while i >= n2:
result.add if numeric: i.intToStr(width) else: $Rune(i)
dec i, n3
 
 
func rangeExpand(s: string): seq[string] =
result = @[""]
var rng = ""
var inRng = false
 
for c in s:
if c == '{' and not inRng:
inRng = true
rng = ""
elif c == '}' and inRng:
let rngRes = rng.parseRange()
var res: seq[string]
for i in 0..result.high:
for j in 0..rngRes.high:
res.add result[i] & rngRes[j]
result = move(res)
inRng = false
elif inRng:
rng.add c
else:
for s in result.mitems: s.add c
 
if inRng:
for s in result.mitems: s.add '{' & rng # unmatched braces.
 
 
when isMainModule:
 
const Examples = ["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",
"mixedNumberAlpha{5..k}",
"steppedAlphaRising{P..Z..2}.txt",
"stops after endpoint-{02..10..3}.txt"]
 
for s in Examples:
stdout.write s, " →\n "
let res = rangeExpand(s)
stdout.write res.join("\n ")
echo '\n'
</syntaxhighlight>
 
{{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
 
mixedNumberAlpha{5..k} →
mixedNumberAlpha{5..k}
 
steppedAlphaRising{P..Z..2}.txt →
steppedAlphaRisingP.txt
steppedAlphaRisingR.txt
steppedAlphaRisingT.txt
steppedAlphaRisingV.txt
steppedAlphaRisingX.txt
steppedAlphaRisingZ.txt
 
stops after endpoint-{02..10..3}.txt →
stops after endpoint-02.txt
stops after endpoint-05.txt
stops after endpoint-08.txt
</pre>
=={{header|Phix}}==
{{trans|Wren}}
<!--<syntaxhighlight lang="phix">(phixonline)-->
<span style="color: #7060A8;">requires</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"0.8.2"</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- (is_integer() is new, plus "==sign(inc)" found me a long-buried compiler bug)</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">parse_range</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">r</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">sp</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">split</span><span style="color: #0000FF;">(</span><span style="color: #000000;">r</span><span style="color: #0000FF;">,</span><span style="color: #008000;">".."</span><span style="color: #0000FF;">)&{</span><span style="color: #008000;">"1"</span><span style="color: #0000FF;">},</span>
<span style="color: #000000;">res</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{}</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">sp</span><span style="color: #0000FF;">)>=</span><span style="color: #000000;">3</span> <span style="color: #008080;">then</span>
<span style="color: #004080;">string</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">,</span><span style="color: #000000;">ending</span><span style="color: #0000FF;">,</span><span style="color: #000000;">step</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">sp</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">inc</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">to_integer</span><span style="color: #0000FF;">(</span><span style="color: #000000;">step</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">inc</span><span style="color: #0000FF;">!=</span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #004080;">bool</span> <span style="color: #000000;">ns</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">is_integer</span><span style="color: #0000FF;">(</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">),</span>
<span style="color: #000000;">ne</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">is_integer</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ending</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">ns</span><span style="color: #0000FF;">=</span><span style="color: #000000;">ne</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">ns</span> <span style="color: #008080;">then</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">s</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">to_integer</span><span style="color: #0000FF;">(</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">),</span>
<span style="color: #000000;">e</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">to_integer</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ending</span><span style="color: #0000FF;">),</span>
<span style="color: #000000;">w</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">max</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">),</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ending</span><span style="color: #0000FF;">))</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">inc</span><span style="color: #0000FF;"><</span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">s</span><span style="color: #0000FF;">,</span><span style="color: #000000;">e</span><span style="color: #0000FF;">,</span><span style="color: #000000;">inc</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">e</span><span style="color: #0000FF;">,</span><span style="color: #000000;">s</span><span style="color: #0000FF;">,-</span><span style="color: #000000;">inc</span><span style="color: #0000FF;">}</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">s</span><span style="color: #0000FF;">></span><span style="color: #000000;">e</span> <span style="color: #008080;">then</span> <span style="color: #000000;">inc</span> <span style="color: #0000FF;">*=</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">zfill</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">(</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">)></span><span style="color: #000000;">1</span> <span style="color: #008080;">and</span> <span style="color: #000000;">strange</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]=</span><span style="color: #008000;">'0'</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">or</span>
<span style="color: #0000FF;">(</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ending</span><span style="color: #0000FF;">)></span><span style="color: #000000;">1</span> <span style="color: #008080;">and</span> <span style="color: #000000;">ending</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]=</span><span style="color: #008000;">'0'</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">fmt</span> <span style="color: #0000FF;">=</span> <span style="color: #008080;">iff</span><span style="color: #0000FF;">(</span><span style="color: #000000;">zfill</span><span style="color: #0000FF;">?</span><span style="color: #7060A8;">sprintf</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"%%0%dd"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">w</span><span style="color: #0000FF;">}):</span><span style="color: #008000;">"%d"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">k</span><span style="color: #0000FF;">=</span><span style="color: #000000;">s</span> <span style="color: #008080;">to</span> <span style="color: #000000;">e</span> <span style="color: #008080;">by</span> <span style="color: #000000;">inc</span> <span style="color: #008080;">do</span>
<span style="color: #000000;">res</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">append</span><span style="color: #0000FF;">(</span><span style="color: #000000;">res</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">sprintf</span><span style="color: #0000FF;">(</span><span style="color: #000000;">fmt</span><span style="color: #0000FF;">,</span><span style="color: #000000;">k</span><span style="color: #0000FF;">))</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">return</span> <span style="color: #000000;">res</span>
<span style="color: #008080;">elsif</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">)=</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ending</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #004080;">bool</span> <span style="color: #000000;">ok</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">(</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">)=</span><span style="color: #000000;">1</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #008080;">not</span> <span style="color: #000000;">ok</span> <span style="color: #008080;">then</span>
<span style="color: #004080;">object</span> <span style="color: #000000;">s32</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">utf8_to_utf32</span><span style="color: #0000FF;">(</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">,-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">),</span>
<span style="color: #000000;">e32</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">utf8_to_utf32</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ending</span><span style="color: #0000FF;">,-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #004080;">sequence</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s32</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">and</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s32</span><span style="color: #0000FF;">)=</span><span style="color: #000000;">1</span>
<span style="color: #008080;">and</span> <span style="color: #004080;">sequence</span><span style="color: #0000FF;">(</span><span style="color: #000000;">e32</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">and</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">e32</span><span style="color: #0000FF;">)=</span><span style="color: #000000;">1</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">ok</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">true</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">ok</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">strange</span><span style="color: #0000FF;">></span><span style="color: #000000;">ending</span> <span style="color: #008080;">then</span> <span style="color: #000000;">inc</span> <span style="color: #0000FF;">*=</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">while</span> <span style="color: #004600;">true</span> <span style="color: #008080;">do</span>
<span style="color: #000000;">res</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">append</span><span style="color: #0000FF;">(</span><span style="color: #000000;">res</span><span style="color: #0000FF;">,</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">sdx</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">while</span> <span style="color: #004600;">true</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">ch</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">strange</span><span style="color: #0000FF;">[</span><span style="color: #000000;">sdx</span><span style="color: #0000FF;">]+</span><span style="color: #000000;">inc</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;"><=</span><span style="color: #000000;">#FF</span> <span style="color: #008080;">and</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;">>=</span><span style="color: #000000;">#00</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">strange</span><span style="color: #0000FF;">[</span><span style="color: #000000;">sdx</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">ch</span>
<span style="color: #008080;">exit</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">strange</span><span style="color: #0000FF;">[</span><span style="color: #000000;">sdx</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #008080;">iff</span><span style="color: #0000FF;">(</span><span style="color: #000000;">inc</span><span style="color: #0000FF;"><</span><span style="color: #000000;">0</span><span style="color: #0000FF;">?</span><span style="color: #000000;">#FF</span><span style="color: #0000FF;">:</span><span style="color: #000000;">#00</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">sdx</span> <span style="color: #0000FF;">-=</span> <span style="color: #000000;">1</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">while</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">compare</span><span style="color: #0000FF;">(</span><span style="color: #000000;">strange</span><span style="color: #0000FF;">,</span><span style="color: #000000;">ending</span><span style="color: #0000FF;">)==</span><span style="color: #7060A8;">sign</span><span style="color: #0000FF;">(</span><span style="color: #000000;">inc</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span> <span style="color: #008080;">exit</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">res</span><span style="color: #0000FF;">)></span><span style="color: #000000;">10</span> <span style="color: #008080;">then</span> <span style="color: #0000FF;">?</span><span style="color: #000000;">9</span><span style="color: #0000FF;">/</span><span style="color: #000000;">0</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span> <span style="color: #000080;font-style:italic;">-- (sanity check)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">while</span>
<span style="color: #008080;">return</span> <span style="color: #000000;">res</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> <span style="color: #000080;font-style:italic;">-- ([utf8] strings not single char)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> <span style="color: #000080;font-style:italic;">-- (neither numeric nor same-length alpha)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> <span style="color: #000080;font-style:italic;">-- (mixed numeric/alpha)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> <span style="color: #000080;font-style:italic;">-- (non-numeric increment)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> <span style="color: #000080;font-style:italic;">-- (rangeless)</span>
<span style="color: #008080;">return</span> <span style="color: #0000FF;">{</span><span style="color: #008000;">"{"</span><span style="color: #0000FF;">&</span><span style="color: #000000;">r</span><span style="color: #0000FF;">&</span><span style="color: #008000;">"}"</span><span style="color: #0000FF;">}</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">range_expand</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">s</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">res</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #008000;">""</span><span style="color: #0000FF;">}</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">range</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">""</span>
<span style="color: #004080;">bool</span> <span style="color: #000000;">in_range</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">false</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">k</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">c</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">s</span><span style="color: #0000FF;">[</span><span style="color: #000000;">k</span><span style="color: #0000FF;">]</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">c</span> <span style="color: #0000FF;">==</span> <span style="color: #008000;">'{'</span> <span style="color: #008080;">and</span> <span style="color: #008080;">not</span> <span style="color: #000000;">in_range</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">in_range</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">true</span>
<span style="color: #000000;">range</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">""</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">c</span> <span style="color: #0000FF;">==</span> <span style="color: #008000;">'}'</span> <span style="color: #008080;">and</span> <span style="color: #000000;">in_range</span> <span style="color: #008080;">then</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">range_res</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">parse_range</span><span style="color: #0000FF;">(</span><span style="color: #000000;">range</span><span style="color: #0000FF;">),</span>
<span style="color: #000000;">prev_res</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">res</span>
<span style="color: #000000;">res</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{}</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">prev_res</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">j</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">range_res</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #000000;">res</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">append</span><span style="color: #0000FF;">(</span><span style="color: #000000;">res</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">prev_res</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">range_res</span><span style="color: #0000FF;">[</span><span style="color: #000000;">j</span><span style="color: #0000FF;">])</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #000000;">in_range</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">false</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">in_range</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">range</span> <span style="color: #0000FF;">&=</span> <span style="color: #000000;">c</span>
<span style="color: #008080;">else</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">res</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #000000;">res</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">&=</span> <span style="color: #000000;">c</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">in_range</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">res</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #000000;">res</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">&=</span> <span style="color: #008000;">"{"</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">range</span> <span style="color: #000080;font-style:italic;">// unmatched braces</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">return</span> <span style="color: #000000;">res</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">examples</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span>
<span style="color: #008000;">"simpleNumberRising{1..3}.txt"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"simpleAlphaDescending-{Z..X}.txt"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"steppedDownAndPadded-{10..00..5}.txt"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"minusSignFlipsSequence {030..20..-5}.txt"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"combined-{Q..P}{2..1}.txt"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"emoji{🌵..🌶}{🌽..🌾}etc"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"multi char emoji ranges fail {🌵🌵..🌵🌶}"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"li{teral"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"rangeless{}empty"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"rangeless{random}string"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"mixedNumberAlpha{5..k}"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"steppedAlphaRising{P..Z..2}.txt"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"stops after endpoint-{02..10..3}.txt"</span>
<span style="color: #0000FF;">}</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">examples</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">s</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">examples</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span>
<span style="color: #7060A8;">printf</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"%s -&gt;\n %s\n"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">s</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">join</span><span style="color: #0000FF;">(</span><span style="color: #000000;">range_expand</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">),</span><span style="color: #008000;">"\n "</span><span style="color: #0000FF;">)})</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<!--</syntaxhighlight>-->
{{out}}
Note that, as usual, unicode output does not look good on a windows console for tests 6 & 7 (linux output shown)
<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
multi char emoji ranges fail {🌵🌵..🌵🌶} ->
multi char emoji ranges fail {🌵🌵..🌵🌶}
li{teral ->
li{teral
rangeless{}empty ->
rangeless{}empty
rangeless{random}string ->
rangeless{random}string
mixedNumberAlpha{5..k} ->
mixedNumberAlpha{5..k}
steppedAlphaRising{P..Z..2}.txt ->
steppedAlphaRisingP.txt
steppedAlphaRisingR.txt
steppedAlphaRisingT.txt
steppedAlphaRisingV.txt
steppedAlphaRisingX.txt
steppedAlphaRisingZ.txt
stops after endpoint-{02..10..3}.txt ->
stops after endpoint-02.txt
stops after endpoint-05.txt
stops after endpoint-08.txt
</pre>
=={{header|Python}}==
<syntaxhighlight lang="python">"""Brace expansion using ranges. Requires Python >= 3.6.
 
Here we use regular expressions for parsing and take an object orientated approach
to expansion of range expressions.
 
This implementation supports stepped ordinal range expressions.
"""
 
from __future__ import annotations
 
import itertools
import re
 
from abc import ABC
from abc import abstractmethod
 
from typing import Iterable
from typing import Optional
 
 
RE_SPEC = [
(
"INT_RANGE",
r"\{(?P<int_start>[0-9]+)..(?P<int_stop>[0-9]+)(?:(?:..)?(?P<int_step>-?[0-9]+))?}",
),
(
"ORD_RANGE",
r"\{(?P<ord_start>[^0-9])..(?P<ord_stop>[^0-9])(?:(?:..)?(?P<ord_step>-?[0-9]+))?}",
),
(
"LITERAL",
r".+?(?=\{|$)",
),
]
 
 
RE_EXPRESSION = re.compile(
"|".join(rf"(?P<{name}>{pattern})" for name, pattern in RE_SPEC)
)
 
 
class Expression(ABC):
"""Brace expression abstract base class."""
 
@abstractmethod
def expand(self, prefix: str) -> Iterable[str]:
pass
 
 
class Literal(Expression):
"""An expression literal."""
 
def __init__(self, value: str):
self.value = value
 
def expand(self, prefix: str) -> Iterable[str]:
return [f"{prefix}{self.value}"]
 
 
class IntRange(Expression):
"""An integer range expression."""
 
def __init__(
self, start: int, stop: int, step: Optional[int] = None, zfill: int = 0
):
self.start, self.stop, self.step = fix_range(start, stop, step)
self.zfill = zfill
 
def expand(self, prefix: str) -> Iterable[str]:
return (
f"{prefix}{str(i).zfill(self.zfill)}"
for i in range(self.start, self.stop, self.step)
)
 
 
class OrdRange(Expression):
"""An ordinal range expression."""
 
def __init__(self, start: str, stop: str, step: Optional[int] = None):
self.start, self.stop, self.step = fix_range(ord(start), ord(stop), step)
 
def expand(self, prefix: str) -> Iterable[str]:
return (f"{prefix}{chr(i)}" for i in range(self.start, self.stop, self.step))
 
 
def expand(expressions: Iterable[Expression]) -> Iterable[str]:
"""Expand a sequence of ``Expression``s. Each expression builds on the results
of the expressions that come before it in the sequence."""
expanded = [""]
 
for expression in expressions:
expanded = itertools.chain.from_iterable(
[expression.expand(prefix) for prefix in expanded]
)
 
return expanded
 
 
def zero_fill(start, stop) -> int:
"""Return the target zero padding width."""
 
def _zfill(s):
if len(s) <= 1 or not s.startswith("0"):
return 0
return len(s)
 
return max(_zfill(start), _zfill(stop))
 
 
def fix_range(start, stop, step):
"""Transform start, stop and step so that we can pass them to Python's
built-in ``range`` function."""
if not step:
# Zero or None. Explicit zero gets changed to default.
if start <= stop:
# Default step for ascending ranges.
step = 1
else:
# Default step for descending ranges.
step = -1
 
elif step < 0:
# A negative step means we reverse the range.
start, stop = stop, start
 
if start < stop:
step = abs(step)
else:
start -= 1
stop -= 1
 
elif start > stop:
# A descending range with explicit step.
step = -step
 
# Don't overshoot or fall short.
if (start - stop) % step == 0:
stop += step
 
return start, stop, step
 
 
def parse(expression: str) -> Iterable[Expression]:
"""Generate a sequence of ``Expression``s from the given range expression."""
for match in RE_EXPRESSION.finditer(expression):
kind = match.lastgroup
 
if kind == "INT_RANGE":
start = match.group("int_start")
stop = match.group("int_stop")
step = match.group("int_step")
zfill = zero_fill(start, stop)
 
if step is not None:
step = int(step)
 
yield IntRange(int(start), int(stop), step, zfill=zfill)
 
elif kind == "ORD_RANGE":
start = match.group("ord_start")
stop = match.group("ord_stop")
step = match.group("ord_step")
 
if step is not None:
step = int(step)
 
yield OrdRange(start, stop, step)
 
elif kind == "LITERAL":
yield Literal(match.group())
 
 
def examples():
cases = [
r"simpleNumberRising{1..3}.txt",
r"simpleAlphaDescending-{Z..X}.txt",
r"steppedDownAndPadded-{10..00..5}.txt",
r"minusSignFlipsSequence {030..20..-5}.txt",
r"reverseSteppedNumberRising{1..6..-2}.txt",
r"combined-{Q..P}{2..1}.txt",
r"emoji{🌵..🌶}{🌽..🌾}etc",
r"li{teral",
r"rangeless{}empty",
r"rangeless{random}string",
# Extra examples, not from the task description.
r"steppedNumberRising{1..6..2}.txt",
r"steppedNumberDescending{20..9..2}.txt",
r"steppedAlphaDescending-{Z..M..2}.txt",
r"reverseSteppedAlphaRising{A..F..-2}.txt",
r"reversedSteppedAlphaDescending-{Z..M..-2}.txt",
]
 
for case in cases:
print(f"{case} ->")
expressions = parse(case)
 
for itm in expand(expressions):
print(f"{' '*4}{itm}")
 
print("") # Blank line between cases
 
 
if __name__ == "__main__":
examples()
</syntaxhighlight>
 
{{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
 
reverseSteppedNumberRising{1..6..-2}.txt ->
reverseSteppedNumberRising5.txt
reverseSteppedNumberRising3.txt
reverseSteppedNumberRising1.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
 
steppedNumberRising{1..6..2}.txt ->
steppedNumberRising1.txt
steppedNumberRising3.txt
steppedNumberRising5.txt
 
steppedNumberDescending{20..9..2}.txt ->
steppedNumberDescending20.txt
steppedNumberDescending18.txt
steppedNumberDescending16.txt
steppedNumberDescending14.txt
steppedNumberDescending12.txt
steppedNumberDescending10.txt
 
steppedAlphaDescending-{Z..M..2}.txt ->
steppedAlphaDescending-Z.txt
steppedAlphaDescending-X.txt
steppedAlphaDescending-V.txt
steppedAlphaDescending-T.txt
steppedAlphaDescending-R.txt
steppedAlphaDescending-P.txt
steppedAlphaDescending-N.txt
 
reverseSteppedAlphaRising{A..F..-2}.txt ->
reverseSteppedAlphaRisingE.txt
reverseSteppedAlphaRisingC.txt
reverseSteppedAlphaRisingA.txt
 
reversedSteppedAlphaDescending-{Z..M..-2}.txt ->
reversedSteppedAlphaDescending-M.txt
reversedSteppedAlphaDescending-O.txt
reversedSteppedAlphaDescending-Q.txt
reversedSteppedAlphaDescending-S.txt
reversedSteppedAlphaDescending-U.txt
reversedSteppedAlphaDescending-W.txt
reversedSteppedAlphaDescending-Y.txt
</pre>
=={{header|Raku}}==
{{works with|Rakudo|2020.08.1}}
Also implements some of the string list functions described on the bash-hackers page.
 
<syntaxhighlight lang="raku" perl6line>my $range = rx/ '{' $<start> =( <-[.]>+?) '..' $<end> = (<-[.]>+?) ['..' $<incr> =( ['-'?\d+)] ]? '}' /;
my $list = rx/ ^ $<prefix> = .*? '{' (<-[,}]>+) +%% ',' '}' $<postfix> = .* $/;
 
sub expand (Str $string) {
Line 880 ⟶ 2,391:
quietly my ($start, $end, $incr) = $/<start end incr>».Str;
$incr ||= 1;
if $incr < 0 { ($end, $start) = $start, $end; if $incr =< -$incr}0;
$incr.=abs;
 
if try all( +$start ~~ Numeric and, +$end ) ~~ Numeric {
if $startincr >= - $endincr {if $incrstart = -> $incr }end;
 
my ($sl, $el) = 0,0;
my ($sl, $el) = 0, 0;
$sl = $start.chars if $start.starts-with('0');
$el = $end.chars if $end.starts-with('0');
 
if $start < $end {
my @this = $start @return< =$end ?? (+$start, * + $incr …^ * > +$end).map: {!! $string.subst(+$rangestart, sprintf("%{'0'* ~ max+ $sl,incr $el}d",…^ $_)* )< }+$end);
@return = @this.map: { $string.subst($range, sprintf("%{'0' ~ max $sl, $el}d", $_) ) }
} else {
}
@return = (+$start, * + $incr …^ * < +$end).map: { $string.subst($range, sprintf("%{'0' ~ max $sl, $el}d", $_) ) }
elsif try +$start ~~ Numeric or +$end ~~ Numeric {
return $string #fail
}
else {
my @this;
if $start.chars + $end.chars > 2 {
return $string if $start.succ eq $start or $end.succ eq $end; # fail
@this = $start lt $end ?? ($start, (*.succ xx $incr).tail …^ * gt $end) !! ($start, (*.pred xx $incr).tail …^ * lt $end);
}
} elsif $start !~~ /\d/ and $end !~~ /\d/else {
if $start gt $end { $incr = - $incr }if $start gt $end;
if @this = $start lt $end {?? ($start, (*.ord + $incr).chr …^ * gt $end) !! ($start, (*.ord + $incr).chr …^ * lt $end);
@return = ($start, (*.ord + $incr).chr …^ * gt $end).map: { $string.subst($range, sprintf("%s", $_) ) }
} else {
@return = ($start, (*.ord + $incr).chr …^ * lt $end).map: { $string.subst($range, sprintf("%s", $_) ) }
}
@return = @this.map: { $string.subst($range, sprintf("%s", $_) ) }
}
}
Line 907 ⟶ 2,425:
if ($prefix ~ $postfix).chars {
@return = $these.map: { $string.subst($list, $prefix ~ $_ ~ $postfix) } if $these.elems > 1
} else {
else {
@return = $these.join: ' '
}
}
my $cnt = 1;
while $cnt != +@return {
$cnt = +@return;
@return.=map: { |.&expand }
}
@return
Line 929 ⟶ 2,453:
# Test some other features
 
'stop point not in sequence-{02..10..3}.txt'
# Emoji lists
steppedAlphaRising{P..Z..2}.txt
emoji{☃,☄}{★,🇺🇸,☆}lists
'simple {just,give,me,money} list'
{thatʼs,what,I,want}
# Doesn't end exactly on endpoint
'emoji {☃,☄}{★,🇺🇸,☆} lists'
'stops after endpoint-{02..10..3}.txt'
'alphanumeric mix{ab7..ac1}.txt'
'alphanumeric mix{0A..0C}.txt'
 
# Simplefail listby design
'simple {I,want,my,money,back} list'
 
# Prefix, list
_{I,want,my,money,back}
 
# Postfix, list
{I,want,my,money,back}!
 
'mixed terms fail {7..C}.txt'
# List, no prefix, postfix
'multi char emoji ranges fail {🌵🌵..🌵🌶}'
{I,want,my,money,back}
 
# fail by design
'alphanumeric mix{a..1}.txt'
> -> $test {
say "$test ->";
mysay @strings(' = ' xx * Z~ expand $test).join: "\n";
my $cnt = -1;
while $cnt != +@strings {
$cnt = +@strings;
@strings.=map: { |.&expand }
}
 
say (' ' xx * Z~ @strings).join("\n");
say '';
}
}</lang>
</syntaxhighlight>
{{out}}
<pre>simpleNumberRising{1..3}.txt ->
Line 1,003 ⟶ 2,513:
rangeless{random}string
 
stop point not in sequence-{02..10..3}.txt ->
emoji{☃,☄}{★,🇺🇸,☆}lists ->
stop point not in sequence-02.txt
emoji☃★lists
stop point not in sequence-05.txt
emoji☃🇺🇸lists
stop point not in sequence-08.txt
emoji☃☆lists
 
emoji☄★lists
steppedAlphaRising{P..Z..2}.txt ->
emoji☄🇺🇸lists
steppedAlphaRisingP.txt
emoji☄☆lists
steppedAlphaRisingR.txt
steppedAlphaRisingT.txt
steppedAlphaRisingV.txt
steppedAlphaRisingX.txt
steppedAlphaRisingZ.txt
 
simple {just,give,me,money} list ->
simple just list
simple give list
simple me list
simple money list
 
{thatʼs,what,I,want} ->
thatʼs what I want
 
emoji {☃,☄}{★,🇺🇸,☆} lists ->
emoji ☃★ lists
emoji ☃🇺🇸 lists
emoji ☃☆ lists
emoji ☄★ lists
emoji ☄🇺🇸 lists
emoji ☄☆ lists
 
alphanumeric mix{ab7..ac1}.txt ->
alphanumeric mixab7.txt
alphanumeric mixab8.txt
alphanumeric mixab9.txt
alphanumeric mixac0.txt
alphanumeric mixac1.txt
 
alphanumeric mix{0A..0C}.txt ->
alphanumeric mix0A.txt
alphanumeric mix0B.txt
alphanumeric mix0C.txt
 
mixed terms fail {7..C}.txt ->
mixed terms fail {7..C}.txt
 
multi char emoji ranges fail {🌵🌵..🌵🌶} ->
multi char emoji ranges fail {🌵🌵..🌵🌶}
</pre>
=={{header|Wren}}==
{{libheader|Wren-fmt}}
Added a few more examples to the minimum number needed for the task.
<syntaxhighlight lang="wren">import "./fmt" for Fmt
 
var parseRange = Fn.new { |r|
if (r == "") return ["{}"] // rangeless, empty
var sp = r.split("..")
if (sp.count == 1) return ["{%(r)}"] // rangeless, random value
var sta = sp[0]
var end = sp[1]
var inc = (sp.count == 2) ? "1" : sp[2]
var n1 = Num.fromString(sta)
var n2 = Num.fromString(end)
var n3 = Num.fromString(inc)
if (!n3) return ["{%(r)}"] // increment isn't a number
var numeric = n1 && n2
if (!numeric) {
if ((n1 && !n2) || (!n1 && n2)) return ["{%(r)}"] // mixed numeric/alpha not expanded
if (sta.count != 1 || end.count != 1) return ["{%(r)}"] // start/end are not both single alpha
n1 = sta.codePoints[0]
n2 = end.codePoints[0]
}
var width = 1
if (numeric) width = (sta.count < end.count) ? end.count : sta.count
if (n3 == 0) return (numeric) ? [Fmt.dz(width, n1)] : [sta] // zero increment
var res = []
var asc = n1 < n2
if (n3 < 0) {
asc = !asc
var t = n1
var d = (n1 - n2).abs % (-n3)
n1 = n2 - d * (n2 - n1).sign
n2 = t
n3 = -n3
}
var i = n1
if (asc) {
while (i <= n2) {
res.add( (numeric) ? Fmt.dz(width, i) : String.fromCodePoint(i) )
i = i + n3
}
} else {
while (i >= n2) {
res.add(( numeric) ? Fmt.dz(width, i) : String.fromCodePoint(i) )
i = i - n3
}
}
return res
}
 
var rangeExpand = Fn.new { |s|
var res = [""]
var rng = ""
var inRng = false
for (c in s) {
if (c == "{" && !inRng) {
inRng = true
rng = ""
} else if (c == "}" && inRng) {
var rngRes = parseRange.call(rng)
var rngCount = rngRes.count
var res2 = []
for (i in 0...res.count) {
for (j in 0...rngCount) res2.add(res[i] + rngRes[j])
}
res = res2
inRng = false
} else if (inRng) {
rng = rng + c
} else {
for (i in 0...res.count) res[i] = res[i] + c
}
}
if (inRng) for (i in 0...res.count) res[i] = res[i] + "{" + rng // unmatched braces
return res
}
 
var examples = [
"simpleNumberRising{1..3}.txt",
"simpleAlphaDescending-{Z..X}.txt",
"steppedDownAndPadded-{10..00..5}.txt",
"minusSignFlipsSequence {030..20..-5}.txt",
"reverseSteppedNumberRising{1..6..-2}.txt",
"combined-{Q..P}{2..1}.txt",
"emoji{🌵..🌶}{🌽..🌾}etc",
"li{teral",
"rangeless{}empty",
"rangeless{random}string",
"mixedNumberAlpha{5..k}",
"steppedAlphaRising{P..Z..2}.txt",
"stops after endpoint-{02..10..3}.txt",
"steppedNumberRising{1..6..2}.txt",
"steppedNumberDescending{20..9..2}",
"steppedAlphaDescending-{Z..M..2}.txt",
"reversedSteppedAlphaDescending-{Z..M..-2}.txt"
]
 
for (s in examples) {
System.write("%(s) ->\n ")
var res = rangeExpand.call(s)
System.print(res.join("\n "))
System.print()
}</syntaxhighlight>
 
{{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
 
reverseSteppedNumberRising{1..6..-2}.txt ->
reverseSteppedNumberRising5.txt
reverseSteppedNumberRising3.txt
reverseSteppedNumberRising1.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
 
mixedNumberAlpha{5..k} ->
mixedNumberAlpha{5..k}
 
steppedAlphaRising{P..Z..2}.txt ->
steppedAlphaRisingP.txt
steppedAlphaRisingR.txt
steppedAlphaRisingT.txt
steppedAlphaRisingV.txt
steppedAlphaRisingX.txt
steppedAlphaRisingZ.txt
 
stops after endpoint-{02..10..3}.txt ->
Line 1,016 ⟶ 2,730:
stops after endpoint-08.txt
 
steppedNumberRising{1..6..2}.txt ->
simple {I,want,my,money,back} list ->
steppedNumberRising1.txt
simple I list
steppedNumberRising3.txt
simple want list
steppedNumberRising5.txt
simple my list
simple money list
simple back list
 
_{I,want,my,money,back} ->
_I
_want
_my
_money
_back
 
steppedNumberDescending{20..9..2} ->
{I,want,my,money,back}! ->
steppedNumberDescending20
I!
steppedNumberDescending18
want!
steppedNumberDescending16
my!
steppedNumberDescending14
money!
steppedNumberDescending12
back!
steppedNumberDescending10
 
steppedAlphaDescending-{Z..M..2}.txt ->
{I,want,my,money,back} ->
steppedAlphaDescending-Z.txt
I want my money back
steppedAlphaDescending-X.txt
steppedAlphaDescending-V.txt
steppedAlphaDescending-T.txt
steppedAlphaDescending-R.txt
steppedAlphaDescending-P.txt
steppedAlphaDescending-N.txt
 
alphanumeric mixreversedSteppedAlphaDescending-{aZ..1M..-2}.txt ->
reversedSteppedAlphaDescending-N.txt
alphanumeric mix{a..1}.txt
reversedSteppedAlphaDescending-P.txt
reversedSteppedAlphaDescending-R.txt
reversedSteppedAlphaDescending-T.txt
reversedSteppedAlphaDescending-V.txt
reversedSteppedAlphaDescending-X.txt
reversedSteppedAlphaDescending-Z.txt
</pre>
2,442

edits