Implement ascii_downcase and ascii_upcase in C (#3689)
Both were defined as explode | map(...) | implode, which took 4 seconds on a 10 MB string, about 90 times as long as parsing it. Change the case of A-Z or a-z a byte at a time in C, copying multibyte UTF-8 sequences unchanged, so the output stays the same and the 10 MB case now takes about 60 ms including parsing. On a non-string input the error now names the function, "ascii_downcase input must be a string", instead of "explode input must be a string".
This commit is contained in:
1 parent
bd56a74562
commit
864d769d88
3 files changed
+50
-6
No files matched your search
@@ -1355,6 +1355,36 @@ static jv f_string_explode(jq_state *jq, jv a) {
|
|||||||
return jv_string_explode(a);
|
return jv_string_explode(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only the letters from..to change case. Bytes of multibyte UTF-8 sequences
|
||||||
|
// are all >= 0x80, so they are copied unchanged.
|
||||||
|
static void ascii_case_copy(char *dst, const char *src, int len, char from,
|
||||||
|
char to) {
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
char c = src[i];
|
||||||
|
dst[i] = (c >= from && c <= to) ? c ^ 0x20 : c; // flip case: 'A' <-> 'a'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static jv ascii_case(jv input, char from, char to, const char *errmsg) {
|
||||||
|
if (jv_get_kind(input) != JV_KIND_STRING)
|
||||||
|
return ret_error(input, jv_string(errmsg));
|
||||||
|
int len = jv_string_length_bytes(jv_copy(input));
|
||||||
|
char *result = jv_mem_alloc((size_t)len + 1);
|
||||||
|
ascii_case_copy(result, jv_string_value(input), len, from, to);
|
||||||
|
jv ret = jv_string_sized(result, len);
|
||||||
|
free(result);
|
||||||
|
jv_free(input);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static jv f_ascii_downcase(jq_state *jq, jv input) {
|
||||||
|
return ascii_case(input, 'A', 'Z', "ascii_downcase input must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
static jv f_ascii_upcase(jq_state *jq, jv input) {
|
||||||
|
return ascii_case(input, 'a', 'z', "ascii_upcase input must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
static jv f_string_indexes(jq_state *jq, jv a, jv b) {
|
static jv f_string_indexes(jq_state *jq, jv a, jv b) {
|
||||||
if (jv_get_kind(a) != JV_KIND_STRING) {
|
if (jv_get_kind(a) != JV_KIND_STRING) {
|
||||||
jv_free(b);
|
jv_free(b);
|
||||||
@@ -2015,6 +2045,8 @@ BINOPS
|
|||||||
CFUNC(f_string_split, "split", 2),
|
CFUNC(f_string_split, "split", 2),
|
||||||
CFUNC(f_string_explode, "explode", 1),
|
CFUNC(f_string_explode, "explode", 1),
|
||||||
CFUNC(f_string_implode, "implode", 1),
|
CFUNC(f_string_implode, "implode", 1),
|
||||||
|
CFUNC(f_ascii_downcase, "ascii_downcase", 1),
|
||||||
|
CFUNC(f_ascii_upcase, "ascii_upcase", 1),
|
||||||
CFUNC(f_string_indexes, "_strindices", 2),
|
CFUNC(f_string_indexes, "_strindices", 2),
|
||||||
CFUNC(f_string_trim, "trim", 1),
|
CFUNC(f_string_trim, "trim", 1),
|
||||||
CFUNC(f_string_ltrim, "ltrim", 1),
|
CFUNC(f_string_ltrim, "ltrim", 1),
|
||||||
|
|||||||
@@ -184,12 +184,6 @@ def repeat(exp):
|
|||||||
exp, _repeat;
|
exp, _repeat;
|
||||||
_repeat;
|
_repeat;
|
||||||
def inputs: try repeat(input) catch if .=="break" then empty else error end;
|
def inputs: try repeat(input) catch if .=="break" then empty else error end;
|
||||||
# like ruby's downcase - only characters A to Z are affected
|
|
||||||
def ascii_downcase:
|
|
||||||
explode | map( if 65 <= . and . <= 90 then . + 32 else . end) | implode;
|
|
||||||
# like ruby's upcase - only characters a to z are affected
|
|
||||||
def ascii_upcase:
|
|
||||||
explode | map( if 97 <= . and . <= 122 then . - 32 else . end) | implode;
|
|
||||||
|
|
||||||
# Streaming utilities
|
# Streaming utilities
|
||||||
def truncate_stream(stream):
|
def truncate_stream(stream):
|
||||||
|
|||||||
@@ -1888,6 +1888,24 @@ ascii_upcase
|
|||||||
"useful but not for é"
|
"useful but not for é"
|
||||||
"USEFUL BUT NOT FOR é"
|
"USEFUL BUT NOT FOR é"
|
||||||
|
|
||||||
|
# Only A-Z and a-z change; the neighbouring bytes @ [ ` { and non-ASCII do not
|
||||||
|
[ascii_downcase, ascii_upcase]
|
||||||
|
"@AZ[`az{ ÀÉé Dž 日本 😀 0123456789 THE QUICK brown fox JUMPS over"
|
||||||
|
["@az[`az{ ÀÉé Dž 日本 😀 0123456789 the quick brown fox jumps over","@AZ[`AZ{ ÀÉé Dž 日本 😀 0123456789 THE QUICK BROWN FOX JUMPS OVER"]
|
||||||
|
|
||||||
|
[ascii_downcase, ascii_upcase]
|
||||||
|
""
|
||||||
|
["",""]
|
||||||
|
|
||||||
|
# An embedded NUL does not end the string
|
||||||
|
[ascii_downcase, ascii_upcase]
|
||||||
|
"A\u0000z"
|
||||||
|
["a\u0000z","A\u0000Z"]
|
||||||
|
|
||||||
|
[.[] | try ascii_downcase catch ., try ascii_upcase catch .]
|
||||||
|
[null, ["A"]]
|
||||||
|
["ascii_downcase input must be a string","ascii_upcase input must be a string","ascii_downcase input must be a string","ascii_upcase input must be a string"]
|
||||||
|
|
||||||
bsearch(0,1,2,3,4)
|
bsearch(0,1,2,3,4)
|
||||||
[1,2,3]
|
[1,2,3]
|
||||||
-1
|
-1
|
||||||
|
|||||||
Reference in new issue
Block a user