Fix five bugs surfaced by commonmark-app (jolt-3xur, dl4s, ik3a, w2wf, xtss) (#158)
* Fix four bugs surfaced by the commonmark-app example
- regex: bounded quantifiers {n,m} no longer expand exponentially. The
desugaring inlined the continuation per optional level, doubling it each
time, so {0,61} built a PEG the compiler expanded to ~2^61 nodes and hung.
Each level is now its own grammar rule referenced by name (jolt-3xur).
- strings are a seqable of chars for vec/set/into, matching seq. They went
through realize-for-iteration, which had no string case, so into/set got
raw bytes (code points) and set threw; vec used string/from-bytes (1-char
strings). realize-for-iteration now maps make-char over the bytes, and
core-vec matches (jolt-dl4s).
- clojure.string/split takes Java Pattern.split limit semantics: negative
keeps trailing empties, 0/omitted drops them (a no-match result stays
[input]), positive caps the part count with the remainder unsplit. It used
to delegate the limit to take, so a negative limit returned [] and the
2-arg form never trimmed trailing empties (jolt-ik3a).
- System/exit is registered (maps to os/exit), so (System/exit n) works
(jolt-w2wf).
* regex: single-digit backreferences \1..\9 (jolt-xtss)
\1..\9 now match the text captured by the corresponding group, so
patterns like ([-*_])\1\1 or (\w+) \1 work. The parser records which
groups are referenced; a referenced group additionally captures its text
under a tag and the backref compiles to a PEG (backmatch). Only referenced
groups change — they match possessively (the CPS-over-possessive-PEG engine
can't backtrack into a tagged capture), so backtracking back into a
backreferenced group isn't supported (rare). Unreferenced groups keep full
backtracking and position-based result capture unchanged.
---------
Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
parent
8192fc9541
commit
951a3000e6
9 changed files with 111 additions and 25 deletions
|
|
@ -51,10 +51,16 @@
|
||||||
(str-reverse-b s))
|
(str-reverse-b s))
|
||||||
|
|
||||||
(defn split
|
(defn split
|
||||||
([s re]
|
([s re] (split s re 0))
|
||||||
(vec (str-split re s)))
|
|
||||||
([s re limit]
|
([s re limit]
|
||||||
(vec (take limit (str-split re s)))))
|
;; Java Pattern.split semantics: limit > 0 caps the parts (trailing empties
|
||||||
|
;; kept); limit < 0 splits fully and keeps trailing empties; limit 0 (the
|
||||||
|
;; default) splits fully then drops trailing empty strings — but a no-match
|
||||||
|
;; result ([input], the only 1-element case) is returned as-is.
|
||||||
|
(let [parts (vec (str-split re s (if (pos? limit) limit nil)))]
|
||||||
|
(if (and (zero? limit) (> (count parts) 1))
|
||||||
|
(loop [v parts] (if (and (seq v) (= "" (peek v))) (recur (pop v)) v))
|
||||||
|
parts))))
|
||||||
|
|
||||||
(defn split-lines
|
(defn split-lines
|
||||||
"Split s on \\n or \\r\\n, returning a vector of lines."
|
"Split s on \\n or \\r\\n, returning a vector of lines."
|
||||||
|
|
|
||||||
|
|
@ -418,7 +418,7 @@
|
||||||
(array? coll) (make-vec coll)
|
(array? coll) (make-vec coll)
|
||||||
(tuple? coll) (make-vec coll)
|
(tuple? coll) (make-vec coll)
|
||||||
(struct? coll) (make-vec (map |(in (kvs coll) (+ (* $ 2) 1)) (range (/ (length (kvs coll)) 2))))
|
(struct? coll) (make-vec (map |(in (kvs coll) (+ (* $ 2) 1)) (range (/ (length (kvs coll)) 2))))
|
||||||
(string? coll) (make-vec (map |(string/from-bytes $) (string/bytes coll)))
|
(string? coll) (make-vec (map make-char (string/bytes coll)))
|
||||||
(make-vec @[]))))
|
(make-vec @[]))))
|
||||||
|
|
||||||
# Bulk-build a map value from a native array of [k v] pairs, mirroring
|
# Bulk-build a map value from a native array of [k v] pairs, mirroring
|
||||||
|
|
|
||||||
|
|
@ -247,10 +247,16 @@
|
||||||
# (core/20-coll.clj) — pure over get on the tagged value the constructor builds.
|
# (core/20-coll.clj) — pure over get on the tagged value the constructor builds.
|
||||||
|
|
||||||
# String split/replace that accept either a literal string or a regex value.
|
# String split/replace that accept either a literal string or a regex value.
|
||||||
(defn core-str-split [pat s]
|
(defn core-str-split [pat s &opt limit]
|
||||||
(if (regex? pat)
|
(if (regex? pat)
|
||||||
(re-split pat s)
|
(re-split pat s limit)
|
||||||
(string/split pat s)))
|
(let [parts (string/split pat s)]
|
||||||
|
# literal separator: a positive limit caps the part count; rejoin the tail
|
||||||
|
# with the (constant) separator to recover the unsplit remainder.
|
||||||
|
(if (and limit (> limit 0) (> (length parts) limit))
|
||||||
|
(array/push (array/slice parts 0 (- limit 1))
|
||||||
|
(string/join (array/slice parts (- limit 1)) pat))
|
||||||
|
parts))))
|
||||||
(defn core-str-replace-all [pat repl s]
|
(defn core-str-replace-all [pat repl s]
|
||||||
(if (regex? pat)
|
(if (regex? pat)
|
||||||
(re-replace-all pat s repl)
|
(re-replace-all pat s repl)
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,9 @@
|
||||||
(phm? c) (phm-entries c)
|
(phm? c) (phm-entries c)
|
||||||
# sorted colls iterate their comparator-ordered entries/elements
|
# sorted colls iterate their comparator-ordered entries/elements
|
||||||
(core-sorted? c) (sorted-entries-arr c)
|
(core-sorted? c) (sorted-entries-arr c)
|
||||||
|
# a string is a seqable of CHARS in Clojure (not bytes/1-char strings) — mirror
|
||||||
|
# core-seq so vec/set/into over a string agree with seq (jolt-dl4s)
|
||||||
|
(string? c) (tuple ;(map make-char (string/bytes c)))
|
||||||
# byte array (Janet buffer) -> array of byte values
|
# byte array (Janet buffer) -> array of byte values
|
||||||
(buffer? c) (let [a @[]] (each x c (array/push a x)) a)
|
(buffer? c) (let [a @[]] (each x c (array/push a x)) a)
|
||||||
# struct map literal (no :jolt/type marker — not a symbol/char) -> entries
|
# struct map literal (no :jolt/type marker — not a symbol/char) -> entries
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,9 @@
|
||||||
"file.separator" "/"
|
"file.separator" "/"
|
||||||
"user.dir" (os/cwd)
|
"user.dir" (os/cwd)
|
||||||
"user.home" (or (os/getenv "HOME") "")
|
"user.home" (or (os/getenv "HOME") "")
|
||||||
"java.io.tmpdir" (or (os/getenv "TMPDIR") "/tmp")})})
|
"java.io.tmpdir" (or (os/getenv "TMPDIR") "/tmp")})
|
||||||
|
# terminate the process with the given status code
|
||||||
|
"exit" (fn [&opt status] (os/exit (if (nil? status) 0 status)))})
|
||||||
|
|
||||||
# sentinels portable code compares against. jolt numbers are doubles, so these
|
# sentinels portable code compares against. jolt numbers are doubles, so these
|
||||||
# are the f64 approximations.
|
# are the f64 approximations.
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@
|
||||||
# Supported: literals, `.`, char classes `[...]`/`[^...]` (ranges, POSIX,
|
# Supported: literals, `.`, char classes `[...]`/`[^...]` (ranges, POSIX,
|
||||||
# `\d \w \s` etc.), quantifiers `* + ? {n} {n,} {n,m}` (greedy + lazy `?`),
|
# `\d \w \s` etc.), quantifiers `* + ? {n} {n,} {n,m}` (greedy + lazy `?`),
|
||||||
# groups `(...)` (numbered), non-capturing `(?:...)`, lookahead `(?=...)`/`(?!...)`,
|
# groups `(...)` (numbered), non-capturing `(?:...)`, lookahead `(?=...)`/`(?!...)`,
|
||||||
# alternation `|`, anchors `^ $ \b \B`, escapes, inline flag `(?i)`.
|
# alternation `|`, anchors `^ $ \b \B`, escapes, inline flag `(?i)`, and
|
||||||
# Not supported: lookbehind, backreferences, named groups (rare; documented).
|
# single-digit backreferences `\1`..`\9` (a backreferenced group matches
|
||||||
|
# possessively — no backtracking back into it; rare in practice).
|
||||||
|
# Not supported: lookbehind, named groups (rare; documented).
|
||||||
|
|
||||||
(defn- chr [s] (get s 0))
|
(defn- chr [s] (get s 0))
|
||||||
(defn regex? [x] (and (table? x) (= :jolt/regex (x :jolt/type))))
|
(defn regex? [x] (and (table? x) (= :jolt/regex (x :jolt/type))))
|
||||||
|
|
@ -189,6 +191,10 @@
|
||||||
p {:op :pred :peg p}
|
p {:op :pred :peg p}
|
||||||
(= nc (chr "b")) {:op :anchor :kind :wordb}
|
(= nc (chr "b")) {:op :anchor :kind :wordb}
|
||||||
(= nc (chr "B")) {:op :anchor :kind :nwordb}
|
(= nc (chr "B")) {:op :anchor :kind :nwordb}
|
||||||
|
# \1..\9 backreference (single digit) — record the referenced group so
|
||||||
|
# the emitter knows to tag-capture its text
|
||||||
|
(and (>= nc (chr "1")) (<= nc (chr "9")))
|
||||||
|
(let [n (- nc (chr "0"))] (put (st :refs) n true) {:op :backref :n n})
|
||||||
{:op :char :b (esc-byte nc) :ci ci})))
|
{:op :char :b (esc-byte nc) :ci ci})))
|
||||||
(= c (chr "^")) (do (++ (st :pos)) {:op :anchor :kind :start})
|
(= c (chr "^")) (do (++ (st :pos)) {:op :anchor :kind :start})
|
||||||
(= c (chr "$")) (do (++ (st :pos)) {:op :anchor :kind :end})
|
(= c (chr "$")) (do (++ (st :pos)) {:op :anchor :kind :end})
|
||||||
|
|
@ -241,9 +247,9 @@
|
||||||
(if (= 1 (length branches)) (branches 0) {:op :alt :items branches})))
|
(if (= 1 (length branches)) (branches 0) {:op :alt :items branches})))
|
||||||
|
|
||||||
(defn- parse [source]
|
(defn- parse [source]
|
||||||
(def st @{:s source :pos 0 :ngroup 0 :ci false :ml false :dotall false})
|
(def st @{:s source :pos 0 :ngroup 0 :ci false :ml false :dotall false :refs @{}})
|
||||||
(def ast (parse-alt st))
|
(def ast (parse-alt st))
|
||||||
[ast (st :ngroup) (st :ml)])
|
[ast (st :ngroup) (st :ml) (st :refs)])
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Emit: AST -> PEG grammar (continuation passing)
|
# Emit: AST -> PEG grammar (continuation passing)
|
||||||
|
|
@ -256,7 +262,7 @@
|
||||||
~(set ,(string/from-bytes (lower-b b) (upper-b b)))
|
~(set ,(string/from-bytes (lower-b b) (upper-b b)))
|
||||||
~(set ,(string/from-bytes b))))
|
~(set ,(string/from-bytes b))))
|
||||||
|
|
||||||
(defn- make-emitter [grammar ml]
|
(defn- make-emitter [grammar ml refs]
|
||||||
(var ctr 0)
|
(var ctr 0)
|
||||||
(defn fresh [] (++ ctr) (keyword (string "r" ctr)))
|
(defn fresh [] (++ ctr) (keyword (string "r" ctr)))
|
||||||
(var emit nil)
|
(var emit nil)
|
||||||
|
|
@ -292,19 +298,38 @@
|
||||||
~(choice ,(emit item (keyword r)) ,k)
|
~(choice ,(emit item (keyword r)) ,k)
|
||||||
~(choice ,k ,(emit item (keyword r)))))
|
~(choice ,k ,(emit item (keyword r)))))
|
||||||
(keyword r))
|
(keyword r))
|
||||||
(do (var acc k) (var c (- hi lo))
|
# Each optional level is its OWN grammar rule that
|
||||||
|
# references the previous level by name. Inlining instead
|
||||||
|
# (choice (emit item acc) acc) duplicates `acc` per level,
|
||||||
|
# so {0,n} built a structure the PEG compiler expanded to
|
||||||
|
# 2^n nodes and hung (jolt-3xur). Naming keeps it O(n).
|
||||||
|
(do (def kr (fresh)) (put grammar kr k)
|
||||||
|
(var acc (keyword kr)) (var c (- hi lo))
|
||||||
(while (> c 0)
|
(while (> c 0)
|
||||||
(set acc (if greedy ~(choice ,(emit item acc) ,acc)
|
(let [r (fresh)]
|
||||||
~(choice ,acc ,(emit item acc))))
|
(put grammar r (if greedy ~(choice ,(emit item acc) ,acc)
|
||||||
|
~(choice ,acc ,(emit item acc))))
|
||||||
|
(set acc (keyword r)))
|
||||||
(-- c))
|
(-- c))
|
||||||
acc)))
|
acc)))
|
||||||
(var acc tail) (var c lo)
|
(var acc tail) (var c lo)
|
||||||
(while (> c 0) (set acc (emit item acc)) (-- c))
|
(while (> c 0) (set acc (emit item acc)) (-- c))
|
||||||
acc)
|
acc)
|
||||||
:group (let [n (ast :n)]
|
:group (let [n (ast :n)]
|
||||||
~(sequence (/ (position) ,(fn [p] [n :s p]))
|
(if (and refs (refs n))
|
||||||
,(emit (ast :item)
|
# backreferenced group: also capture its text under tag :gN so a
|
||||||
~(sequence (/ (position) ,(fn [p] [n :e p])) ,k))))
|
# later \N can (backmatch). The item runs with just the end-marker
|
||||||
|
# as its continuation, so it matches POSSESSIVELY — backtracking
|
||||||
|
# INTO a backreferenced group isn't supported (rare; the usual
|
||||||
|
# (x)...\1 patterns don't need it).
|
||||||
|
(let [tag (keyword (string "g" n))]
|
||||||
|
~(sequence (/ (position) ,(fn [p] [n :s p]))
|
||||||
|
(<- ,(emit (ast :item) ~(/ (position) ,(fn [p] [n :e p]))) ,tag)
|
||||||
|
,k))
|
||||||
|
~(sequence (/ (position) ,(fn [p] [n :s p]))
|
||||||
|
,(emit (ast :item)
|
||||||
|
~(sequence (/ (position) ,(fn [p] [n :e p])) ,k)))))
|
||||||
|
:backref ~(sequence (backmatch ,(keyword (string "g" (ast :n)))) ,k)
|
||||||
:ncgroup (emit (ast :item) k)
|
:ncgroup (emit (ast :item) k)
|
||||||
:look (if (ast :neg)
|
:look (if (ast :neg)
|
||||||
~(sequence (not ,(emit (ast :item) 0)) ,k)
|
~(sequence (not ,(emit (ast :item) 0)) ,k)
|
||||||
|
|
@ -327,9 +352,9 @@
|
||||||
emit)
|
emit)
|
||||||
|
|
||||||
(defn compile-regex [source]
|
(defn compile-regex [source]
|
||||||
(def [ast ngroups ml] (parse source))
|
(def [ast ngroups ml refs] (parse source))
|
||||||
(def grammar @{})
|
(def grammar @{})
|
||||||
(def emit (make-emitter grammar ml))
|
(def emit (make-emitter grammar ml refs))
|
||||||
# group 0 = whole match: mark start, body, mark end
|
# group 0 = whole match: mark start, body, mark end
|
||||||
(def body (emit ast ~(sequence (/ (position) ,(fn [p] [0 :e p])) 0)))
|
(def body (emit ast ~(sequence (/ (position) ,(fn [p] [0 :e p])) 0)))
|
||||||
(put grammar :main ~(sequence (/ (position) ,(fn [p] [0 :s p])) ,body))
|
(put grammar :main ~(sequence (/ (position) ,(fn [p] [0 :s p])) ,body))
|
||||||
|
|
@ -354,8 +379,11 @@
|
||||||
(def starts (array/new-filled (+ ngroups 1)))
|
(def starts (array/new-filled (+ ngroups 1)))
|
||||||
(def ends (array/new-filled (+ ngroups 1)))
|
(def ends (array/new-filled (+ ngroups 1)))
|
||||||
(each m marks
|
(each m marks
|
||||||
(let [n (m 0)]
|
# backreferenced groups also leave a tagged TEXT capture (a string) on the
|
||||||
(if (= (m 1) :s) (put starts n (m 2)) (put ends n (m 2)))))
|
# stack — skip those; only the [n :s/:e pos] position tuples carry group spans
|
||||||
|
(when (indexed? m)
|
||||||
|
(let [n (m 0)]
|
||||||
|
(if (= (m 1) :s) (put starts n (m 2)) (put ends n (m 2))))))
|
||||||
(def groups (array/new-filled (+ ngroups 1)))
|
(def groups (array/new-filled (+ ngroups 1)))
|
||||||
(var n 0)
|
(var n 0)
|
||||||
(while (<= n ngroups)
|
(while (<= n ngroups)
|
||||||
|
|
@ -401,10 +429,14 @@
|
||||||
# `(if-let [m (re-seq ...)] ...)` works — an empty seq would be truthy.
|
# `(if-let [m (re-seq ...)] ...)` works — an empty seq would be truthy.
|
||||||
(if (= 0 (length out)) nil out))
|
(if (= 0 (length out)) nil out))
|
||||||
|
|
||||||
(defn re-split [re s]
|
(defn re-split [re s &opt limit]
|
||||||
|
# limit (Java Pattern.split n>0): at most `limit` parts — stop after limit-1
|
||||||
|
# splits and keep the rest of the string as the final unsplit part. nil/<=0
|
||||||
|
# splits at every match.
|
||||||
(def re (re-pattern re))
|
(def re (re-pattern re))
|
||||||
|
(def lim (if (and limit (> limit 0)) limit nil))
|
||||||
(def out @[]) (var pos 0) (var last 0)
|
(def out @[]) (var pos 0) (var last 0)
|
||||||
(while (<= pos (length s))
|
(while (and (<= pos (length s)) (or (nil? lim) (< (length out) (dec lim))))
|
||||||
(def g (match-at re s pos))
|
(def g (match-at re s pos))
|
||||||
(if (and g (> (length (in g 0)) 0))
|
(if (and g (> (length (in g 0)) 0))
|
||||||
(do (array/push out (string/slice s last pos))
|
(do (array/push out (string/slice s last pos))
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,8 @@
|
||||||
"[(Boolean/parseBoolean \"true\") (Boolean/parseBoolean \"false\") (Boolean/parseBoolean \"yes\")]"]
|
"[(Boolean/parseBoolean \"true\") (Boolean/parseBoolean \"false\") (Boolean/parseBoolean \"yes\")]"]
|
||||||
["System/getenv is a map" "true"
|
["System/getenv is a map" "true"
|
||||||
"(string? (get (System/getenv) \"HOME\"))"]
|
"(string? (get (System/getenv) \"HOME\"))"]
|
||||||
|
# registered, so (System/exit n) resolves; actual exit verified via subprocess
|
||||||
|
["System/exit resolves" "true" "(fn? System/exit)"]
|
||||||
# NOT every? alone — it held vacuously while seq over a raw host table
|
# NOT every? alone — it held vacuously while seq over a raw host table
|
||||||
# yielded nothing, hiding that read-system-env came back empty
|
# yielded nothing, hiding that read-system-env came back empty
|
||||||
["getenv entries destructure (non-empty)" "true"
|
["getenv entries destructure (non-empty)" "true"
|
||||||
|
|
|
||||||
|
|
@ -62,3 +62,22 @@
|
||||||
[".matches partial -> false" "false" "(.matches \"abcd\" \"a.c\")"]
|
[".matches partial -> false" "false" "(.matches \"abcd\" \"a.c\")"]
|
||||||
[".replaceAll regex" "\"a-b-c\"" "(.replaceAll \"a_b_c\" \"_\" \"-\")"]
|
[".replaceAll regex" "\"a-b-c\"" "(.replaceAll \"a_b_c\" \"_\" \"-\")"]
|
||||||
[".replaceFirst regex" "\"a-b_c\"" "(.replaceFirst \"a_b_c\" \"_\" \"-\")"])
|
[".replaceFirst regex" "\"a-b_c\"" "(.replaceFirst \"a_b_c\" \"_\" \"-\")"])
|
||||||
|
|
||||||
|
(defspec "regex / bounded quantifiers (jolt-3xur)"
|
||||||
|
["exact {n}" "\"aaa\"" "(re-matches #\"a{3}\" \"aaa\")"]
|
||||||
|
["range {n,m} max" "\"aaaa\"" "(re-find #\"a{2,4}\" \"aaaaa\")"]
|
||||||
|
["zero lower {0,n}" "\"aa\"" "(re-find #\"a{0,3}\" \"aa\")"]
|
||||||
|
["{n,} unbounded" "\"aaaa\"" "(re-find #\"a{2,}\" \"aaaa\")"]
|
||||||
|
# nested bounded quantifiers must not blow up the PEG compiler — this used to
|
||||||
|
# expand to ~2^61 nodes and hang at compile time.
|
||||||
|
["nested bounds compile" "true"
|
||||||
|
"(boolean (re-matches #\"[a-z](?:[a-z]{0,61}[a-z])?(?:-[a-z]{0,61}[a-z])*\" \"abc-def\"))"])
|
||||||
|
|
||||||
|
(defspec "regex / backreferences (jolt-xtss)"
|
||||||
|
["repeated char" "true" "(boolean (re-find #\"(.)\\1\" \"abba\"))"]
|
||||||
|
["no repeat = nil" "nil" "(re-find #\"(.)\\1\" \"abc\")"]
|
||||||
|
["thematic-break run" "true" "(boolean (re-matches #\"([-*_])\\1\\1\" \"---\"))"]
|
||||||
|
["mismatched run" "nil" "(re-matches #\"([-*_])\\1\\1\" \"-*_\")"]
|
||||||
|
["repeated word" "[\"the the\" \"the\"]" "(re-find #\"(\\w+) \\1\" \"say the the word\")"]
|
||||||
|
["group still captures" "[\"x=x\" \"x\"]" "(re-matches #\"(\\w+)=\\1\" \"x=x\")"]
|
||||||
|
["html tag pair" "true" "(boolean (re-matches #\"<(\\w+)>.*</\\1>\" \"<b>hi</b>\"))"])
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,22 @@
|
||||||
["pr-str of a defn" "\"#'user/gg\"" "(pr-str (defn gg [x] x))"]
|
["pr-str of a defn" "\"#'user/gg\"" "(pr-str (defn gg [x] x))"]
|
||||||
["seq of string" "[\\a \\b]" "(seq \"ab\")"])
|
["seq of string" "[\\a \\b]" "(seq \"ab\")"])
|
||||||
|
|
||||||
|
(defspec "string as a seqable of chars (jolt-dl4s)"
|
||||||
|
["vec of string" "[\\a \\b]" "(vec \"ab\")"]
|
||||||
|
["into [] of string" "[\\a \\b]" "(into [] \"ab\")"]
|
||||||
|
["set of string" "true" "(= #{\\a \\b} (set \"ab\"))"]
|
||||||
|
["into #{} of string" "true" "(= #{\\a \\b} (into #{} \"ab\"))"]
|
||||||
|
["set dedups chars" "2" "(count (set \"aab\"))"]
|
||||||
|
["mapv over string" "[\\a \\b]" "(mapv identity \"ab\")"])
|
||||||
|
|
||||||
|
(defspec "clojure.string / split limit (jolt-ik3a)"
|
||||||
|
["neg keeps trailing" "[\"a\" \"\" \"\"]" "(do (require (quote [clojure.string :as s])) (s/split \"a,,\" #\",\" -1))"]
|
||||||
|
["zero trims trailing" "[\"a\"]" "(do (require (quote [clojure.string :as s])) (s/split \"a,,\" #\",\" 0))"]
|
||||||
|
["omitted trims" "[\"a\"]" "(do (require (quote [clojure.string :as s])) (s/split \"a,,\" #\",\"))"]
|
||||||
|
["positive caps parts" "[\"a\" \"b,c\"]" "(do (require (quote [clojure.string :as s])) (s/split \"a,b,c\" #\",\" 2))"]
|
||||||
|
["empty string" "[\"\"]" "(do (require (quote [clojure.string :as s])) (s/split \"\" #\",\"))"]
|
||||||
|
["interior empties kept" "[\"a\" \"\" \"b\"]" "(do (require (quote [clojure.string :as s])) (s/split \"a,,b\" #\",\"))"])
|
||||||
|
|
||||||
(defspec "clojure.string"
|
(defspec "clojure.string"
|
||||||
["join" "\"a,b,c\"" "(do (require (quote [clojure.string :as s])) (s/join \",\" [\"a\" \"b\" \"c\"]))"]
|
["join" "\"a,b,c\"" "(do (require (quote [clojure.string :as s])) (s/join \",\" [\"a\" \"b\" \"c\"]))"]
|
||||||
["join no sep" "\"abc\"" "(do (require (quote [clojure.string :as s])) (s/join [\"a\" \"b\" \"c\"]))"]
|
["join no sep" "\"abc\"" "(do (require (quote [clojure.string :as s])) (s/join [\"a\" \"b\" \"c\"]))"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue