1
0
mirror of https://github.com/janet-lang/janet synced 2024-11-28 11:09:54 +00:00

Merge pull request #1112 from ianthehenry/dynamic-replace

string and peg replacement functions can now take functions
This commit is contained in:
Calvin Rose 2023-04-24 09:07:03 -05:00 committed by GitHub
commit 83cd519702
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 114 additions and 16 deletions

View File

@ -1637,7 +1637,7 @@ typedef struct {
JanetPeg *peg; JanetPeg *peg;
PegState s; PegState s;
JanetByteView bytes; JanetByteView bytes;
JanetByteView repl; Janet subst;
int32_t start; int32_t start;
} PegCall; } PegCall;
@ -1653,7 +1653,7 @@ static PegCall peg_cfun_init(int32_t argc, Janet *argv, int get_replace) {
ret.peg = compile_peg(argv[0]); ret.peg = compile_peg(argv[0]);
} }
if (get_replace) { if (get_replace) {
ret.repl = janet_getbytes(argv, 1); ret.subst = argv[1];
ret.bytes = janet_getbytes(argv, 2); ret.bytes = janet_getbytes(argv, 2);
} else { } else {
ret.bytes = janet_getbytes(argv, 1); ret.bytes = janet_getbytes(argv, 1);
@ -1738,7 +1738,8 @@ static Janet cfun_peg_replace_generic(int32_t argc, Janet *argv, int only_one) {
trail = i; trail = i;
} }
int32_t nexti = (int32_t)(result - c.bytes.bytes); int32_t nexti = (int32_t)(result - c.bytes.bytes);
janet_buffer_push_bytes(ret, c.repl.bytes, c.repl.len); JanetByteView subst = janet_text_substitution(&c.subst, c.bytes.bytes + i, nexti - i, c.s.captures);
janet_buffer_push_bytes(ret, subst.bytes, subst.len);
trail = nexti; trail = nexti;
if (nexti == i) nexti++; if (nexti == i) nexti++;
i = nexti; i = nexti;
@ -1754,14 +1755,20 @@ static Janet cfun_peg_replace_generic(int32_t argc, Janet *argv, int only_one) {
} }
JANET_CORE_FN(cfun_peg_replace_all, JANET_CORE_FN(cfun_peg_replace_all,
"(peg/replace-all peg repl text &opt start & args)", "(peg/replace-all peg subst text &opt start & args)",
"Replace all matches of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement.") { "Replace all matches of `peg` in `text` with `subst`, returning a new buffer. "
"The peg does not need to make captures to do replacement. "
"If `subst` is a function, it will be called with the "
"matching text followed by any captures.") {
return cfun_peg_replace_generic(argc, argv, 0); return cfun_peg_replace_generic(argc, argv, 0);
} }
JANET_CORE_FN(cfun_peg_replace, JANET_CORE_FN(cfun_peg_replace,
"(peg/replace peg repl text &opt start & args)", "(peg/replace peg repl text &opt start & args)",
"Replace first match of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement. " "Replace first match of `peg` in `text` with `subst`, returning a new buffer. "
"The peg does not need to make captures to do replacement. "
"If `subst` is a function, it will be called with the "
"matching text followed by any captures. "
"If no matches are found, returns the input string in a new buffer.") { "If no matches are found, returns the input string in a new buffer.") {
return cfun_peg_replace_generic(argc, argv, 1); return cfun_peg_replace_generic(argc, argv, 1);
} }

View File

