/* * libslack - https://libslack.org * * Copyright (C) 1999-2004, 2010, 2020-2023 raf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * 20230824 raf */ /* * $OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $ * $OpenBSD: strlcat.c,v 1.5 2001/01/13 16:17:24 millert Exp $ * Modified by raf * * Copyright (c) 1998 Todd C. Miller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* =head1 NAME I - string module =head1 SYNOPSIS #include #include typedef struct String String; typedef struct StringTR StringTR; enum StringAlignment { ALIGN_LEFT = '<', ALIGN_RIGHT = '>', ALIGN_CENTRE = '|', ALIGN_CENTER = '|', ALIGN_FULL = '=' }; enum StringTROption { TR_COMPLEMENT = 1, TR_DELETE = 2, TR_SQUASH = 4 }; typedef enum StringAlignment StringAlignment; typedef enum StringTROption StringTROption; String *str_create(const char *format, ...); String *str_create_with_locker(Locker *locker, const char *format, ...); String *str_vcreate(const char *format, va_list args); String *str_vcreate_with_locker(Locker *locker, const char *format, va_list args); String *str_create_sized(size_t size, const char *format, ...); String *str_create_with_locker_sized(Locker *locker, size_t size, const char *format, ...); String *str_vcreate_sized(size_t size, const char *format, va_list args); String *str_vcreate_with_locker_sized(Locker *locker, size_t size, const char *format, va_list args); String *str_copy(const String *str); String *str_copy_unlocked(const String *str); String *str_copy_with_locker(Locker *locker, const String *str); String *str_copy_with_locker_unlocked(Locker *locker, const String *str); String *str_fgetline(FILE *stream); String *str_fgetline_with_locker(Locker *locker, FILE *stream); void str_release(String *str); void *str_destroy(String **str); int str_rdlock(const String *str); int str_wrlock(const String *str); int str_unlock(const String *str); int str_empty(const String *str); int str_empty_unlocked(const String *str); ssize_t str_length(const String *str); ssize_t str_length_unlocked(const String *str); char *cstr(const String *str); ssize_t str_set_length(String *str, size_t length); ssize_t str_set_length_unlocked(String *str, size_t length); ssize_t str_recalc_length(String *str); ssize_t str_recalc_length_unlocked(String *str); String *str_clear(String *str); String *str_clear_unlocked(String *str); String *str_remove(String *str, ssize_t index); String *str_remove_unlocked(String *str, ssize_t index); String *str_remove_range(String *str, ssize_t index, ssize_t range); String *str_remove_range_unlocked(String *str, ssize_t index, ssize_t range); String *str_insert(String *str, ssize_t index, const char *format, ...); String *str_insert_unlocked(String *str, ssize_t index, const char *format, ...); String *str_vinsert(String *str, ssize_t index, const char *format, va_list args); String *str_vinsert_unlocked(String *str, ssize_t index, const char *format, va_list args); String *str_insert_str(String *str, ssize_t index, const String *src); String *str_insert_str_unlocked(String *str, ssize_t index, const String *src); String *str_append(String *str, const char *format, ...); String *str_append_unlocked(String *str, const char *format, ...); String *str_vappend(String *str, const char *format, va_list args); String *str_vappend_unlocked(String *str, const char *format, va_list args); String *str_append_str(String *str, const String *src); String *str_append_str_unlocked(String *str, const String *src); String *str_prepend(String *str, const char *format, ...); String *str_prepend_unlocked(String *str, const char *format, ...); String *str_vprepend(String *str, const char *format, va_list args); String *str_vprepend_unlocked(String *str, const char *format, va_list args); String *str_prepend_str(String *str, const String *src); String *str_prepend_str_unlocked(String *str, const String *src); String *str_replace(String *str, ssize_t index, ssize_t range, const char *format, ...); String *str_replace_unlocked(String *str, ssize_t index, ssize_t range, const char *format, ...); String *str_vreplace(String *str, ssize_t index, ssize_t range, const char *format, va_list args); String *str_vreplace_unlocked(String *str, ssize_t index, ssize_t range, const char *format, va_list args); String *str_replace_str(String *str, ssize_t index, ssize_t range, const String *src); String *str_replace_str_unlocked(String *str, ssize_t index, ssize_t range, const String *src); String *str_substr(const String *str, ssize_t index, ssize_t range); String *str_substr_unlocked(const String *str, ssize_t index, ssize_t range); String *str_substr_with_locker(Locker *locker, const String *str, ssize_t index, ssize_t range); String *str_substr_with_locker_unlocked(Locker *locker, const String *str, ssize_t index, ssize_t range); String *substr(const char *str, ssize_t index, ssize_t range); String *substr_with_locker(Locker *locker, const char *str, ssize_t index, ssize_t range); String *str_splice(String *str, ssize_t index, ssize_t range); String *str_splice_unlocked(String *str, ssize_t index, ssize_t range); String *str_splice_with_locker(Locker *locker, String *str, ssize_t index, ssize_t range); String *str_splice_with_locker_unlocked(Locker *locker, String *str, ssize_t index, ssize_t range); String *str_repeat(size_t count, const char *format, ...); String *str_repeat_with_locker(Locker *locker, size_t count, const char *format, ...); String *str_vrepeat(size_t count, const char *format, va_list args); String *str_vrepeat_with_locker(Locker *locker, size_t count, const char *format, va_list args); int str_tr(String *str, const char *from, const char *to, int option); int str_tr_unlocked(String *str, const char *from, const char *to, int option); int str_tr_str(String *str, const String *from, const String *to, int option); int str_tr_str_unlocked(String *str, const String *from, const String *to, int option); int tr(char *str, const char *from, const char *to, int option); StringTR *tr_compile(const char *from, const char *to, int option); StringTR *tr_compile_with_locker(Locker *locker, const char *from, const char *to, int option); StringTR *str_tr_compile(const String *from, const String *to, int option); StringTR *str_tr_compile_unlocked(const String *from, const String *to, int option); StringTR *str_tr_compile_with_locker(Locker *locker, const String *from, const String *to, int option); StringTR *str_tr_compile_with_locker_unlocked(Locker *locker, const String *from, const String *to, int option); void tr_release(StringTR *table); void *tr_destroy(StringTR **table); int str_tr_compiled(String *str, StringTR *table); int str_tr_compiled_unlocked(String *str, StringTR *table); int tr_compiled(char *str, StringTR *table); List *str_regexpr(const char *pattern, const String *text, int cflags, int eflags); List *str_regexpr_unlocked(const char *pattern, const String *text, int cflags, int eflags); List *str_regexpr_with_locker(Locker *locker, const char *pattern, const String *text, int cflags, int eflags); List *str_regexpr_with_locker_unlocked(Locker *locker, const char *pattern, const String *text, int cflags, int eflags); List *regexpr(const char *pattern, const char *text, int cflags, int eflags); List *regexpr_with_locker(Locker *locker, const char *pattern, const char *text, int cflags, int eflags); int regexpr_compile(regex_t *compiled, const char *pattern, int cflags); void regexpr_release(regex_t *compiled); List *str_regexpr_compiled(const regex_t *compiled, const String *text, int eflags); List *str_regexpr_compiled_unlocked(const regex_t *compiled, const String *text, int eflags); List *str_regexpr_compiled_with_locker(Locker *locker, const regex_t *compiled, const String *text, int eflags); List *str_regexpr_compiled_with_locker_unlocked(Locker *locker, const regex_t *compiled, const String *text, int eflags); List *regexpr_compiled(const regex_t *compiled, const char *text, int eflags); List *regexpr_compiled_with_locker(Locker *locker, const regex_t *compiled, const char *text, int eflags); String *str_regsub(const char *pattern, const char *replacement, String *text, int cflags, int eflags, int all); String *str_regsub_unlocked(const char *pattern, const char *replacement, String *text, int cflags, int eflags, int all); String *str_regsub_compiled(const regex_t *compiled, const char *replacement, String *text, int eflags, int all); String *str_regsub_compiled_unlocked(const regex_t *compiled, const char *replacement, String *text, int eflags, int all); List *str_fmt(const String *str, size_t line_width, StringAlignment alignment); List *str_fmt_unlocked(const String *str, size_t line_width, StringAlignment alignment); List *str_fmt_with_locker(Locker *locker, const String *str, size_t line_width, StringAlignment alignment); List *str_fmt_with_locker_unlocked(Locker *locker, const String *str, size_t line_width, StringAlignment alignment); List *fmt(const char *str, size_t line_width, StringAlignment alignment); List *fmt_with_locker(Locker *locker, const char *str, size_t line_width, StringAlignment alignment); List *str_split(const String *str, const char *delim); List *str_split_unlocked(const String *str, const char *delim); List *str_split_with_locker(Locker *locker, const String *str, const char *delim); List *str_split_with_locker_unlocked(Locker *locker, const String *str, const char *delim); List *split(const char *str, const char *delim); List *split_with_locker(Locker *locker, const char *str, const char *delim); List *str_regexpr_split(const String *str, const char *delim, int cflags, int eflags); List *str_regexpr_split_unlocked(const String *str, const char *delim, int cflags, int eflags); List *str_regexpr_split_with_locker(Locker *locker, const String *str, const char *delim, int cflags, int eflags); List *str_regexpr_split_with_locker_unlocked(Locker *locker, const String *str, const char *delim, int cflags, int eflags); List *regexpr_split(const char *str, const char *delim, int cflags, int eflags); List *regexpr_split_with_locker(Locker *locker, const char *str, const char *delim, int cflags, int eflags); String *str_join(const List *list, const char *delim); String *str_join_unlocked(const List *list, const char *delim); String *str_join_with_locker(Locker *locker, const List *list, const char *delim); String *str_join_with_locker_unlocked(Locker *locker, const List *list, const char *delim); String *join(const List *list, const char *delim); String *join_with_locker(Locker *locker, const List *list, const char *delim); int str_soundex(const String *str); int str_soundex_unlocked(const String *str); int soundex(const char *str); String *str_trim(String *str); String *str_trim_unlocked(String *str); char *trim(char *str); String *str_trim_left(String *str); String *str_trim_left_unlocked(String *str); char *trim_left(char *str); String *str_trim_right(String *str); String *str_trim_right_unlocked(String *str); char *trim_right(char *str); String *str_squeeze(String *str); String *str_squeeze_unlocked(String *str); char *squeeze(char *str); String *str_quote(const String *str, const char *quotable, char quote_char); String *str_quote_unlocked(const String *str, const char *quotable, char quote_char); String *str_quote_with_locker(Locker *locker, const String *str, const char *quotable, char quote_char); String *str_quote_with_locker_unlocked(Locker *locker, const String *str, const char *quotable, char quote_char); String *quote(const char *str, const char *quotable, char quote_char); String *quote_with_locker(Locker *locker, const char *str, const char *quotable, char quote_char); String *str_unquote(const String *str, const char *quotable, char quote_char); String *str_unquote_unlocked(const String *str, const char *quotable, char quote_char); String *str_unquote_with_locker(Locker *locker, const String *str, const char *quotable, char quote_char); String *str_unquote_with_locker_unlocked(Locker *locker, const String *str, const char *quotable, char quote_char); String *unquote(const char *str, const char *quotable, char quote_char); String *unquote_with_locker(Locker *locker, const char *str, const char *quotable, char quote_char); String *str_encode(const String *str, const char *uncoded, const char *coded, char quote_char, int printable); String *str_encode_unlocked(const String *str, const char *uncoded, const char *coded, char quote_char, int printable); String *str_encode_with_locker(Locker *locker, const String *str, const char *uncoded, const char *coded, char quote_char, int printable); String *str_encode_with_locker_unlocked(Locker *locker, const String *str, const char *uncoded, const char *coded, char quote_char, int printable); String *str_decode(const String *str, const char *uncoded, const char *coded, char quote_char, int printable); String *str_decode_unlocked(const String *str, const char *uncoded, const char *coded, char quote_char, int printable); String *str_decode_with_locker(Locker *locker, const String *str, const char *uncoded, const char *coded, char quote_char, int printable); String *str_decode_with_locker_unlocked(Locker *locker, const String *str, const char *uncoded, const char *coded, char quote_char, int printable); String *encode(const char *str, const char *uncoded, const char *coded, char quote_char, int printable); String *encode_with_locker(Locker *locker, const char *str, const char *uncoded, const char *coded, char quote_char, int printable); String *decode(const char *str, const char *uncoded, const char *coded, char quote_char, int printable); String *decode_with_locker(Locker *locker, const char *str, const char *uncoded, const char *coded, char quote_char, int printable); String *str_lc(String *str); String *str_lc_unlocked(String *str); char *lc(char *str); String *str_lcfirst(String *str); String *str_lcfirst_unlocked(String *str); char *lcfirst(char *str); String *str_uc(String *str); String *str_uc_unlocked(String *str); char *uc(char *str); String *str_ucfirst(String *str); String *str_ucfirst_unlocked(String *str); char *ucfirst(char *str); int str_chop(String *str); int str_chop_unlocked(String *str); int chop(char *str); int str_chomp(String *str); int str_chomp_unlocked(String *str); int chomp(char *str); int str_bin(const String *str); int str_bin_unlocked(const String *str); int bin(const char *str); int str_hex(const String *str); int str_hex_unlocked(const String *str); int hex(const char *str); int str_oct(const String *str); int str_oct_unlocked(const String *str); int oct(const char *str); int strcasecmp(const char *s1, const char *s2); int strncasecmp(const char *s1, const char *s2, size_t n); size_t strlcpy(char *dst, const char *src, size_t size); size_t strlcat(char *dst, const char *src, size_t size); char *cstrcpy(char *dst, const char *src); char *cstrcat(char *dst, const char *src); char *cstrchr(const char *str, int c); char *cstrpbrk(const char *str, const char *brk); char *cstrrchr(const char *str, int c); char *cstrstr(const char *str, const char *srch); int asprintf(char **str, const char *format, ...); int vasprintf(char **str, const char *format, va_list args); =head1 DESCRIPTION This module provides text strings that grow and shrink automatically, and functions for manipulating them. Some of the functions were modelled on the I module. Others were modelled on the string functions and operators in I and I. Others came from I. =over 4 =cut */ #ifndef _BSD_SOURCE #define _BSD_SOURCE /* For snprintf() on OpenBSD-4.7 */ #endif #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE /* New name for _BSD_SOURCE */ #endif #ifndef __BSD_VISIBLE #define __BSD_VISIBLE 1 /* For ntohl() on FreeBSD-8.0 */ #endif #ifndef _NETBSD_SOURCE #define _NETBSD_SOURCE /* For ntohl() on NetBSD-5.0.2 */ #endif #include "config.h" #include "std.h" #include #include "err.h" #include "str.h" #include "mem.h" #include "fio.h" #ifndef HAVE_SNPRINTF #include "snprintf.h" #endif struct String { size_t size; /* number of bytes allocated */ size_t length; /* number of bytes used (including nul) */ char *str; /* vector of characters */ Locker *locker; /* locking strategy for this string */ }; #define CHARSET 256 struct StringTR { int squash; /* whether or not to squash duplicate characters */ short table[CHARSET]; /* the translation table */ Locker *locker; /* locking strategy for this structure */ }; typedef enum { TRCODE_NOMAP = -1, TRCODE_DELETE = -2 } TRCode; #define is_alpha(c) isalpha((int)(unsigned char)(c)) #define is_alnum(c) isalnum((int)(unsigned char)(c)) #define is_print(c) isprint((int)(unsigned char)(c)) #define is_space(c) isspace((int)(unsigned char)(c)) #define is_digit(c) isdigit((int)(unsigned char)(c)) #define is_xdigit(c) isxdigit((int)(unsigned char)(c)) #define to_lower(c) tolower((int)(unsigned char)(c)) #define to_upper(c) toupper((int)(unsigned char)(c)) #ifndef TEST /* Minimum string length: must be a power of 2 */ static const size_t MIN_STRING_SIZE = 32; /* Maximum bytes for an empty string: must be a power of 2 greater than MIN_STRING_SIZE */ static const size_t MIN_EMPTY_STRING_SIZE = 1024; void (flockfile)(FILE *stream); /* Missing from old glibc headers */ void (funlockfile)(FILE *stream); #ifndef HAVE_FLOCKFILE #define flockfile(stream) #define funlockfile(stream) #define getc_unlocked(stream) getc(stream) #endif /* C Allocates enough memory to add C extra bytes to C if necessary. On success, returns C<0>. On error, returns C<-1>. */ static int grow(String *str, size_t bytes) { int grown = 0; while (str->length + bytes > str->size) { if (str->size) str->size <<= 1; else str->size = MIN_STRING_SIZE; grown = 1; } if (grown) return mem_resize(&str->str, str->size) ? 0 : -1; return 0; } /* C Allocates less memory for removing C bytes from C if necessary. On success, returns C<0>. On error, returns C<-1>. */ static int shrink(String *str, size_t bytes) { int shrunk = 0; while (str->length - bytes < str->size >> 1) { if (str->size <= MIN_EMPTY_STRING_SIZE) break; str->size >>= 1; shrunk = 1; } if (shrunk) return mem_resize(&str->str, str->size) ? 0 : -1; return 0; } /* C Slides C's bytes, starting at C, C positions to the right to make room for more. On success, returns C<0>. On error, returns C<-1>. */ static int expand(String *str, ssize_t index, size_t range) { if (grow(str, range) == -1) return -1; memmove(str->str + index + range, str->str + index, (str->length - index) * sizeof(*str->str)); str->length += range; return 0; } /* C Slides C's bytes, starting at C + C, C positions to the left to close a gap starting at C. On success, returns C<0>. On error, returns C<-1>. */ static int contract(String *str, ssize_t index, size_t range) { memmove(str->str + index, str->str + index + range, (str->length - index - range) * sizeof(*str->str)); if (shrink(str, range) == -1) return -1; str->length -= range; return 0; } /* C Expands or contracts C as required so that C occupies C. On success, returns C<0>. On error, returns C<-1>. */ static int adjust(String *str, ssize_t index, size_t range, size_t length) { if (range < length) return expand(str, index + range, length - range); if (range > length) return contract(str, index + length, range - length); return 0; } /* =item C Creates a I specified by C and the following arguments as in I. On success, returns the new string. It is the caller's responsibility to deallocate the new string with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On error, returns C with C set appropriately. B String *str = str_create(buf); // EVIL String *str = str_create("%s", buf); // GOOD =cut */ String *str_create(const char *format, ...) { String *str; va_list args; va_start(args, format); str = str_vcreate_with_locker_sized(NULL, MIN_STRING_SIZE, format, args); va_end(args); return str; } /* =item C Equivalent to I except that multiple threads accessing the new string will be synchronized by C. =cut */ String *str_create_with_locker(Locker *locker, const char *format, ...) { String *str; va_list args; va_start(args, format); str = str_vcreate_with_locker_sized(locker, MIN_STRING_SIZE, format, args); va_end(args); return str; } /* =item C Equivalent to I with the variable argument list specified directly as for I. =cut */ String *str_vcreate(const char *format, va_list args) { return str_vcreate_with_locker_sized(NULL, MIN_STRING_SIZE, format, args); } /* =item C Equivalent to I except that multiple threads accessing the new string will be synchronized by C. =cut */ String *str_vcreate_with_locker(Locker *locker, const char *format, va_list args) { return str_vcreate_with_locker_sized(locker, MIN_STRING_SIZE, format, args); } /* =item C Creates a I specified by C and the following arguments as in I. The initial allocation for the string data is at least C bytes. On success, returns the new string. It is the caller's responsibility to deallocate the new string with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On error, returns C with C set appropriately. =cut */ String *str_create_sized(size_t size, const char *format, ...) { String *str; va_list args; va_start(args, format); str = str_vcreate_with_locker_sized(NULL, size, format, args); va_end(args); return str; } /* =item C Equivalent to I except that multiple threads accessing the new string will be synchronised by C. =cut */ String *str_create_with_locker_sized(Locker *locker, size_t size, const char *format, ...) { String *str; va_list args; va_start(args, format); str = str_vcreate_with_locker_sized(locker, size, format, args); va_end(args); return str; } /* =item C Equivalent to I with the variable argument list specified directly as for I. =cut */ String *str_vcreate_sized(size_t size, const char *format, va_list args) { return str_vcreate_with_locker_sized(NULL, size, format, args); } /* =item C Equivalent to I except that multiple threads accessing the new string will be synchronised by C. =cut */ #ifndef va_copy #define va_copy(dst, src) __va_copy((dst), (src)) #endif String *str_vcreate_with_locker_sized(Locker *locker, size_t size, const char *format, va_list args) { String *str; char *buf = NULL; ssize_t length; unsigned int bit; va_list args_copy; for (bit = 1; bit; bit <<= 1) { if (bit >= size) { size = bit; break; } } if (!bit) return set_errnull(EINVAL); if (!format) format = ""; for (;; size <<= 1) { if (!mem_resize(&buf, size)) { mem_release(buf); return NULL; } #ifdef va_copy va_copy(args_copy, args); length = vsnprintf(buf, size, format, args_copy); va_end(args_copy); #else length = vsnprintf(buf, size, format, args); #endif if (length != -1 && length < size) break; } if (!(str = mem_new(String))) /* XXX decouple */ { mem_release(buf); return NULL; } str->size = size; str->length = length + 1; str->str = buf; str->locker = locker; return str; } /* =item C Creates a copy of C. On success, returns the copy. It is the caller's responsibility to deallocate the new string with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On error, returns C with C set appropriately. =cut */ String *str_copy(const String *str) { return str_copy_with_locker(NULL, str); } /* =item C Equivalent to I except that C is not read-locked. =cut */ String *str_copy_unlocked(const String *str) { return str_copy_with_locker_unlocked(NULL, str); } /* =item C Equivalent to I except that multiple threads accessing the new string will be synchronised by C. =cut */ String *str_copy_with_locker(Locker *locker, const String *str) { return str_substr_with_locker(locker, str, 0, -1); } /* =item C Equivalent to I except that C is not read-locked. =cut */ String *str_copy_with_locker_unlocked(Locker *locker, const String *str) { return str_substr_with_locker_unlocked(locker, str, 0, -1); } /* =item C Similar to I except that it recognises UNIX (C<"\n">), DOS/Windows (C<"\r\n">) and old Macintosh (C<"\r">) line endings (even different line endings in the same file), and it can read a line of any size into the I that it returns. Reading stops after the C, or after the end of the line is reached. Line endings are always stored as a single C<"\n"> character. A C is placed after the last character in the buffer. On success, returns a new I. It is the caller's responsibility to deallocate the new string with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On error, or when the end of file occurs while no characters have been read, returns C. Calls to this function can be mixed with calls to other input functions from the I library on the same input stream. =cut */ String *str_fgetline(FILE *stream) { return str_fgetline_with_locker(NULL, stream); } /* =item C Equivalent to I except that multiple threads accessing the new string will be synchronised by C. =cut */ String *str_fgetline_with_locker(Locker *locker, FILE *stream) { String *ret = NULL; char buf[BUFSIZ]; flockfile(stream); while (fgetline_unlocked(buf, BUFSIZ, stream)) { if (!ret) { if (!(ret = str_create_with_locker(locker, "%s", buf))) break; } else if (!str_append(ret, "%s", buf)) { str_destroy(&ret); break; } if (cstr(ret)[ret->length - 2] == '\n') break; } funlockfile(stream); return ret; } /* =item C Releases (deallocates) C. =cut */ void str_release(String *str) { Locker *locker; if (!str) return; if (str_wrlock(str)) return; locker = str->locker; mem_release(str->str); mem_release(str); locker_unlock(locker); } /* =item C Destroys (deallocates and sets to C) C<*str>. Returns C. B strings shared by multiple threads must not be destroyed until after all threads have finished with it. =cut */ void *str_destroy(String **str) { if (str && *str) { str_release(*str); *str = NULL; } return NULL; } /* =item C Claims a read lock on C (if C was created with a I). Clients must call this before calling I (for the purpose of reading the raw string data) on a string that was created with a I. It is the client's responsibility to call I when finished with the raw string data. It is also needed when multiple read-only I module functions need to be called atomically. It is the caller's responsibility to call I after the atomic operation. The only functions that may be called on C between calls to I and I are I and any read-only I module functions whose name ends with C<_unlocked>. On success, returns C<0>. On error, returns an error code. =cut */ #define str_rdlock(str) ((str) ? locker_rdlock((str)->locker) : EINVAL) #define str_wrlock(str) ((str) ? locker_wrlock((str)->locker) : EINVAL) #define str_unlock(str) ((str) ? locker_unlock((str)->locker) : EINVAL) int (str_rdlock)(const String *str) { return str_rdlock(str); } /* =item C Claims a write lock on C (if C was created with a I). Clients need to call this before calling I (for the purpose of modifying the raw string data) on a string that was created with a I. It is the client's responsibility to call I when finished with the raw string data. It is also needed when multiple read/write I module functions need to be called atomically. It is the caller's responsibility to call I after the atomic operation. The only functions that may be called on C between calls to I and I are I and any I module functions whose name ends with C<_unlocked>. On success, returns C<0>. On error, returns an error code. =cut */ int (str_wrlock)(const String *str) { return str_wrlock(str); } /* =item C Unlocks a read lock or a write lock on C obtained with I or I (if C was created with a I). On success, returns C<0>. On error, returns an error code. =cut */ int (str_unlock)(const String *str) { return str_unlock(str); } /* =item C Returns whether or not C is the empty string. On error, returns C<-1> with C set appropriately. =cut */ int str_empty(const String *str) { int empty; int err; if (!str) return set_errno(EINVAL); if ((err = str_rdlock(str))) return set_errno(err); empty = str_empty_unlocked(str); if ((err = str_unlock(str))) return set_errno(err); return empty; } /* =item C Equivalent to I except that C is not read-locked. =cut */ int str_empty_unlocked(const String *str) { if (!str) return set_errno(EINVAL); return (str->length == 1); } /* =item C Returns the length of C. On error, returns C<-1> with C set appropriately. =cut */ ssize_t str_length(const String *str) { size_t length; int err; if (!str) return set_errno(EINVAL); if ((err = str_rdlock(str))) return set_errno(err); length = str_length_unlocked(str); if ((err = str_unlock(str))) return set_errno(err); return length; } /* =item C Equivalent to I except that C is not read-locked. =cut */ ssize_t str_length_unlocked(const String *str) { if (!str) return set_errno(EINVAL); return str->length - 1; } /* =item C Returns the raw I string in C. Do not use this pointer to extend the length of the string. It's OK to use it to reduce the length of the string, provided that you call I or I immediately afterwards. When used on a string that is shared by multiple threads, I must appear between calls to I or I and I. =cut */ char *cstr(const String *str) { if (!str) return set_errnull(EINVAL); return str->str; } /* =item C Sets the length of C to C. Only needed after the raw I string returned by I has been used to shorten a string. On success, returns the length of C. On error, returns C<-1> with C set appropriately. =cut */ ssize_t str_set_length(String *str, size_t length) { ssize_t len; int err; if (!str) return set_errno(EINVAL); if ((err = str_wrlock(str))) return set_errno(err); len = str_set_length_unlocked(str, length); if ((err = str_unlock(str))) return set_errno(err); return len; } /* =item C Equivalent to I except that C is not write-locked. =cut */ ssize_t str_set_length_unlocked(String *str, size_t length) { if (!str || length >= str->length) return set_errno(EINVAL); str->length = length + 1; str->str[str->length - 1] = '\0'; return str->length - 1; } /* =item C Calculates and stores the length of C. Only needed after the raw I string returned by I has been used to shorten a string. Note: Treats C as a C-terminated string and should be avoided. Use I instead. On success, returns the length of C. On error, returns C<-1> with C set appropriately. =cut */ ssize_t str_recalc_length(String *str) { ssize_t len; int err; if (!str) return set_errno(EINVAL); if ((err = str_wrlock(str))) return set_errno(err); len = str_recalc_length_unlocked(str); if ((err = str_unlock(str))) return set_errno(err); return len; } /* =item C Equivalent to I except that C is not write-locked. =cut */ ssize_t str_recalc_length_unlocked(String *str) { if (!str) return set_errno(EINVAL); str->length = strlen(str->str) + 1; return str->length - 1; } /* =item C Makes C the empty string. On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_clear(String *str) { return str_remove_range(str, 0, -1); } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_clear_unlocked(String *str) { return str_remove_range_unlocked(str, 0, -1); } /* =item C Removes the C'th character from C. If C is negative, it refers to a character position relative to the end of the string (C<-1> is the position after the last character, C<-2> is the position of the last character, and so on). On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_remove(String *str, ssize_t index) { return str_remove_range(str, index, 1); } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_remove_unlocked(String *str, ssize_t index) { return str_remove_range_unlocked(str, index, 1); } /* =item C Removes C characters from C starting at C. If C or C are negative, they refer to character positions relative to the end of the string (C<-1> is the position after the last character, C<-2> is the position of the last character, and so on). On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_remove_range(String *str, ssize_t index, ssize_t range) { String *ret; int err; if (!str) return set_errnull(EINVAL); if ((err = str_wrlock(str))) return set_errnull(err); ret = str_remove_range_unlocked(str, index, range); if ((err = str_unlock(str))) return set_errnull(err); return ret; } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_remove_range_unlocked(String *str, ssize_t index, ssize_t range) { if (!str) return set_errnull(EINVAL); if (index < 0) index = str->length + index; if (index < 0) return set_errnull(EINVAL); if (range < 0) range = str->length + range - index; if (range < 0) return set_errnull(EINVAL); if (str->length - 1 < index + range) return set_errnull(EINVAL); contract(str, index, range); return str; } /* =item C Adds the string specified by C to C at position C. If C is negative, it refers to a character position relative to the end of the string (C<-1> is the position after the last character, C<-2> is the position of the last character, and so on). On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_insert(String *str, ssize_t index, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vinsert(str, index, format, args); va_end(args); return ret; } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_insert_unlocked(String *str, ssize_t index, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vinsert_unlocked(str, index, format, args); va_end(args); return ret; } /* =item C Equivalent to I with the variable argument list specified directly as for I. =cut */ String *str_vinsert(String *str, ssize_t index, const char *format, va_list args) { String *ret; int err; if (!str) return set_errnull(EINVAL); if ((err = str_wrlock(str))) return set_errnull(err); ret = str_vinsert_unlocked(str, index, format, args); if ((err = str_unlock(str))) return set_errnull(err); return ret; } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_vinsert_unlocked(String *str, ssize_t index, const char *format, va_list args) { String *tmp, *ret; if (!str) return set_errnull(EINVAL); if (index < 0) index = str->length + index; if (index < 0) return set_errnull(EINVAL); if (str->length - 1 < index) return set_errnull(EINVAL); if (!(tmp = str_vcreate(format, args))) return NULL; ret = str_insert_str_unlocked(str, index, tmp); str_release(tmp); return ret; } /* =item C Inserts C into C, starting at position C. If C is negative, it refers to a character position relative to the end of the string (C<-1> is the position after the last character, C<-2> is the position of the last character, and so on). On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_insert_str(String *str, ssize_t index, const String *src) { String *ret; int err; if (!str || !src) return set_errnull(EINVAL); if ((err = str_rdlock(src))) return set_errnull(err); if ((err = str_wrlock(str))) { str_unlock(src); return set_errnull(err); } ret = str_insert_str_unlocked(str, index, src); if ((err = str_unlock(str))) { str_unlock(src); return set_errnull(err); } if ((err = str_unlock(src))) return set_errnull(err); return ret; } /* =item C Equivalent to I except that C is not write-locked and C is not read-locked. Note: If C needs to be read-locked, it is the caller's responsibility to lock and unlock it explicitly with I and I. =cut */ String *str_insert_str_unlocked(String *str, ssize_t index, const String *src) { size_t length; if (!str || !src) return set_errnull(EINVAL); if (index < 0) index = str->length + index; if (index < 0) return set_errnull(EINVAL); if (str->length - 1 < index) return set_errnull(EINVAL); length = src->length - 1; if (expand(str, index, length) == -1) return NULL; memcpy(str->str + index, src->str, length); return str; } /* =item C Appends the string specified by C to C. On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_append(String *str, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vinsert(str, -1, format, args); va_end(args); return ret; } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_append_unlocked(String *str, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vinsert_unlocked(str, -1, format, args); va_end(args); return ret; } /* =item C Equivalent to I with the variable argument list specified directly as for I. =cut */ String *str_vappend(String *str, const char *format, va_list args) { return str_vinsert(str, -1, format, args); } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_vappend_unlocked(String *str, const char *format, va_list args) { return str_vinsert_unlocked(str, -1, format, args); } /* =item C Appends C to C. On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_append_str(String *str, const String *src) { return str_insert_str(str, -1, src); } /* =item C Equivalent to I except that C is not write-locked and C is not read-locked. Note: If C needs to be read-locked, it is the caller's responsibility to lock and unlock it explicitly with I and I. =cut */ String *str_append_str_unlocked(String *str, const String *src) { return str_insert_str_unlocked(str, -1, src); } /* =item C Prepends the string specified by C to C. On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_prepend(String *str, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vinsert(str, 0, format, args); va_end(args); return ret; } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_prepend_unlocked(String *str, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vinsert_unlocked(str, 0, format, args); va_end(args); return ret; } /* =item C Equivalent to I with the variable argument list specified directly as for I. =cut */ String *str_vprepend(String *str, const char *format, va_list args) { return str_vinsert(str, 0, format, args); } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_vprepend_unlocked(String *str, const char *format, va_list args) { return str_vinsert_unlocked(str, 0, format, args); } /* =item C Prepends C to C. On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_prepend_str(String *str, const String *src) { return str_insert_str(str, 0, src); } /* =item C Equivalent to I except that C is not write-locked and C is not read-locked. Note: If C needs to be read-locked, it is the caller's responsibility to lock and unlock it explicitly with I and I. =cut */ String *str_prepend_str_unlocked(String *str, const String *src) { return str_insert_str_unlocked(str, 0, src); } /* =item C Replaces C characters in C, starting at C, with the string specified by C. If C or C are negative, they refer to character positions relative to the end of the string (C<-1> is the position after the last character, C<-2> is the position of the last character, and so on). On success, returns C. On error, returns C with C set appropriately. =cut */ String *str_replace(String *str, ssize_t index, ssize_t range, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vreplace(str, index, range, format, args); va_end(args); return ret; } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_replace_unlocked(String *str, ssize_t index, ssize_t range, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vreplace_unlocked(str, index, range, format, args); va_end(args); return ret; } /* =item C Equivalent to I with the variable argument list specified directly as for I. =cut */ String *str_vreplace(String *str, ssize_t index, ssize_t range, const char *format, va_list args) { String *tmp, *ret; if (!str) return set_errnull(EINVAL); if (!(tmp = str_vcreate(format, args))) return NULL; ret = str_replace_str(str, index, range, tmp); str_release(tmp); return ret; } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_vreplace_unlocked(String *str, ssize_t index, ssize_t range, const char *format, va_list args) { String *tmp, *ret; if (!str) return set_errnull(EINVAL); if (!(tmp = str_vcreate(format, args))) return NULL; ret = str_replace_str_unlocked(str, index, range, tmp); str_release(tmp); return ret; } /* =item C Replaces C characters in C, starting at C, with C. If C or C are negative, they refer to character positions relative to the end of the string (C<-1> is the position after the last character, C<-2> is the position of the last character, and so on). On success, return C. On error, returns C with C set appropriately. =cut */ String *str_replace_str(String *str, ssize_t index, ssize_t range, const String *src) { String *ret; int err; if (!src || !str) return set_errnull(EINVAL); if ((err = str_rdlock(src))) return set_errnull(err); if ((err = str_wrlock(str))) { str_unlock(src); return set_errnull(err); } ret = str_replace_str_unlocked(str, index, range, src); if ((err = str_unlock(str))) { str_unlock(src); return set_errnull(err); } if ((err = str_unlock(src))) return set_errnull(err); return ret; } /* =item C Equivalent to I except that C is not write-locked and C is not read-locked. Note: If C needs to be read-locked, it is the caller's responsibility to lock and unlock it explicitly with I and I. =cut */ String *str_replace_str_unlocked(String *str, ssize_t index, ssize_t range, const String *src) { size_t length; if (!src || !str) return set_errnull(EINVAL); if (index < 0) index = str->length + index; if (index < 0) return set_errnull(EINVAL); if (range < 0) range = str->length + range - index; if (range < 0) return set_errnull(EINVAL); if (str->length - 1 < index + range) return set_errnull(EINVAL); length = src->length - 1; if (adjust(str, index, range, length) == -1) return NULL; memcpy(str->str + index, src->str, length); return str; } /* =item C Creates a new I object consisting of C characters from C, starting at C. If C or C are negative, they refer to character positions relative to the end of the string (C<-1> is the position after the last character, C<-2> is the position of the last character, and so on). On success, returns the new string. It is the caller's responsibility to deallocate the new string with I or I. On error, returns C with C set appropriately. =cut */ String *str_substr(const String *str, ssize_t index, ssize_t range) { return str_substr_with_locker(NULL, str, index, range); } /* =item C Equivalent to I except that C is not read-locked. =cut */ String *str_substr_unlocked(const String *str, ssize_t index, ssize_t range) { return str_substr_with_locker_unlocked(NULL, str, index, range); } /* =item C Equivalent to I except that multiple threads accessing the new substring will be synchronised by C. =cut */ String *str_substr_with_locker(Locker *locker, const String *str, ssize_t index, ssize_t range) { String *ret; int err; if (!str) return set_errnull(EINVAL); if ((err = str_rdlock(str))) return set_errnull(err); ret = str_substr_with_locker_unlocked(locker, str, index, range); if ((err = str_unlock(str))) return set_errnull(err); return ret; } /* =item C Equivalent to I except that C is not read-locked. =cut */ String *str_substr_with_locker_unlocked(Locker *locker, const String *str, ssize_t index, ssize_t range) { String *ret; if (!str) return set_errnull(EINVAL); if (index < 0) index = str->length + index; if (index < 0) return set_errnull(EINVAL); if (range < 0) range = str->length + range - index; if (range < 0) return set_errnull(EINVAL); if (str->length - 1 < index + range) return set_errnull(EINVAL); if (!(ret = str_create_with_locker_sized(locker, range + 1, NULL))) return NULL; memcpy(ret->str, str->str + index, range); ret->length = range + 1; ret->str[ret->length - 1] = '\0'; return ret; } /* =item C Equivalent to I but works on an ordinary I string. =cut */ String *substr(const char *str, ssize_t index, ssize_t range) { return substr_with_locker(NULL, str, index, range); } /* =item C Equivalent to I except that multiple threads accessing the new substring will be synchronised by C. Note that no locking is performed on C as it is a raw I string. =cut */ String *substr_with_locker(Locker *locker, const char *str, ssize_t index, ssize_t range) { String *ret; size_t len = 0; if (!str) return set_errnull(EINVAL); if (index < 0 || range < 0) len = strlen(str) + 1; if (index < 0) index = len + index; if (index < 0) return set_errnull(EINVAL); if (range < 0) range = len + range - index; if (range < 0) return set_errnull(EINVAL); if (!(ret = str_create_with_locker_sized(locker, range + 1, NULL))) return NULL; memcpy(ret->str, str + index, range); ret->length = range + 1; ret->str[ret->length - 1] = '\0'; return ret; } /* =item C Removes a substring from C starting at C of length C characters. If C or C are negative, they refer to character positions relative to the end of the string (C<-1> is the position after the last character, C<-2> is the position of the last character, and so on). On success, returns the substring. It is the caller's responsibility to deallocate the new substring with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On error, returns C with C set appropriately. =cut */ String *str_splice(String *str, ssize_t index, ssize_t range) { return str_splice_with_locker(NULL, str, index, range); } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_splice_unlocked(String *str, ssize_t index, ssize_t range) { return str_splice_with_locker_unlocked(NULL, str, index, range); } /* =item C Equivalent to I except that multiple threads accessing the new string will be synchronised by C. =cut */ String *str_splice_with_locker(Locker *locker, String *str, ssize_t index, ssize_t range) { String *ret; int err; if (!str) return set_errnull(EINVAL); if ((err = str_wrlock(str))) return set_errnull(err); ret = str_splice_with_locker_unlocked(locker, str, index, range); if ((err = str_unlock(str))) { str_release(ret); return set_errnull(err); } return ret; } /* =item C Equivalent to I except that C is not write-locked. =cut */ String *str_splice_with_locker_unlocked(Locker *locker, String *str, ssize_t index, ssize_t range) { String *ret; if (!str) return set_errnull(EINVAL); if (!(ret = str_substr_with_locker_unlocked(locker, str, index, range))) return NULL; if (!str_remove_range_unlocked(str, index, range)) { str_release(ret); return NULL; } return ret; } /* =item C Creates a new I containing the string determined by C repeated C times. On success, return the new string. It is the caller's responsibility to deallocate the new string with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On error, returns C with C set appropriately. =cut */ String *str_repeat(size_t count, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vrepeat_with_locker(NULL, count, format, args); va_end(args); return ret; } /* =item C Equivalent to I except that multiple threads accessing the new string will be synchronised by C. =cut */ String *str_repeat_with_locker(Locker *locker, size_t count, const char *format, ...) { String *ret; va_list args; va_start(args, format); ret = str_vrepeat_with_locker(locker, count, format, args); va_end(args); return ret; } /* =item C Equivalent to I with the variable argument list specified directly as for I. =cut */ String *str_vrepeat(size_t count, const char *format, va_list args) { return str_vrepeat_with_locker(NULL, count, format, args); } /* =item C Equivalent to I except that multiple threads accessing the new string will be synchronised by C. =cut */ String *str_vrepeat_with_locker(Locker *locker, size_t count, const char *format, va_list args) { String *tmp, *ret; ssize_t length; size_t i; if (!(tmp = str_vcreate(format, args))) return NULL; if ((length = str_length(tmp)) == -1) return NULL; if (!(ret = str_create_with_locker_sized(locker, length * count + 1, NULL))) { str_release(tmp); return NULL; } for (i = 0; i < count; ++i) { if (!str_append_str(ret, tmp)) { str_release(tmp); str_release(ret); return NULL; } } str_release(tmp); return ret; } /* =item C This is just like the I operator. The following documentation was taken from I. Transliterates all occurrences of the characters in C with the corresponding character in C. On success, returns the number of characters replaced or deleted. On error, returns C<-1> with C set appropriately. A character range can be specified with a hyphen, so C does the same replacement as C. Note also that the whole range idea is rather unportable between character sets - and even within character sets they might cause results you probably didn't expect. A sound principle is to use only ranges that begin from and end at either alphabets of equal case (a-e, A-E), or digits (0-4). Anything else is unsafe. If in doubt, spell out the character sets in full. Options: TR_COMPLEMENT Complement from. TR_DELETE Delete found but unreplaced characters. TR_SQUASH Squash duplicate replaced characters. If C is specified, C is complemented. If C is specified, any characters specified by C not found in C are deleted. (Note that this is slightly more flexible than the behavior of some tr programs, which delete anything they find in C.) If C is specified, sequences of characters that were transliterated to the same character are squashed down to a single instance of the character. If C is used, C is always interpreted exactly as specified. Otherwise, if C is shorter than C, the final character is replicated till it is long enough. If C is empty or C, C is replicated. This latter is useful for counting characters in a class or for squashing character sequences in a class. Examples: str_tr(s, "A-Z", "a-z", 0); // canonicalize to lower case str_tr(s, "a-z", "A-Z", 0); // canonicalize to upper case str_tr(s, "a-zA-Z", "A-Za-z", 0); // swap upper and lower case str_tr(s, "*", "*", 0); // count the stars in str str_tr(s, "0-9", "", 0); // count the digits in $_ str_tr(s, "a-zA-Z", "", TR_SQUASH); // bookkeeper -> bokeper str_tr(s, "a-zA-Z", " ", TR_COMPLEMENT | TR_SQUASH); // change non-alphas to single space str_tr(c, "a-zA-Z", "n-za-mN-ZA-M", 0); // Rot13 from = str_create("\200-\377"); to = str_create("%c-\177", '\000'); str_tr_str(s, from, to, 0); // clear 8th bit If multiple transliterations are given for a character, only the first one is used: str_tr(str, "AAA", "XYZ", 0); will transliterate any A to X. =cut */ static StringTR *tr_compile_table(StringTR *table, const char *from, const char *to, int option); int str_tr(String *str, const char *from, const char *to, int option) { StringTR table[1]; if (!str || !from) return set_errno(EINVAL); table->locker = NULL; if (!tr_compile_table(table, from, to, option)) return -1; return str_tr_compiled(str, table); } /* =item C Equivalent to I except that C is not write-locked. =cut */ int str_tr_unlocked(String *str, const char *from, const char *to, int option) { StringTR table[1]; if (!str || !from) return set_errno(EINVAL); table->locker = NULL; if (!tr_compile_table(table, from, to, option)) return -1; return str_tr_compiled_unlocked(str, table); } /* =item C Equivalent to I except that C and C are I objects. This is needed when C or C need to contain C characters. =cut */ static StringTR *str_tr_compile_table(StringTR *table, const String *from, const String *to, int option); int str_tr_str(String *str, const String *from, const String *to, int option) { StringTR table[1]; if (!str || !from) return set_errno(EINVAL); table->locker = NULL; if (!str_tr_compile_table(table, from, to, option)) return -1; return str_tr_compiled(str, table); } /* =item C Equivalent to I except that C is not write-locked and C and C are not read-locked. Note: If C and C need to be read-locked, it is the caller's responsibility to lock and unlock them explicitly with I and I. =cut */ static StringTR *str_tr_compile_table_unlocked(StringTR *table, const String *from, const String *to, int option); int str_tr_str_unlocked(String *str, const String *from, const String *to, int option) { StringTR table[1]; if (!str || !from) return set_errno(EINVAL); table->locker = NULL; if (!str_tr_compile_table_unlocked(table, from, to, option)) return -1; return str_tr_compiled_unlocked(str, table); } /* =item C Equivalent to I but works on an ordinary I string. =cut */ int tr(char *str, const char *from, const char *to, int option) { StringTR table[1]; if (!str || !from) return set_errno(EINVAL); table->locker = NULL; if (!tr_compile_table(table, from, to, option)) return -1; return tr_compiled(str, table); } /* =item C Compiles C, C and C