Task: Brace expansion using ranges




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{"{}"} 
    }
    sp := strings.Split(r, "..")
    if len(sp) == 1 {
        return []string{"{" + r + "}"} 
    }
    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 + "}"} 
    }
    numeric := (ok1 == nil) && (ok2 == nil)
    if !numeric {
        if (ok1 == nil && ok2 != nil) || (ok1 != nil && ok2 == nil) {
            return []string{"{" + r + "}"} 
        }
        if utf8.RuneCountInString(sta) != 1 || utf8.RuneCountInString(end) != 1 {
            return []string{"{" + r + "}"} 
        }
        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 { 
        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 
        }
    }
    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()
    }
}




Python: 

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):
    

    @abstractmethod
    def expand(self, prefix: str) -> Iterable[str]:
        pass


class Literal(Expression):
    

    def __init__(self, value: str):
        self.value = value

    def expand(self, prefix: str) -> Iterable[str]:
        return [f"{prefix}{self.value}"]


class IntRange(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):
    

    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]:
    
    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:
    

    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):
    
    if not step:
        
        if start <= stop:
            
            step = 1
        else:
            
            step = -1

    elif step < 0:
        
        start, stop = stop, start

        if start < stop:
            step = abs(step)
        else:
            start -= 1
            stop -= 1

    elif start > stop:
        
        step = -step

    
    if (start - stop) % step == 0:
        stop += step

    return start, stop, step


def parse(expression: str) -> Iterable[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",
        
        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("")  


if __name__ == "__main__":
    examples()