Instructions to use ntedvs/irex with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use ntedvs/irex with MLX:
# Make sure mlx-lm is installed # pip install --upgrade mlx-lm # if on a CUDA device, also pip install mlx[cuda] # Generate text with mlx-lm from mlx_lm import load, generate model, tokenizer = load("ntedvs/irex") prompt = "Once upon a time in" text = generate(model, tokenizer, prompt=prompt, verbose=True) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- MLX LM
How to use ntedvs/irex with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Generate some text mlx_lm.generate --model "ntedvs/irex" --prompt "Once upon a time"
- Atomic Chat
Download grammar.py from ntedvs/irex: direct link, hf CLI and curl.
- Browser
- Download file 19.1 kB
-
https://huggingface.co/ntedvs/irex/resolve/main/grammar.py
- Command line
-
hf download hf://ntedvs/irex/grammar.py
-
curl -L -o grammar.py https://huggingface.co/ntedvs/irex/resolve/main/grammar.py
19.1 kB
| """Regex pool: sample in-scope JS regexes, synthesize positive/negative strings. | |
| Pipeline per regex: string -> parse (scope check) -> sample positives -> mutate | |
| into negatives -> label everything with real JS semantics (QuickJS). | |
| python src/grammar.py --n 60000 | |
| """ | |
| import argparse | |
| import json | |
| import random | |
| import string | |
| import time | |
| from pathlib import Path | |
| import engine | |
| # ---------------------------------------------------------------- char sets | |
| U = frozenset(chr(c) for c in range(32, 127)) # printable ASCII universe | |
| DIG = frozenset(string.digits) | |
| WRD = frozenset(string.ascii_letters + string.digits + "_") | |
| SPC = frozenset(" ") | |
| ESC = {"d": DIG, "D": U - DIG, "w": WRD, "W": U - WRD, "s": SPC, "S": U - SPC} | |
| SPECIAL = set("\\^$.|?*+()[]{}/") | |
| # ---------------------------------------------------------------- parser | |
| # AST: ("set", chars) | ("seq", [n]) | ("alt", [n]) | ("rep", n, lo, hi) | ("nil",) | |
| class Parser: | |
| def __init__(self, s): | |
| self.s, self.i = s, 0 | |
| def peek(self): | |
| return self.s[self.i] if self.i < len(self.s) else None | |
| def eat(self, c=None): | |
| ch = self.peek() | |
| if ch is None or (c and ch != c): | |
| raise ValueError(f"expected {c!r} at {self.i} in {self.s!r}") | |
| self.i += 1 | |
| return ch | |
| def parse(self): | |
| n = self.alt() | |
| if self.peek() is not None: | |
| raise ValueError(f"trailing {self.s[self.i:]!r}") | |
| return n | |
| def alt(self): | |
| xs = [self.seq()] | |
| while self.peek() == "|": | |
| self.eat() | |
| xs.append(self.seq()) | |
| return xs[0] if len(xs) == 1 else ("alt", xs) | |
| def seq(self): | |
| xs = [] | |
| while self.peek() not in (None, "|", ")"): | |
| xs.append(self.quant(self.atom())) | |
| return ("seq", xs) | |
| def quant(self, a): | |
| c = self.peek() | |
| if c in ("*", "+", "?"): | |
| self.eat() | |
| lo, hi = {"*": (0, None), "+": (1, None), "?": (0, 1)}[c] | |
| elif c == "{": | |
| self.eat() | |
| lo = self.num() | |
| hi = lo | |
| if self.peek() == ",": | |
| self.eat() | |
| hi = self.num() if self.peek() != "}" else None | |
| self.eat("}") | |
| if hi is not None and hi < lo: | |
| raise ValueError("bad range") | |
| else: | |
| return a | |
| if a[0] == "nil": | |
| raise ValueError("quantified anchor") | |
| if self.peek() == "?": # lazy: same language for sampling | |
| self.eat() | |
| if self.peek() in ("*", "+", "?", "{"): | |
| raise ValueError("stacked quantifier") | |
| if hi is None and (nullable(a) or ambiguous(a)): | |
| raise ValueError("ambiguous unbounded repeat (ReDoS)") | |
| return ("rep", a, lo, hi) | |
| def num(self): | |
| j = self.i | |
| while (self.peek() or "").isdigit(): | |
| self.i += 1 | |
| if j == self.i: | |
| raise ValueError("expected number") | |
| return int(self.s[j:self.i]) | |
| def atom(self): | |
| c = self.eat() | |
| if c == "(": | |
| if self.peek() == "?": | |
| self.eat() | |
| self.eat(":") # only non-capturing allowed | |
| n = self.alt() | |
| self.eat(")") | |
| return n | |
| if c == "[": | |
| return self.cls() | |
| if c == ".": | |
| return ("set", U) | |
| if c in "^$": | |
| return ("nil",) | |
| if c == "\\": | |
| e = self.eat() | |
| if e in ESC: | |
| return ("set", ESC[e]) | |
| if e in "bB": | |
| return ("nil",) | |
| if e in SPECIAL or e in "-": | |
| return ("set", frozenset(e)) | |
| raise ValueError(f"out-of-scope escape \\{e}") | |
| if c in "*+?{}|)]": | |
| raise ValueError(f"unexpected {c!r}") | |
| return ("set", frozenset(c)) | |
| def cls(self): | |
| neg = self.peek() == "^" | |
| if neg: | |
| self.eat() | |
| out, first = set(), True | |
| while self.peek() != "]" or first: | |
| first = False | |
| lo = self.cls_char() | |
| if isinstance(lo, frozenset): | |
| out |= lo | |
| continue | |
| if self.peek() == "-" and self.s[self.i + 1:self.i + 2] not in ("]", ""): | |
| self.eat() | |
| hi = self.cls_char() | |
| if isinstance(hi, frozenset) or ord(hi) < ord(lo): | |
| raise ValueError("bad class range") | |
| out |= {chr(x) for x in range(ord(lo), ord(hi) + 1)} | |
| else: | |
| out.add(lo) | |
| self.eat("]") | |
| out &= U | |
| return ("set", frozenset(U - out if neg else out)) | |
| def cls_char(self): | |
| c = self.eat() | |
| if c != "\\": | |
| return c | |
| e = self.eat() | |
| if e in ESC: | |
| return ESC[e] | |
| if e in SPECIAL or e in "-]^": | |
| return e | |
| raise ValueError(f"out-of-scope class escape \\{e}") | |
| def nullable(n) -> bool: | |
| k = n[0] | |
| if k == "set": | |
| return False | |
| if k == "seq": | |
| return all(map(nullable, n[1])) | |
| if k == "alt": | |
| return any(map(nullable, n[1])) | |
| if k == "rep": | |
| return n[2] == 0 or nullable(n[1]) | |
| return True | |
| def _chars(n): | |
| """First-level char set of a quantified atom, else None.""" | |
| while n[0] == "seq" and len(n[1]) == 1: | |
| n = n[1][0] | |
| if n[0] == "rep": | |
| return _chars(n[1]) | |
| return n[1] if n[0] == "set" else None | |
| def ambiguous(n) -> bool: | |
| r"""Body shapes that backtrack exponentially under an outer + or *: | |
| (a+)+, (\s*\s+)+, (\w+\d+)* ... i.e. a nested unbounded repeat, or two | |
| adjacent quantified atoms with overlapping chars.""" | |
| while n[0] == "seq" and len(n[1]) == 1: | |
| n = n[1][0] | |
| if n[0] == "rep" and (n[3] is None or n[3] > 1): | |
| return True | |
| if n[0] == "alt": | |
| return any(map(ambiguous, n[1])) | |
| if n[0] != "seq": | |
| return False | |
| xs = n[1] | |
| for x, y in zip(xs, xs[1:]): | |
| if x[0] == "rep" and y[0] == "rep": | |
| cx, cy = _chars(x), _chars(y) | |
| if cx is None or cy is None or cx & cy: | |
| return True | |
| return False | |
| def parse(pattern: str): | |
| return Parser(pattern).parse() | |
| def in_scope(pattern: str, flags: str = "") -> bool: | |
| try: | |
| parse(pattern) | |
| except (ValueError, IndexError): | |
| return False | |
| return set(flags) <= {"i"} and engine.compiles(pattern, flags) | |
| # ---------------------------------------------------------------- sampler | |
| _SORTED: dict = {} | |
| def _sorted(s): | |
| t = _SORTED.get(s) | |
| if t is None: | |
| t = _SORTED[s] = tuple(sorted(s)) | |
| return t | |
| def sample(n, rng: random.Random, ci=False) -> str: | |
| k = n[0] | |
| if k == "set": | |
| if not n[1]: | |
| raise ValueError("empty set") | |
| c = rng.choice(_sorted(n[1])) | |
| return c.swapcase() if ci and rng.random() < 0.3 else c | |
| if k == "seq": | |
| return "".join(sample(x, rng, ci) for x in n[1]) | |
| if k == "alt": | |
| return sample(rng.choice(n[1]), rng, ci) | |
| if k == "rep": | |
| hi = n[3] if n[3] is not None else n[2] + rng.choice([0, 1, 2, 3, 5]) | |
| return "".join(sample(n[1], rng, ci) for _ in range(rng.randint(n[2], min(hi, n[2] + 6)))) | |
| return "" | |
| NOISE = string.ascii_letters + string.digits + " .-_@/:#$," | |
| def mutate(s: str, rng: random.Random) -> str: | |
| op = rng.randrange(7) | |
| i = rng.randrange(len(s) + 1) | |
| c = rng.choice(NOISE) | |
| if op == 0 and s: | |
| return s[:i] + s[i + 1:] # delete | |
| if op == 1: | |
| return s[:i] + c + s[i:] # insert | |
| if op == 2 and s: | |
| i = min(i, len(s) - 1) | |
| return s[:i] + c + s[i + 1:] # substitute | |
| if op == 3: | |
| return c + s # prefix (breaks ^) | |
| if op == 4: | |
| return s + c # suffix (breaks $) | |
| if op == 5 and len(s) > 1: | |
| return s[: rng.randrange(1, len(s))] # truncate | |
| return s.swapcase() | |
| def examples(pattern, flags, rng, k=5): | |
| """-> (pos, neg) labeled by JS, or None if degenerate or slow (ReDoS).""" | |
| t = time.perf_counter() | |
| try: | |
| ex = _examples(pattern, flags, rng, k) | |
| except Exception: # QuickJS time limit, empty set, ... | |
| return None | |
| return ex if time.perf_counter() - t < 0.05 else None | |
| def _examples(pattern, flags, rng, k): | |
| tree = parse(pattern) | |
| ci = "i" in flags | |
| cand = list(dict.fromkeys(sample(tree, rng, ci) for _ in range(24))) | |
| cand = [s for s in cand if s and len(s) <= 60] | |
| if len(cand) < 3: | |
| return None | |
| ok = engine.test(pattern, flags, cand) | |
| pos = [s for s, m in zip(cand, ok) if m] | |
| if len(pos) < 3: | |
| return None | |
| muts = [m for m in dict.fromkeys(mutate(rng.choice(pos), rng) for _ in range(40)) if m not in pos] | |
| muts += ["".join(rng.choices(NOISE, k=rng.randint(1, 12))) for _ in range(6)] | |
| ok = engine.test(pattern, flags, muts) | |
| neg = [s for s, m in zip(muts, ok) if not m and s.strip()] | |
| if len(neg) < 3: | |
| return None | |
| rng.shuffle(pos), rng.shuffle(neg) | |
| return pos[:k], neg[:k] | |
| # ---------------------------------------------------------------- generator | |
| WORDS = """cat dog bird fish red green blue black white gray error warn info debug | |
| fatal http https ftp www com org net io dev app api user admin root guest test | |
| id key token name file data log img src lib bin tmp home yes no true false null | |
| on off get post put delete open close start stop begin end jan feb mar apr may | |
| jun jul aug sep oct nov dec mon tue wed thu fri sat sun am pm usd eur gbp jpg | |
| png gif pdf txt csv json xml html css js py md zip tar gz mp3 mp4 wav todo fixme | |
| note hello world foo bar baz qux the and or not color colour gray grey cancel | |
| ok fail pass high low new old big small north south east west""".split() | |
| CLASSES = ["[a-z]", "[A-Z]", "[a-zA-Z]", "[0-9]", "[a-z0-9]", "[A-Za-z0-9_]", | |
| "[aeiou]", "[^aeiou]", "[a-f0-9]", "[A-F0-9]", "[0-7]", "[1-9]", | |
| "[01]", "[^0-9]", "[^\\s]", "[-_.]", "[.,;:!?]", "[+-]", "[a-zA-Z ]", | |
| "[^a-z]", "[A-Z0-9]", "[a-zA-Z0-9.-]", "[^,]", "[^\"]", "[xyz]"] | |
| ESCAPES = ["\\d", "\\w", "\\s", "\\D", "\\W", "\\S", "."] | |
| SEPS = ["-", "\\.", "_", ":", "\\/", " ", "@", ",", "\\s", "\\s*", "\\s+", "#", "="] | |
| def lit(s): | |
| return "".join("\\" + c if c in SPECIAL else c for c in s) | |
| def rand_class(rng): | |
| if rng.random() < 0.2: | |
| cs = "".join(sorted(rng.sample(string.ascii_lowercase, rng.randint(2, 5)))) | |
| return f"[{'^' if rng.random() < 0.2 else ''}{cs}]" | |
| return rng.choice(CLASSES) | |
| def quant(rng, allow_none=True): | |
| r = rng.random() | |
| lo = rng.randint(1, 5) | |
| q = ("" if allow_none else "+") if r < 0.25 else \ | |
| "?" if r < 0.35 else "*" if r < 0.47 else "+" if r < 0.65 else \ | |
| f"{{{lo}}}" if r < 0.8 else f"{{{lo},}}" if r < 0.86 else f"{{{lo},{lo + rng.randint(1, 6)}}}" | |
| return q + ("?" if q and rng.random() < 0.04 else "") | |
| def atom(rng): | |
| r = rng.random() | |
| if r < 0.5: | |
| return rand_class(rng) | |
| if r < 0.85: | |
| return rng.choice(ESCAPES) | |
| return lit(rng.choice(string.ascii_lowercase + "-_.@#$%!")) | |
| def group(rng, body): | |
| return f"({'?:' if rng.random() < 0.4 else ''}{body})" | |
| def chunk(rng, depth=0): | |
| r = rng.random() | |
| if r < 0.18: | |
| return lit(rng.choice(WORDS)) | |
| if r < 0.55: | |
| return atom(rng) + quant(rng) | |
| if r < 0.72: | |
| alts = rng.sample(WORDS, rng.randint(2, 4)) | |
| return group(rng, "|".join(map(lit, alts))) + ("?" if rng.random() < 0.15 else "") | |
| if r < 0.85 and depth < 1: | |
| body = rng.choice(SEPS) + chunk(rng, depth + 1) | |
| return group(rng, body) + rng.choice(["?", "*", "+", "?", f"{{{rng.randint(2, 4)}}}"]) | |
| return rng.choice(SEPS) | |
| def anchor(rng, body): | |
| r = rng.random() | |
| if r < 0.4: | |
| return f"^{body}$" | |
| if r < 0.55: | |
| return f"^{body}" | |
| if r < 0.65: | |
| return f"{body}$" | |
| if r < 0.8: | |
| return f"\\b{body}\\b" | |
| return body | |
| def random_regex(rng): | |
| body = "".join(chunk(rng) for _ in range(rng.choice([1, 1, 2, 2, 2, 3, 3, 4]))) | |
| p = anchor(rng, body) | |
| letters = any(c.isalpha() for c in p.replace("\\", " \\")) | |
| return p, ("i" if letters and rng.random() < 0.15 else "") | |
| def C(*xs): | |
| return lambda rng: rng.choice(xs) | |
| def opt(rng, s, p=0.5): | |
| return s if rng.random() < p else "" | |
| # realistic shapes; each call randomizes details | |
| TEMPLATES = [ | |
| lambda r: f"^\\d{{{r.choice([3, 4, 5, 6, 8])}}}$", | |
| lambda r: "^\\d{5}" + opt(r, "(?:-\\d{4})?") + "$", | |
| lambda r: "^" + opt(r, "\\(?") + "\\d{3}" + opt(r, "\\)?") + C("-", "[-.\\s]?", "\\s?", "[-. ]")(r) + "\\d{3}" + C("-", "[-.]?", "[-. ]")(r) + "\\d{4}$", | |
| lambda r: (lambda s: f"^\\d{{4}}{s}\\d{{2}}{s}\\d{{2}}$")(C("-", "\\/", "\\.")(r)), | |
| lambda r: (lambda s: f"^\\d{{1,2}}{s}\\d{{1,2}}{s}\\d{{{r.choice(['2', '4', '2,4'])}}}$")(C("\\/", "-", "\\.")(r)), | |
| lambda r: "^" + C("\\d{2}", "[01]\\d", "\\d{1,2}", "(?:[01]\\d|2[0-3])")(r) + ":[0-5]\\d" + opt(r, "(?::[0-5]\\d)?") + opt(r, "\\s?(?:am|pm)", 0.3) + "$", | |
| lambda r: "^#" + C("[0-9a-fA-F]{6}", "[0-9a-f]{6}", "(?:[0-9a-fA-F]{3}){1,2}", "[A-F0-9]{6}")(r) + "$", | |
| lambda r: "^" + C("\\d{1,3}(?:\\.\\d{1,3}){3}", "(?:\\d{1,3}\\.){3}\\d{1,3}")(r) + "$", | |
| lambda r: "^" + C("[\\w.+-]+", "[a-z0-9._%+-]+", "\\w+", "[a-zA-Z0-9._-]+")(r) + "@" + C("[\\w-]+", "[a-z0-9-]+", "\\w+")(r) + C("\\.[a-z]{2,}", "\\.[a-zA-Z]{2,6}", "(?:\\.\\w+)+", "\\.(?:com|org|net)")(r) + "$", | |
| lambda r: "^" + C("https?", "https", "(?:https?|ftp)")(r) + ":\\/\\/" + opt(r, "(?:www\\.)?") + C("[\\w-]+(?:\\.[\\w-]+)+", "[^\\s\\/]+", "[a-z0-9.-]+\\.[a-z]{2,}")(r) + opt(r, "(?:\\/\\S*)?") + "$", | |
| lambda r: "\\." + group(r, "|".join(r.sample(["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"], r.randint(2, 4)))) + "$", | |
| lambda r: "\\." + group(r, "|".join(r.sample(["pdf", "docx?", "txt", "md", "csv", "xlsx?", "json", "ya?ml"], r.randint(2, 4)))) + "$", | |
| lambda r: C("#\\w+", "#[a-zA-Z]\\w*", "\\B#\\w+", "#[a-z0-9_]+")(r), | |
| lambda r: C("@\\w+", "@[a-zA-Z0-9_]{1,15}", "\\B@\\w+")(r), | |
| lambda r: "^\\$" + C("\\d+", "\\d{1,3}(?:,\\d{3})*")(r) + opt(r, "(?:\\.\\d{2})?", 0.7) + "$", | |
| lambda r: "^[a-z" + opt(r, "A-Z") + "][a-z" + opt(r, "A-Z") + "0-9_" + opt(r, "-") + f"]{{{r.randint(2, 4)},{r.randint(12, 20)}}}$", | |
| lambda r: "^" + opt(r, "v?") + "\\d+\\.\\d+" + C("\\.\\d+", "(?:\\.\\d+)?")(r) + opt(r, "(?:-[a-z0-9.]+)?", 0.3) + "$", | |
| lambda r: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", | |
| lambda r: "^" + C("\\d{4}(?:[- ]?\\d{4}){3}", "(?:\\d{4}[- ]?){3}\\d{4}", "\\d{16}")(r) + "$", | |
| lambda r: "^" + opt(r, "-?") + C("\\d+", "\\d{1,3}", "100|\\d{1,2}", "\\d+(?:\\.\\d+)?")(r) + "%$", | |
| lambda r: "^" + opt(r, "[+-]?") + C("\\d+", "\\d*\\.\\d+", "\\d+(?:\\.\\d+)?", "\\d+(?:\\.\\d*)?")(r) + opt(r, "(?:[eE][+-]?\\d+)?", 0.25) + "$", | |
| lambda r: "^0x[0-9a-fA-F]+$" if r.random() < 0.5 else "^(?:0x)?[0-9a-f]+$", | |
| lambda r: "^[01]+$" if r.random() < 0.5 else f"^[01]{{{r.choice([4, 8, 16])}}}$", | |
| lambda r: f"\\b{lit(r.choice(WORDS))}\\w*" if r.random() < 0.5 else f"\\w*{lit(r.choice(['ing', 'ed', 'ly', 'tion', 's', 'er']))}\\b", | |
| lambda r: f"^{lit(r.choice(WORDS))}" + C("\\b", ".*", "\\s", ":")(r), | |
| lambda r: f"{lit(r.choice(WORDS))}$", | |
| lambda r: "\\b" + group(r, "|".join(map(lit, r.sample(WORDS, r.randint(2, 5))))) + "\\b", | |
| lambda r: "^" + group(r, "|".join(map(lit, r.sample(WORDS, r.randint(2, 5))))) + "$", | |
| lambda r: "^" + C("[A-Z]", "[A-Z][a-z]+", "[a-z]")(r) + C("[a-z]*", "\\w*", "[a-z]+(?: [a-z]+)*")(r) + opt(r, "[.!?]") + "$", | |
| lambda r: "^\\s*$" if r.random() < 0.3 else C("^\\s+", "\\s+$", "\\s{2,}", "^\\s+|\\s+$")(r), | |
| lambda r: "^" + C("[A-Z]{2}", "[A-Z]{3}", "[A-Z]{2,3}")(r) + C("-", "", "\\s?", "_")(r) + f"\\d{{{r.randint(2, 6)}}}$", | |
| lambda r: "^[A-Z]{1,2}\\d[A-Z\\d]? ?\\d[A-Z]{2}$", | |
| lambda r: "<" + C("[a-z]+", "\\/?[a-z]+", "[a-z][a-z0-9]*")(r) + C(">", "[^>]*>", "\\s*\\/?>")(r), | |
| lambda r: "\"" + C("[^\"]*", "[^\"]+", ".*?")(r) + "\"", | |
| lambda r: "\\(" + C("[^)]*", "\\d+", "\\w+")(r) + "\\)", | |
| lambda r: "\\[" + C("[^\\]]*", "\\d+", "\\w+")(r) + "\\]", | |
| lambda r: "^" + group(r, "|".join(r.sample(["ERROR", "WARN", "INFO", "DEBUG", "FATAL", "TRACE"], r.randint(2, 4)))) + C(":", "\\s", "\\b", ":\\s.*")(r), | |
| lambda r: "^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}" + opt(r, ":\\d{2}") + opt(r, "(?:Z|[+-]\\d{2}:\\d{2})?", 0.3) + "$", | |
| lambda r: "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", | |
| lambda r: "^" + C("\\d+", "[1-9]\\d*", "[1-9]\\d{0,2}", "0|[1-9]\\d*")(r) + "$", | |
| lambda r: "^(?:" + C("\\w+", "[a-z]+", "\\d+")(r) + C(",", ";", "\\|", ",\\s*")(r) + ")*" + C("\\w+", "[a-z]+", "\\d+")(r) + "$", | |
| lambda r: "^" + C("[A-Za-z]{2,}", "[A-Z][a-z]+")(r) + " " + C("[A-Za-z]{2,}", "[A-Z][a-z]+")(r) + "$", | |
| lambda r: C("colou?r", "gr[ae]y", "(?:cancell?ed)", "favou?rite", "analy[sz]e")(r), | |
| lambda r: C("\\d+(?:px|em|rem|%)", "-?\\d+(?:\\.\\d+)?(?:px|em|rem)", "\\d+(?:ms|s)")(r), | |
| lambda r: "^[a-z]+(?:-[a-z]+)*$" if r.random() < 0.5 else C("^[a-z]+(?:_[a-z]+)*$", "^[a-z]+(?:[A-Z][a-z]+)*$", "^[A-Z][a-z]+(?:[A-Z][a-z]+)*$")(r), | |
| lambda r: "^\\/" + C("[\\w-]+(?:\\/[\\w-]+)*", "(?:[\\w.-]+\\/)*[\\w.-]+", "[a-z]+\\/\\d+")(r) + opt(r, "\\/?") + "$", | |
| lambda r: "^" + C("[A-Z]{3}", "[A-Z]{2}")(r) + "\\s?\\d+(?:\\.\\d{2})?$", | |
| lambda r: "\\b\\d{1,2}(?:st|nd|rd|th)\\b", | |
| lambda r: "^(?:" + "|".join(r.sample(["mon", "tue", "wed", "thu", "fri", "sat", "sun"], r.randint(3, 7))) + ")$", | |
| lambda r: "^(?:" + "|".join(r.sample(["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"], r.randint(3, 6))) + ")" + opt(r, "[a-z]*") + "$", | |
| lambda r: "^[a-zA-Z0-9]{" + str(r.choice([6, 8, 10, 12])) + ",}$", | |
| lambda r: "^[^\\s]+$" if r.random() < 0.5 else "^\\S+$", | |
| lambda r: "^.{" + str(r.randint(1, 5)) + "," + str(r.randint(8, 40)) + "}$", | |
| ] | |
| def flags_for(rng, p): | |
| return "i" if any(c.isalpha() for c in p if c not in "dwsDWSbB") and rng.random() < 0.15 else "" | |
| def regex(rng): | |
| if rng.random() < 0.5: | |
| p = rng.choice(TEMPLATES)(rng) | |
| return p, flags_for(rng, p) | |
| return random_regex(rng) | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--n", type=int, default=60000) | |
| ap.add_argument("--out", default=Path(__file__).parents[1] / "data/regex.jsonl") | |
| ap.add_argument("--seed", type=int, default=0) | |
| a = ap.parse_args() | |
| rng, seen, rows, tries = random.Random(a.seed), set(), 0, 0 | |
| with open(a.out, "w") as f: | |
| while rows < a.n and tries < a.n * 20: | |
| tries += 1 | |
| p, fl = regex(rng) | |
| key = (p, fl) | |
| if key in seen or not (2 <= len(p) <= 72) or not in_scope(p, fl): | |
| continue | |
| seen.add(key) | |
| ex = examples(p, fl, rng) | |
| if not ex: | |
| continue | |
| f.write(json.dumps({"id": rows, "regex": engine.literal(p, fl), "pattern": p, | |
| "flags": fl, "pos": ex[0], "neg": ex[1]}) + "\n") | |
| rows += 1 | |
| print(f"{rows} regexes from {tries} tries -> {a.out}") | |
| if __name__ == "__main__": | |
| main() | |