@ -364,14 +364,13 @@ JANET_CORE_FN(cfun_string_findall,
struct replace_state { struct replace_state {
struct kmp_state kmp; struct kmp_state kmp;
const uint8_t *subst; Janet subst;
int32_t substlen;
}; };
static void replacesetup(int32_t argc, Janet *argv, struct replace_state *s) { static void replacesetup(int32_t argc, Janet *argv, struct replace_state *s) {
janet_arity(argc, 3, 4); janet_arity(argc, 3, 4);
JanetByteView pat = janet_getbytes(argv, 0); JanetByteView pat = janet_getbytes(argv, 0);
JanetByteView subst = janet_getbytes(argv, 1); Janet subst = argv[1];
JanetByteView text = janet_getbytes(argv, 2); JanetByteView text = janet_getbytes(argv, 2);
int32_t start = 0; int32_t start = 0;
if (argc == 4) { if (argc == 4) {
@ -380,13 +379,14 @@ static void replacesetup(int32_t argc, Janet *argv, struct replace_state *s) {
} }
kmp_init(&s->kmp, text.bytes, text.len, pat.bytes, pat.len); kmp_init(&s->kmp, text.bytes, text.len, pat.bytes, pat.len);
s->kmp.i = start; s->kmp.i = start;
s->subst = subst.bytes; s->subst = subst;
s->substlen = subst.len;
} }
JANET_CORE_FN(cfun_string_replace, JANET_CORE_FN(cfun_string_replace,
"(string/replace patt subst str)", "(string/replace patt subst str)",
"Replace the first occurrence of `patt` with `subst` in the string `str`. " "Replace the first occurrence of `patt` with `subst` in the string `str`. "
"If `subst` is a function, it will be called with `patt` only if a match is found, "
"and should return the actual replacement text to use. "
"Will return the new string if `patt` is found, otherwise returns `str`.") { "Will return the new string if `patt` is found, otherwise returns `str`.") {
int32_t result; int32_t result;
struct replace_state s; struct replace_state s;
@ -397,10 +397,11 @@ JANET_CORE_FN(cfun_string_replace,
kmp_deinit(&s.kmp); kmp_deinit(&s.kmp);
return janet_stringv(s.kmp.text, s.kmp.textlen); return janet_stringv(s.kmp.text, s.kmp.textlen);
} }
buf = janet_string_begin(s.kmp.textlen - s.kmp.patlen + s.substlen); JanetByteView subst = janet_text_substitution(&s.subst, s.kmp.text + result, s.kmp.patlen, NULL);
buf = janet_string_begin(s.kmp.textlen - s.kmp.patlen + subst.len);
safe_memcpy(buf, s.kmp.text, result); safe_memcpy(buf, s.kmp.text, result);
safe_memcpy(buf + result, s.subst, s.substlen); safe_memcpy(buf + result, subst.bytes, subst.len);
safe_memcpy(buf + result + s.substlen, safe_memcpy(buf + result + subst.len,
s.kmp.text + result + s.kmp.patlen, s.kmp.text + result + s.kmp.patlen,
s.kmp.textlen - result - s.kmp.patlen); s.kmp.textlen - result - s.kmp.patlen);
kmp_deinit(&s.kmp); kmp_deinit(&s.kmp);
@ -411,6 +412,8 @@ JANET_CORE_FN(cfun_string_replaceall,
"(string/replace-all patt subst str)", "(string/replace-all patt subst str)",
"Replace all instances of `patt` with `subst` in the string `str`. Overlapping " "Replace all instances of `patt` with `subst` in the string `str`. Overlapping "
"matches will not be counted, only the first match in such a span will be replaced. " "matches will not be counted, only the first match in such a span will be replaced. "
"If `subst` is a function, it will be called with `patt` once for each match, "
"and should return the actual replacement text to use. "
"Will return the new string if `patt` is found, otherwise returns `str`.") { "Will return the new string if `patt` is found, otherwise returns `str`.") {
int32_t result; int32_t result;
struct replace_state s; struct replace_state s;
@ -419,8 +422,9 @@ JANET_CORE_FN(cfun_string_replaceall,
replacesetup(argc, argv, &s); replacesetup(argc, argv, &s);
janet_buffer_init(&b, s.kmp.textlen); janet_buffer_init(&b, s.kmp.textlen);
while ((result = kmp_next(&s.kmp)) >= 0) { while ((result = kmp_next(&s.kmp)) >= 0) {
JanetByteView subst = janet_text_substitution(&s.subst, s.kmp.text + result, s.kmp.patlen, NULL);
janet_buffer_push_bytes(&b, s.kmp.text + lastindex, result - lastindex); janet_buffer_push_bytes(&b, s.kmp.text + lastindex, result - lastindex);
janet_buffer_push_bytes(&b, s.subst, s.substlen); janet_buffer_push_bytes(&b, subst.bytes, subst.len);
lastindex = result + s.kmp.patlen; lastindex = result + s.kmp.patlen;
kmp_seti(&s.kmp, lastindex); kmp_seti(&s.kmp, lastindex);
} }

View File

@ -663,6 +663,59 @@ JanetBinding janet_binding_from_entry(Janet entry) {
return binding; return binding;
} }
/* If the value at the given address can be coerced to a byte view,
return that byte view. If it can't, replace the value at the address
with the result of janet_to_string, and return a byte view over that
string. */
static JanetByteView memoize_byte_view(Janet *value) {
JanetByteView result;
if (!janet_bytes_view(*value, &result.bytes, &result.len)) {
JanetString str = janet_to_string(*value);
*value = janet_wrap_string(str);
result.bytes = str;
result.len = janet_string_length(str);
}
return result;
}
static JanetByteView to_byte_view(Janet value) {
JanetByteView result;
if (!janet_bytes_view(value, &result.bytes, &result.len)) {
JanetString str = janet_to_string(value);
result.bytes = str;
result.len = janet_string_length(str);
}
return result;
}
JanetByteView janet_text_substitution(
Janet *subst,
const uint8_t *bytes,
uint32_t len,
JanetArray *extra_argv) {
int32_t extra_argc = extra_argv == NULL ? 0 : extra_argv->count;
JanetType type = janet_type(*subst);
switch (type) {
case JANET_FUNCTION:
case JANET_CFUNCTION: {
int32_t argc = 1 + extra_argc;
Janet *argv = janet_tuple_begin(argc);
argv[0] = janet_stringv(bytes, len);
for (int32_t i = 0; i < extra_argc; i++) {
argv[i + 1] = extra_argv->data[i];
}
janet_tuple_end(argv);
if (type == JANET_FUNCTION) {
return to_byte_view(janet_call(janet_unwrap_function(*subst), argc, argv));
} else {
return to_byte_view(janet_unwrap_cfunction(*subst)(argc, argv));
}
}
default:
return memoize_byte_view(subst);
}
}
JanetBinding janet_resolve_ext(JanetTable *env, const uint8_t *sym) { JanetBinding janet_resolve_ext(JanetTable *env, const uint8_t *sym) {
Janet entry = janet_table_get(env, janet_wrap_symbol(sym)); Janet entry = janet_table_get(env, janet_wrap_symbol(sym));
return janet_binding_from_entry(entry); return janet_binding_from_entry(entry);

View File

@ -93,6 +93,11 @@ void janet_buffer_format(
Janet *argv); Janet *argv);
Janet janet_next_impl(Janet ds, Janet key, int is_interpreter); Janet janet_next_impl(Janet ds, Janet key, int is_interpreter);
JanetBinding janet_binding_from_entry(Janet entry); JanetBinding janet_binding_from_entry(Janet entry);
JanetByteView janet_text_substitution(
Janet *subst,
const uint8_t *bytes,
uint32_t len,
JanetArray *extra_args);
/* Registry functions */ /* Registry functions */
void janet_registry_put( void janet_registry_put(

View File

@ -72,6 +72,10 @@
(assert (= (string/replace "X" "." "XXX...XXX...XXX") ".XX...XXX...XXX") "string/replace 1") (assert (= (string/replace "X" "." "XXX...XXX...XXX") ".XX...XXX...XXX") "string/replace 1")
(assert (= (string/replace-all "X" "." "XXX...XXX...XXX") "...............") "string/replace-all 1") (assert (= (string/replace-all "X" "." "XXX...XXX...XXX") "...............") "string/replace-all 1")
(assert (= (string/replace-all "XX" "." "XXX...XXX...XXX") ".X....X....X") "string/replace-all 2") (assert (= (string/replace-all "XX" "." "XXX...XXX...XXX") ".X....X....X") "string/replace-all 2")
(assert (= (string/replace "xx" string/ascii-upper "xxyxyxyxxxy") "XXyxyxyxxxy") "string/replace function")
(assert (= (string/replace-all "xx" string/ascii-upper "xxyxyxyxxxy") "XXyxyxyXXxy") "string/replace-all function")
(assert (= (string/replace "x" 12 "xyx") "12yx") "string/replace stringable")
(assert (= (string/replace-all "x" 12 "xyx") "12y12") "string/replace-all stringable")
(assert (= (string/ascii-lower "ABCabc&^%!@:;.") "abcabc&^%!@:;.") "string/ascii-lower") (assert (= (string/ascii-lower "ABCabc&^%!@:;.") "abcabc&^%!@:;.") "string/ascii-lower")
(assert (= (string/ascii-upper "ABCabc&^%!@:;.") "ABCABC&^%!@:;.") "string/ascii-lower") (assert (= (string/ascii-upper "ABCabc&^%!@:;.") "ABCABC&^%!@:;.") "string/ascii-lower")
(assert (= (string/reverse "") "") "string/reverse 1") (assert (= (string/reverse "") "") "string/reverse 1")

View File

@ -330,7 +330,6 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(assert (deep= (peg/find-all '"/" p) @[0 4 10 14]) "peg find-all") (assert (deep= (peg/find-all '"/" p) @[0 4 10 14]) "peg find-all")
# Peg replace and replace-all # Peg replace and replace-all
(var ti 0)
(defn check-replacer (defn check-replacer
[x y z] [x y z]
(assert (= (string/replace x y z) (string (peg/replace x y z))) "replacer test replace") (assert (= (string/replace x y z) (string (peg/replace x y z))) "replacer test replace")
@ -339,6 +338,32 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(check-replacer "abc" "Z" "") (check-replacer "abc" "Z" "")
(check-replacer "aba" "ZZZZZZ" "ababababababa") (check-replacer "aba" "ZZZZZZ" "ababababababa")
(check-replacer "aba" "" "ababababababa") (check-replacer "aba" "" "ababababababa")
(check-replacer "aba" string/ascii-upper "ababababababa")
(check-replacer "aba" 123 "ababababababa")
(assert (= (string (peg/replace-all ~(set "ab") string/ascii-upper "abcaa"))
"ABcAA")
"peg/replace-all cfunction")
(assert (= (string (peg/replace-all ~(set "ab") |$ "abcaa"))
"abcaa")
"peg/replace-all function")
(defn peg-test [name f peg subst text expected]
(assert (= (string (f peg subst text)) expected) name))
(peg-test "peg/replace has access to captures"
peg/replace
~(sequence "." (capture (set "ab")))
(fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char)))
".a.b.c"
".a -> A, .b.c")
(peg-test "peg/replace-all has access to captures"
peg/replace-all
~(sequence "." (capture (set "ab")))
(fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char)))
".a.b.c"
".a -> A, .b -> B, .c")
# Peg bug # Peg bug
(assert (deep= @[] (peg/match '(any 1) @"")) "peg empty pattern 1") (assert (deep= @[] (peg/match '(any 1) @"")) "peg empty pattern 1")