mirror of
git://git.psyced.org/git/psyclpc
synced 2024-08-15 03:20:16 +00:00
1304 lines
34 KiB
C
1304 lines
34 KiB
C
|
/*------------------------------------------------------------------
|
||
|
* String functions.
|
||
|
*
|
||
|
*------------------------------------------------------------------
|
||
|
* A collection of string related functions and utilities:
|
||
|
*
|
||
|
* xstrncpy(): a safer strncpy().
|
||
|
* trim_all_spaces(): used in efun parse_command().
|
||
|
*
|
||
|
* strbuf_t: an extendable string buffer, useful for incremental
|
||
|
* construction of a string.
|
||
|
* TODO: I am afraid the handling of length in _grow() itself and
|
||
|
* TODO:: its calls is a bit too far on the conservative side.
|
||
|
* TODO: Rewrite the strbuf_t to use a scatter-gather storing
|
||
|
* TODO:: of data, to avoid allocations of large buffers (this happens
|
||
|
* TODO:: when doing save/restore on large objects).
|
||
|
*
|
||
|
* --- Efuns and Operators ---
|
||
|
*
|
||
|
* f_convert_charset(): Convert charsets using iconv().
|
||
|
* intersect_strings(): Implements '&' and '-' on strings
|
||
|
* x_filter_string(): Filter a string through a callback or mapping.
|
||
|
* x_map_string(): Map a string through a callback or mapping.
|
||
|
*
|
||
|
*------------------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
|
||
|
#include "driver.h"
|
||
|
#include "typedefs.h"
|
||
|
|
||
|
#include "my-alloca.h"
|
||
|
#include <stdarg.h>
|
||
|
#include <stddef.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/types.h>
|
||
|
#ifdef HAS_ICONV
|
||
|
#include <iconv.h>
|
||
|
#endif
|
||
|
|
||
|
#include "strfuns.h"
|
||
|
|
||
|
#include "comm.h"
|
||
|
#include "interpret.h"
|
||
|
#include "main.h"
|
||
|
#include "mapping.h"
|
||
|
#include "mstrings.h"
|
||
|
#include "object.h"
|
||
|
#include "simulate.h"
|
||
|
#include "stdstrings.h"
|
||
|
#include "svalue.h"
|
||
|
#include "xalloc.h"
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
void
|
||
|
strbuf_zero (strbuf_t *buf)
|
||
|
|
||
|
/* Initialise the given string buffer <buf>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
buf->alloc_len = 0;
|
||
|
buf->length = 0;
|
||
|
buf->buf = NULL;
|
||
|
}
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
void
|
||
|
strbuf_free (strbuf_t *buf)
|
||
|
|
||
|
/* Free the given string buffer <buf>.
|
||
|
* TODO: Not necessary once all strings are counted and with length?
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
if (buf->buf)
|
||
|
xfree(buf->buf);
|
||
|
strbuf_zero(buf);
|
||
|
}
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
static INLINE size_t
|
||
|
strbuf_grow (strbuf_t *buf, size_t len)
|
||
|
|
||
|
/* Extend the stringbuffer <buf> to hold at least <len> more
|
||
|
* bytes (ie. enough for a string of length <len>-1).
|
||
|
*
|
||
|
* Return <len> if all memory could be allocated, or a lower number
|
||
|
* of only part of the required memory is available.
|
||
|
*
|
||
|
* N.B.: be careful with overflows when doing the checks.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
size_t new_len;
|
||
|
|
||
|
/* Catch some simple situations. */
|
||
|
if (buf->alloc_len >= MAX_STRBUF_LEN)
|
||
|
return 0; /* Truncated */
|
||
|
|
||
|
if (buf->alloc_len - buf->length > len)
|
||
|
return len;
|
||
|
|
||
|
/* Allocate more than we need in anticipation of further adds,
|
||
|
* but not more than we can manage
|
||
|
*/
|
||
|
if (MAX_STRBUF_LEN - buf->length < len * 3)
|
||
|
{
|
||
|
new_len = MAX_STRBUF_LEN;
|
||
|
if (new_len - buf->length < len)
|
||
|
len = new_len - buf->length;
|
||
|
}
|
||
|
else
|
||
|
new_len = buf->length + len * 3;
|
||
|
|
||
|
|
||
|
/* Is this the first allocation? */
|
||
|
if (!buf->buf)
|
||
|
{
|
||
|
memsafe(buf->buf = xalloc(new_len), new_len, "new strbuf");
|
||
|
buf->alloc_len = (u_long)new_len;
|
||
|
buf->length = 0;
|
||
|
*(buf->buf) = '\0';
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
/* Extension of the existing buffer */
|
||
|
|
||
|
memsafe(buf->buf = rexalloc(buf->buf, new_len), new_len, "larger strbuf");
|
||
|
buf->alloc_len = (u_long)new_len;
|
||
|
return len;
|
||
|
} /* strbuf_grow() */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
void
|
||
|
strbuf_add (strbuf_t *buf, const char * text)
|
||
|
|
||
|
/* Add the <text> to string buffer <buf>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
size_t len;
|
||
|
|
||
|
len = strlen(text) + 1;
|
||
|
if (!len)
|
||
|
return;
|
||
|
|
||
|
if (len + buf->length > buf->alloc_len)
|
||
|
len = strbuf_grow(buf, len);
|
||
|
if (len)
|
||
|
{
|
||
|
memcpy(buf->buf+buf->length, text, len);
|
||
|
buf->length += len-1;
|
||
|
buf->buf[buf->length] = '\0';
|
||
|
}
|
||
|
} /* strbuf_add() */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
void
|
||
|
strbuf_addn (strbuf_t *buf, const char * text, size_t len)
|
||
|
|
||
|
/* Add the <len> characters starting at <text> to string buffer <buf>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
if (!len)
|
||
|
return;
|
||
|
|
||
|
len += 1;
|
||
|
if (len + buf->length > buf->alloc_len)
|
||
|
len = strbuf_grow(buf, len);
|
||
|
if (len)
|
||
|
{
|
||
|
len--;
|
||
|
memcpy(buf->buf+buf->length, text, len);
|
||
|
buf->length += len;
|
||
|
buf->buf[buf->length] = '\0';
|
||
|
}
|
||
|
} /* strbuf_addn() */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
void
|
||
|
strbuf_addc (strbuf_t *buf, char ch)
|
||
|
|
||
|
/* Add the <ch>aracter to string buffer <buf>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
size_t len;
|
||
|
|
||
|
len = 2;
|
||
|
if (2 + buf->length > buf->alloc_len)
|
||
|
len = strbuf_grow(buf, 2);
|
||
|
if (len)
|
||
|
{
|
||
|
buf->buf[buf->length] = ch;
|
||
|
buf->length++;
|
||
|
buf->buf[buf->length] = '\0';
|
||
|
}
|
||
|
} /* strbuf_addc() */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
void
|
||
|
strbuf_addf (strbuf_t *buf, const char * format, ...)
|
||
|
|
||
|
/* Create a string from <format> and the following arguments using
|
||
|
* sprintf() rules, and add the result to the string buffer <buf>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
char tmpbuf[4096];
|
||
|
va_list vargs;
|
||
|
|
||
|
tmpbuf[sizeof tmpbuf - 1] = '\0';
|
||
|
|
||
|
va_start(vargs, format);
|
||
|
vsprintf(tmpbuf, format, vargs);
|
||
|
va_end(vargs);
|
||
|
|
||
|
if (tmpbuf[sizeof tmpbuf - 1])
|
||
|
fatal("strbuf_addf: Internal buffer overflow.\n");
|
||
|
|
||
|
strbuf_add(buf, tmpbuf);
|
||
|
} /* strbuf_addf() */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
void
|
||
|
strbuf_send (strbuf_t *buf)
|
||
|
|
||
|
/* Send the string collected in <buf> out to the current user with
|
||
|
* add_message(), and clear <buf>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
if (buf->buf && buf->length)
|
||
|
{
|
||
|
add_message("%s", buf->buf);
|
||
|
}
|
||
|
|
||
|
/* Empty the string buffer */
|
||
|
if (buf->buf)
|
||
|
xfree(buf->buf);
|
||
|
buf->buf = NULL;
|
||
|
buf->length = 0;
|
||
|
buf->alloc_len = 0;
|
||
|
} /* strbuf_send() */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
void
|
||
|
strbuf_store (strbuf_t *buf, svalue_t *svp)
|
||
|
|
||
|
/* Store the string collected in <buf>, which may be the null string "",
|
||
|
* into the empty svalue *<svp>, then clear <buf>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
svp->type = T_STRING;
|
||
|
if (buf->buf && buf->length)
|
||
|
{
|
||
|
svp->u.str = new_n_mstring(buf->buf, buf->length);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
svp->u.str = ref_mstring(STR_EMPTY);
|
||
|
}
|
||
|
|
||
|
/* Empty the string buffer */
|
||
|
if (buf->buf)
|
||
|
xfree(buf->buf);
|
||
|
buf->buf = NULL;
|
||
|
buf->length = 0;
|
||
|
buf->alloc_len = 0;
|
||
|
} /* strbuf_store() */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
void
|
||
|
strbuf_copy (strbuf_t *buf, char *cbuf)
|
||
|
|
||
|
/* Copy the string collected in <buf>, which may be the null string "",
|
||
|
* into the buffer <cbuf> which must have been allocated by the caller
|
||
|
* to a suitable size. The copied string will be terminated with a '\0'.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
if (buf->buf && buf->length)
|
||
|
memcpy(cbuf, buf->buf, buf->length);
|
||
|
cbuf[buf->length] = '\0';
|
||
|
} /* strbuf_copy() */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
char *
|
||
|
xstrncpy (char * dest, const char * src, size_t num)
|
||
|
|
||
|
/* Copy string <src> at address <dest> up to and including the terminating
|
||
|
* 0 or up to size <num>, whichever comes first. Result is <dest>.
|
||
|
*
|
||
|
* In contrast to strncpy(), the copying terminates if a terminating 0
|
||
|
* is found (and copied) in <src> - strncpy() would add additional 0s
|
||
|
* until a total of <num> characters has been written to <dest>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
char * p = dest;
|
||
|
|
||
|
while (num-- != 0 && (*p++ = *src++) != '\0') NOOP;
|
||
|
return dest;
|
||
|
} /* xstrncpy() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
string_t *
|
||
|
trim_all_spaces (const string_t * txt)
|
||
|
|
||
|
/* Trim the input string <txt> by removing all leading and trailing
|
||
|
* space, and by folding embedded space runs into just one each.
|
||
|
* Return the new string with one ref; the refcount of <txt> is not changed.
|
||
|
*
|
||
|
* Throw an error when out of memory.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
char * dest;
|
||
|
const char * src;
|
||
|
size_t dest_ix, src_ix, srclen;
|
||
|
string_t * rc;
|
||
|
|
||
|
dest = alloca(mstrsize(txt));
|
||
|
if (dest == NULL)
|
||
|
errorf("Stack overflow (%ld bytes)\n", (long)mstrsize(txt));
|
||
|
|
||
|
src = get_txt(txt);
|
||
|
srclen = mstrsize(txt);
|
||
|
src_ix = 0;
|
||
|
dest_ix = 0;
|
||
|
|
||
|
/* Blank out trailing spaces */
|
||
|
while (srclen > 0 && src[srclen-1] == ' ')
|
||
|
srclen--;
|
||
|
|
||
|
/* Skip leading spaces */
|
||
|
while (src_ix < srclen && *src == ' ')
|
||
|
src_ix++, src++;
|
||
|
|
||
|
/* Copy characters, but fold embedded spaces. */
|
||
|
for ( ; src_ix < srclen; src_ix++, src++, dest_ix++)
|
||
|
{
|
||
|
dest[dest_ix] = *src;
|
||
|
|
||
|
/* If this and the next character is a space, forward
|
||
|
* src until the last space in this run.
|
||
|
*/
|
||
|
if (' ' == *src)
|
||
|
{
|
||
|
while (src_ix+1 < srclen && ' ' == src[1])
|
||
|
src_ix++, src++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
memsafe(rc = new_n_mstring(dest, dest_ix), dest_ix, "trimmed result");
|
||
|
return rc;
|
||
|
} /* trim_all_spaces() */
|
||
|
|
||
|
/*====================================================================*/
|
||
|
|
||
|
/* EFUNS */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
#ifdef HAS_ICONV
|
||
|
|
||
|
svalue_t *
|
||
|
f_convert_charset (svalue_t *sp)
|
||
|
|
||
|
/* EFUN convert_charset()
|
||
|
*
|
||
|
* string convert_charset(string str, string from_cs, string to_cs)
|
||
|
*
|
||
|
* Convert the string <str> from charset <from_cs> to charset <to_cs>
|
||
|
* and return the converted string.
|
||
|
*
|
||
|
* The efun is only available on systems with libiconv.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
iconv_t context;
|
||
|
|
||
|
string_t *from_cs, *to_cs, *in_str, *out_str;
|
||
|
|
||
|
#if HAS_ICONV_NONCONST_IN
|
||
|
# define ICONV_IN_CAST (char**)
|
||
|
#else
|
||
|
# define ICONV_IN_CAST
|
||
|
#endif
|
||
|
|
||
|
const char *pIn; /* Input string pointer */
|
||
|
size_t in_len; /* Input length */
|
||
|
size_t in_left; /* Input length left */
|
||
|
|
||
|
char * out_buf; /* Output buffer */
|
||
|
size_t out_size; /* Size of the output buffer */
|
||
|
size_t out_left; /* Size left in output buffer */
|
||
|
char *pOut; /* Output string pointer */
|
||
|
|
||
|
in_str = sp[-2].u.str;
|
||
|
from_cs = sp[-1].u.str;
|
||
|
to_cs = sp->u.str;
|
||
|
|
||
|
pIn = get_txt(in_str);
|
||
|
in_len = mstrsize(in_str);
|
||
|
in_left = in_len;
|
||
|
|
||
|
/* If the input string is empty, we can return immediately
|
||
|
* (and in fact must since the allocator will balk at allocating 0 bytes)
|
||
|
*/
|
||
|
if (!in_len)
|
||
|
{
|
||
|
sp -= 2;
|
||
|
free_string_svalue(sp);
|
||
|
free_string_svalue(sp+1);
|
||
|
put_string(sp, sp[2].u.str);
|
||
|
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
/* Allocate a temporary output string */
|
||
|
out_size = in_len > 65536 ? (in_len + 33) : (2 * in_len);
|
||
|
out_left = out_size;
|
||
|
|
||
|
xallocate(out_buf, out_size, "iconv buffer");
|
||
|
pOut = out_buf;
|
||
|
|
||
|
/* Open the iconv context */
|
||
|
context = iconv_open(get_txt(to_cs), get_txt(from_cs));
|
||
|
if (context == (iconv_t) -1)
|
||
|
{
|
||
|
xfree(out_buf);
|
||
|
|
||
|
if (errno == EINVAL)
|
||
|
errorf("convert_charset(): Conversion '%s' -> '%s' not supported.\n"
|
||
|
, get_txt(from_cs), get_txt(to_cs)
|
||
|
);
|
||
|
else
|
||
|
errorf("convert_charset(): Error %d.\n", errno);
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
/* Convert the string, reallocating the output buffer where necessary */
|
||
|
while (in_left)
|
||
|
{
|
||
|
size_t rc;
|
||
|
|
||
|
rc = iconv(context, ICONV_IN_CAST &pIn, &in_left, &pOut, &out_left);
|
||
|
if (rc == (size_t)-1)
|
||
|
{
|
||
|
if (errno == E2BIG)
|
||
|
{
|
||
|
/* Reallocate output buffer */
|
||
|
size_t newsize;
|
||
|
char * tmp;
|
||
|
|
||
|
newsize = out_size + (in_len > 128 ? in_len : 128);
|
||
|
tmp = rexalloc(out_buf, newsize);
|
||
|
if (!tmp)
|
||
|
{
|
||
|
iconv_close(context);
|
||
|
xfree(out_buf);
|
||
|
outofmem(newsize, "iconv buffer");
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
out_buf = tmp;
|
||
|
pOut = out_buf + out_size;
|
||
|
out_left = newsize - out_size;
|
||
|
out_size = newsize;
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Other error: clean up */
|
||
|
iconv_close(context);
|
||
|
xfree(out_buf);
|
||
|
|
||
|
if (errno == EILSEQ)
|
||
|
{
|
||
|
errorf("convert_charset(): Invalid character sequence at index %ld\n", (long)(pIn - get_txt(in_str)));
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
if (errno == EINVAL)
|
||
|
{
|
||
|
errorf("convert_charset(): Incomplete character sequence at index %ld\n", (long)(pIn - get_txt(in_str)));
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
errorf("convert_charset(): Error %d at index %ld\n"
|
||
|
, errno, (long)(pIn - get_txt(in_str))
|
||
|
);
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
} /* if (rc < 0) */
|
||
|
} /* while (in_left) */
|
||
|
|
||
|
/* While the actual conversion is complete, the output stream may now
|
||
|
* be in a non-base state. Add the necessary epilogue to get back
|
||
|
* to the base state.
|
||
|
*/
|
||
|
while(1)
|
||
|
{
|
||
|
size_t rc;
|
||
|
rc = iconv(context, NULL, NULL, &pOut, &out_left);
|
||
|
if (rc == (size_t)-1)
|
||
|
{
|
||
|
if (errno == E2BIG)
|
||
|
{
|
||
|
/* Reallocate output buffer */
|
||
|
size_t newsize;
|
||
|
char * tmp;
|
||
|
|
||
|
newsize = out_size + (in_len > 128 ? in_len : 128);
|
||
|
tmp = rexalloc(out_buf, newsize);
|
||
|
if (!tmp)
|
||
|
{
|
||
|
iconv_close(context);
|
||
|
xfree(out_buf);
|
||
|
outofmem(newsize, "iconv buffer");
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
out_buf = tmp;
|
||
|
pOut = out_buf + out_size;
|
||
|
out_left = newsize - out_size;
|
||
|
out_size = newsize;
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Other error: clean up */
|
||
|
iconv_close(context);
|
||
|
xfree(out_buf);
|
||
|
|
||
|
if (errno == EILSEQ)
|
||
|
{
|
||
|
errorf("convert_charset(): Invalid character sequence at index %ld\n", (long)(pIn - get_txt(in_str)));
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
if (errno == EINVAL)
|
||
|
{
|
||
|
errorf("convert_charset(): Incomplete character sequence at index %ld\n", (long)(pIn - get_txt(in_str)));
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
errorf("convert_charset(): Error %d at index %ld\n"
|
||
|
, errno, (long)(pIn - get_txt(in_str))
|
||
|
);
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
} /* if (rc < 0) */
|
||
|
|
||
|
/* At this point, the iconv() succeeded: we're done */
|
||
|
break;
|
||
|
} /* while(1) */
|
||
|
|
||
|
iconv_close(context);
|
||
|
|
||
|
/* Get the return string and prepare the return arguments */
|
||
|
out_str = new_n_mstring(out_buf, out_size - out_left);
|
||
|
xfree(out_buf);
|
||
|
if (!out_str)
|
||
|
{
|
||
|
outofmem(out_size - out_left, "convert_charset() result");
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
free_string_svalue(sp--);
|
||
|
free_string_svalue(sp--);
|
||
|
free_string_svalue(sp);
|
||
|
put_string(sp, out_str);
|
||
|
|
||
|
return sp;
|
||
|
} /* f_convert_charset() */
|
||
|
|
||
|
#endif /* HAS_ICONV */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
static char *
|
||
|
sort_string (const string_t * p_in, size_t len, long ** pos)
|
||
|
|
||
|
/* Sort the characters of string <in> (with length <len>) by their numeric
|
||
|
* values and return a newly allocated memory block with the sorted string.
|
||
|
* If <pos> is not NULL, it will be set to a newly allocated memory block
|
||
|
* giving the original positions of the characters in the sorted string.
|
||
|
*
|
||
|
* We use Mergesort to sort the strings.
|
||
|
* TODO: Use Quicksort instead of Mergesort?
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
const char * in; /* Input string */
|
||
|
char * out; /* Result string */
|
||
|
long * outpos; /* Result position array */
|
||
|
char * tmp; /* Temporary string */
|
||
|
long * tmppos; /* Temporary position array */
|
||
|
size_t step;
|
||
|
size_t i, j;
|
||
|
|
||
|
in = get_txt(p_in);
|
||
|
out = xalloc(len+1);
|
||
|
tmp = xalloc(len+1);
|
||
|
if (!out || !tmp)
|
||
|
{
|
||
|
if (out)
|
||
|
xfree(out);
|
||
|
if (tmp)
|
||
|
xfree(tmp);
|
||
|
errorf("(sort_string) Out of memory (2 * %lu bytes) for temporaries.\n"
|
||
|
, (unsigned long) len+1);
|
||
|
}
|
||
|
out[len] = '\0';
|
||
|
tmp[len] = '\0';
|
||
|
|
||
|
if (pos)
|
||
|
{
|
||
|
outpos = xalloc(len * sizeof(*outpos) + 1);
|
||
|
tmppos = xalloc(len * sizeof(*outpos) + 1);
|
||
|
/* +1 so that smalloc won't complain when given an empty string */
|
||
|
if (!outpos || !tmppos)
|
||
|
{
|
||
|
if (out)
|
||
|
xfree(out);
|
||
|
if (tmp)
|
||
|
xfree(tmp);
|
||
|
if (outpos)
|
||
|
xfree(outpos);
|
||
|
if (tmppos)
|
||
|
xfree(tmppos);
|
||
|
errorf("(sort_string) Out of memory (2 * %lu bytes) for positions.\n"
|
||
|
, (unsigned long) len*sizeof(*outpos)+1);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
outpos = NULL;
|
||
|
tmppos = NULL;
|
||
|
}
|
||
|
|
||
|
/* First Mergesort pass: comparison of adjacent characters
|
||
|
* and initialisation of the out arrays.
|
||
|
*/
|
||
|
for (i = 0; i < len; i += 2)
|
||
|
{
|
||
|
if (i == len-1)
|
||
|
{
|
||
|
out[i] = in[i];
|
||
|
if (outpos)
|
||
|
outpos[i] = i;
|
||
|
}
|
||
|
else if (in[i] <= in[i+1])
|
||
|
{
|
||
|
out[i] = in[i];
|
||
|
out[i+1] = in[i+1];
|
||
|
if (outpos)
|
||
|
{
|
||
|
outpos[i] = i;
|
||
|
outpos[i+1] = i+1;
|
||
|
}
|
||
|
}
|
||
|
else /* (in[i] > in[i+1]) */
|
||
|
{
|
||
|
out[i] = in[i+1];
|
||
|
out[i+1] = in[i];
|
||
|
if (outpos)
|
||
|
{
|
||
|
outpos[i] = i+1;
|
||
|
outpos[i+1] = i;
|
||
|
}
|
||
|
}
|
||
|
} /* for(initial pass) */
|
||
|
|
||
|
/* Mergesort loop: perform the mergesort passes with increasing steps.
|
||
|
* Invariant: out is the (semi-sorted) data, tmp is the scratchspace.
|
||
|
*/
|
||
|
for (step = 2; step < len; step *= 2)
|
||
|
{
|
||
|
size_t start, dest, left;
|
||
|
|
||
|
/* Exchange out and tmp */
|
||
|
{
|
||
|
char *tmp2;
|
||
|
long *tmp2pos;
|
||
|
|
||
|
tmp2 = out; out = tmp; tmp = tmp2;
|
||
|
if (outpos)
|
||
|
{
|
||
|
tmp2pos = outpos; outpos = tmppos; tmppos = tmp2pos;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (start = 0, dest = 0; start <= len; start += 2*step)
|
||
|
{
|
||
|
for ( i = start, j = start+step, left = 2 * step
|
||
|
; left && dest < len
|
||
|
; left--, dest++
|
||
|
)
|
||
|
{
|
||
|
if (i >= start+step
|
||
|
|| i >= len)
|
||
|
{
|
||
|
if (j < len)
|
||
|
{
|
||
|
out[dest] = tmp[j];
|
||
|
if (outpos)
|
||
|
outpos[dest] = tmppos[j];
|
||
|
j++;
|
||
|
}
|
||
|
}
|
||
|
else if (j >= start+2*step
|
||
|
|| j >= len)
|
||
|
{
|
||
|
if (i < len)
|
||
|
{
|
||
|
out[dest] = tmp[i];
|
||
|
if (outpos)
|
||
|
outpos[dest] = tmppos[i];
|
||
|
i++;
|
||
|
}
|
||
|
}
|
||
|
else if (tmp[i] <= tmp[j])
|
||
|
{
|
||
|
out[dest] = tmp[i];
|
||
|
if (outpos)
|
||
|
outpos[dest] = tmppos[i];
|
||
|
i++;
|
||
|
}
|
||
|
else /* (tmp[i] > tmp[i+step]) */
|
||
|
{
|
||
|
out[dest] = tmp[j];
|
||
|
if (outpos)
|
||
|
outpos[dest] = tmppos[j];
|
||
|
j++;
|
||
|
}
|
||
|
} /* for (sort run) */
|
||
|
} /* for (start) */
|
||
|
} /* for(step) */
|
||
|
|
||
|
/* Free the temporary data */
|
||
|
if (tmppos)
|
||
|
xfree(tmppos);
|
||
|
xfree(tmp);
|
||
|
|
||
|
/* Return the result */
|
||
|
if (pos)
|
||
|
*pos = outpos;
|
||
|
|
||
|
return out;
|
||
|
} /* sort_string() */
|
||
|
|
||
|
/*--------------------------------------------------------------------*/
|
||
|
string_t *
|
||
|
intersect_strings (const string_t * p_left, const string_t * p_right, Bool bSubtract)
|
||
|
|
||
|
/* !bSubtract: Intersect string <left> with string <right> and return
|
||
|
* a newly allocated string with all those characters which are in
|
||
|
* both strings.
|
||
|
* bSubtract: Subtract string <right> from string <left> and return
|
||
|
* a newly allocated string with all those characters which are in
|
||
|
* <left> but not in <right>.
|
||
|
* The order of the characters returned is their order of appearance
|
||
|
* in <left>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
size_t len_left, len_right, len_out;
|
||
|
size_t ix_left, ix_right;
|
||
|
long * pos;
|
||
|
CBool * matches;
|
||
|
const char * left_txt;
|
||
|
char * left, * right, * result_txt;
|
||
|
string_t *result;
|
||
|
|
||
|
len_left = mstrsize(p_left);
|
||
|
len_right = mstrsize(p_right);
|
||
|
|
||
|
xallocate(matches, len_left+1, "intersection matches");
|
||
|
/* +1 so that smalloc won't complain when given an empty left string */
|
||
|
|
||
|
for (ix_left = 0; ix_left < len_left; ix_left++)
|
||
|
matches[ix_left] = bSubtract ? MY_TRUE : MY_FALSE;
|
||
|
|
||
|
/* Sort the two strings */
|
||
|
left = sort_string(p_left, len_left, &pos);
|
||
|
right = sort_string(p_right, len_right, NULL);
|
||
|
|
||
|
/* Intersect the two strings by mutual comparison.
|
||
|
* Each non-matched character in left gets is pos[] set to -1.
|
||
|
*/
|
||
|
len_out = bSubtract ? len_left : 0;
|
||
|
for ( ix_left = 0, ix_right = 0
|
||
|
; ix_left < len_left && ix_right < len_right
|
||
|
; )
|
||
|
{
|
||
|
if (left[ix_left] < right[ix_right])
|
||
|
ix_left++;
|
||
|
else if (left[ix_left] > right[ix_right])
|
||
|
ix_right++;
|
||
|
else /* left[ix_left] == right[ix_right]) */
|
||
|
{
|
||
|
if (!bSubtract)
|
||
|
{
|
||
|
matches[pos[ix_left]] = MY_TRUE;
|
||
|
len_out++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
matches[pos[ix_left]] = MY_FALSE;
|
||
|
len_out--;
|
||
|
}
|
||
|
ix_left++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Create the result: copy all flagged characters */
|
||
|
memsafe(result = alloc_mstring(len_out), len_out, "intersection result");
|
||
|
left_txt = get_txt(p_left);
|
||
|
result_txt = get_txt(result);
|
||
|
for (ix_left = 0, ix_right = 0; ix_left < len_left; ix_left++)
|
||
|
if (matches[ix_left])
|
||
|
result_txt[ix_right++] = left_txt[ix_left];
|
||
|
|
||
|
/* Free intermediate results */
|
||
|
xfree(pos);
|
||
|
xfree(matches);
|
||
|
xfree(left);
|
||
|
xfree(right);
|
||
|
|
||
|
return result;
|
||
|
} /* intersect_strings() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
svalue_t *
|
||
|
x_filter_string (svalue_t *sp, int num_arg)
|
||
|
|
||
|
/* EFUN: filter() for strings.
|
||
|
*
|
||
|
* string filter(string arr, string fun, string|object obj, mixed extra, ...)
|
||
|
* string filter(string arr, closure cl, mixed extra, ...)
|
||
|
* string filter(string arr, mapping map)
|
||
|
*
|
||
|
* Filter the elements of <arr> through a filter defined by the other
|
||
|
* arguments, and return an array of those elements, for which the
|
||
|
* filter yields non-zero.
|
||
|
*
|
||
|
* The filter can be a function call:
|
||
|
*
|
||
|
* <obj>-><fun>(elem, <extra>...)
|
||
|
*
|
||
|
* or a mapping query:
|
||
|
*
|
||
|
* <map>[elem]
|
||
|
*
|
||
|
* <obj> can both be an object reference or a filename. If omitted,
|
||
|
* this_object() is used (this also works if the third argument is
|
||
|
* neither a string nor an object).
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
string_t *rc; /* Result string */
|
||
|
string_t *str; /* Argument string */
|
||
|
svalue_t *arg; /* First argument the vm stack */
|
||
|
mp_int slen; /* Argument string length */
|
||
|
char *src, *dest; /* String text work pointers */
|
||
|
|
||
|
char *flags; /* Flag array, one flag for each element of <str>
|
||
|
* (in reverse order). */
|
||
|
mp_int res; /* Number of surviving elements */
|
||
|
|
||
|
res = 0;
|
||
|
|
||
|
/* Locate the args on the stack, extract the string to filter
|
||
|
* and allocate the flags vector.
|
||
|
*/
|
||
|
arg = sp - num_arg + 1;
|
||
|
|
||
|
str = arg->u.str;
|
||
|
slen = (mp_int)mstrsize(str);
|
||
|
|
||
|
flags = alloca((size_t)slen+1);
|
||
|
if (!flags)
|
||
|
{
|
||
|
errorf("Stack overflow in filter()");
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
/* Every element in flags is associated by index number with an
|
||
|
* element in the vector to filter. The filter function is evaluated
|
||
|
* for every string character, and the associated flag is set to 0
|
||
|
* or 1 according to the result.
|
||
|
* At the end, all 1-flagged elements are gathered and copied
|
||
|
* into the result string.
|
||
|
*/
|
||
|
|
||
|
if (arg[1].type == T_MAPPING)
|
||
|
{
|
||
|
mp_int cnt;
|
||
|
|
||
|
/* --- Filter by mapping query --- */
|
||
|
mapping_t *m;
|
||
|
|
||
|
if (num_arg > 2) {
|
||
|
inter_sp = sp;
|
||
|
errorf("Too many arguments to filter(array)\n");
|
||
|
}
|
||
|
m = arg[1].u.map;
|
||
|
|
||
|
for (src = get_txt(str), cnt = slen; --cnt >= 0; src++)
|
||
|
{
|
||
|
svalue_t key;
|
||
|
|
||
|
put_number(&key, *src);
|
||
|
if (get_map_value(m, &key) == &const0)
|
||
|
{
|
||
|
flags[cnt] = 0;
|
||
|
continue;
|
||
|
}
|
||
|
flags[cnt] = 1;
|
||
|
res++;
|
||
|
}
|
||
|
|
||
|
free_svalue(arg+1); /* the mapping */
|
||
|
sp = arg;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
/* --- Filter by function call --- */
|
||
|
|
||
|
int error_index;
|
||
|
callback_t cb;
|
||
|
mp_int cnt;
|
||
|
|
||
|
assign_eval_cost();
|
||
|
inter_sp = sp;
|
||
|
|
||
|
error_index = setup_efun_callback(&cb, arg+1, num_arg-1);
|
||
|
|
||
|
if (error_index >= 0)
|
||
|
{
|
||
|
vefun_bad_arg(error_index+2, arg);
|
||
|
/* NOTREACHED */
|
||
|
return arg;
|
||
|
}
|
||
|
inter_sp = sp = arg+1;
|
||
|
put_callback(sp, &cb);
|
||
|
|
||
|
/* Loop over all elements in p and call the filter.
|
||
|
* w is the current element filtered.
|
||
|
*/
|
||
|
for (src = get_txt(str), cnt = slen; --cnt >= 0; src++)
|
||
|
{
|
||
|
svalue_t *v;
|
||
|
|
||
|
flags[cnt] = 0;
|
||
|
|
||
|
if (current_object->flags & O_DESTRUCTED)
|
||
|
continue;
|
||
|
/* Don't call the filter anymore, but fill the
|
||
|
* flags array with 0es.
|
||
|
*/
|
||
|
|
||
|
if (!callback_object(&cb))
|
||
|
{
|
||
|
inter_sp = sp;
|
||
|
errorf("object used by filter(array) destructed");
|
||
|
}
|
||
|
|
||
|
push_number(inter_sp, *src);
|
||
|
|
||
|
v = apply_callback(&cb, 1);
|
||
|
if (!v || (v->type == T_NUMBER && !v->u.number) )
|
||
|
continue;
|
||
|
|
||
|
flags[cnt] = 1;
|
||
|
res++;
|
||
|
}
|
||
|
|
||
|
free_callback(&cb);
|
||
|
}
|
||
|
|
||
|
/* flags[] holds the filter results, res is the number of
|
||
|
* elements to keep. Now create the result vector.
|
||
|
*/
|
||
|
rc = alloc_mstring(res);
|
||
|
if (rc)
|
||
|
{
|
||
|
for (src = get_txt(str), dest = get_txt(rc), flags = &flags[slen]
|
||
|
; res > 0 ; src++)
|
||
|
{
|
||
|
if (*--flags)
|
||
|
{
|
||
|
*dest++ = *src;
|
||
|
res--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Cleanup (everything but the string has been removed already) */
|
||
|
free_mstring(str);
|
||
|
arg->u.str = rc;
|
||
|
|
||
|
return arg;
|
||
|
} /* x_filter_string() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
svalue_t *
|
||
|
x_map_string (svalue_t *sp, int num_arg)
|
||
|
|
||
|
/* EFUN map() for strings
|
||
|
*
|
||
|
* string map(string arg, string func, string|object ob, mixed extra...)
|
||
|
* string map(string arg, closure cl, mixed extra...)
|
||
|
* string map(string arg, mapping m)
|
||
|
*
|
||
|
* Call the function <ob>-><func>() resp. the closure <cl> for
|
||
|
* every element of the array/struct/mapping/string <arg>, and return a result
|
||
|
* made up from the returned values.
|
||
|
*
|
||
|
* It is also possible to map every entry through a lookup <m>[element]. If
|
||
|
* the mapping entry doesn't exist, the original value is kept, otherwise the
|
||
|
* result of the mapping lookup.
|
||
|
*
|
||
|
* Since <arg> is a string, only integer return values are allowed, of which
|
||
|
* only the lower 8 bits are considered.
|
||
|
*
|
||
|
* If <ob> is omitted, or neither an object nor a string, then
|
||
|
* this_object() is used.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
string_t *res;
|
||
|
string_t *str;
|
||
|
svalue_t *arg;
|
||
|
mp_int len;
|
||
|
char *src, *dest;
|
||
|
|
||
|
inter_sp = sp;
|
||
|
|
||
|
arg = sp - num_arg + 1;
|
||
|
|
||
|
str = arg->u.str;
|
||
|
len = (mp_int)mstrsize(str);
|
||
|
|
||
|
if (arg[1].type == T_MAPPING)
|
||
|
{
|
||
|
/* --- Map through mapping --- */
|
||
|
|
||
|
mapping_t *m;
|
||
|
|
||
|
if (num_arg > 2) {
|
||
|
inter_sp = sp;
|
||
|
errorf("Too many arguments to map(string)\n");
|
||
|
}
|
||
|
m = arg[1].u.map;
|
||
|
|
||
|
res = alloc_mstring(len);
|
||
|
if (!res)
|
||
|
errorf("(map_string) Out of memory: string[%ld] for result\n", len);
|
||
|
push_string(inter_sp, res); /* In case of errors */
|
||
|
|
||
|
for (src = get_txt(str), dest = get_txt(res); --len >= 0; src++, dest++)
|
||
|
{
|
||
|
svalue_t key, *v;
|
||
|
|
||
|
put_number(&key, *src);
|
||
|
v = get_map_value(m, &key);
|
||
|
if (v == &const0)
|
||
|
*dest = *src;
|
||
|
else
|
||
|
{
|
||
|
if (v->type != T_NUMBER)
|
||
|
{
|
||
|
errorf("(map_string) Illegal value: %s, expected string\n"
|
||
|
, typename(v->type)
|
||
|
);
|
||
|
}
|
||
|
*dest = (v->u.number & 0xFF);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free_svalue(arg+1); /* the mapping */
|
||
|
sp = arg;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* --- Map through function call --- */
|
||
|
|
||
|
callback_t cb;
|
||
|
int error_index;
|
||
|
|
||
|
error_index = setup_efun_callback(&cb, arg+1, num_arg-1);
|
||
|
if (error_index >= 0)
|
||
|
{
|
||
|
vefun_bad_arg(error_index+2, arg);
|
||
|
/* NOTREACHED */
|
||
|
return arg;
|
||
|
}
|
||
|
inter_sp = sp = arg+1;
|
||
|
put_callback(sp, &cb);
|
||
|
num_arg = 2;
|
||
|
|
||
|
res = alloc_mstring(len);
|
||
|
if (!res)
|
||
|
errorf("(map_string) Out of memory: string[%ld] for result\n", len);
|
||
|
push_string(inter_sp, res); /* In case of errors */
|
||
|
|
||
|
for (src = get_txt(str), dest = get_txt(res); --len >= 0; src++, dest++)
|
||
|
{
|
||
|
svalue_t *v;
|
||
|
|
||
|
if (current_object->flags & O_DESTRUCTED)
|
||
|
continue;
|
||
|
|
||
|
if (!callback_object(&cb))
|
||
|
errorf("object used by map(string) destructed");
|
||
|
|
||
|
push_number(inter_sp, *src);
|
||
|
|
||
|
v = apply_callback(&cb, 1);
|
||
|
|
||
|
if (v)
|
||
|
{
|
||
|
if (v->type != T_NUMBER)
|
||
|
{
|
||
|
errorf("(map_string) Illegal value: %s, expected string\n"
|
||
|
, typename(v->type)
|
||
|
);
|
||
|
}
|
||
|
*dest = (v->u.number & 0xFF);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free_callback(&cb);
|
||
|
}
|
||
|
|
||
|
/* The arguments have been removed already, now just replace
|
||
|
* the arr on the stack with the result.
|
||
|
*/
|
||
|
free_mstring(str);
|
||
|
arg->u.str = res; /* Keep svalue type: T_POINTER */
|
||
|
|
||
|
return arg;
|
||
|
} /* x_map_string () */
|
||
|
|
||
|
#ifdef HAS_JSON
|
||
|
/* support for javascript object notation
|
||
|
* depends on the json-c library
|
||
|
* see http://www.json.org for more information
|
||
|
*/
|
||
|
#include <json/json.h>
|
||
|
#include "array.h"
|
||
|
|
||
|
svalue_t *
|
||
|
ldmud_json_inner_parse (svalue_t *sp, struct json_object *val)
|
||
|
{
|
||
|
if (is_error(val)) {
|
||
|
errorf("json_inner_parse: error");
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
|
||
|
}
|
||
|
if (val == NULL) {
|
||
|
/* TODO: I (fippo) am not sure, if this is a really good idea... */
|
||
|
put_number(sp, 0);
|
||
|
return sp;
|
||
|
}
|
||
|
switch(json_object_get_type(val)) {
|
||
|
case json_type_null:
|
||
|
put_number(sp, 0);
|
||
|
break;
|
||
|
case json_type_boolean:
|
||
|
put_number(sp, json_object_get_boolean(val));
|
||
|
break;
|
||
|
case json_type_double:
|
||
|
put_float(sp, json_object_get_double(val));
|
||
|
break;
|
||
|
case json_type_int:
|
||
|
put_number(sp, json_object_get_int(val));
|
||
|
break;
|
||
|
case json_type_string:
|
||
|
put_c_string(sp, json_object_get_string(val));
|
||
|
break;
|
||
|
case json_type_object:
|
||
|
{
|
||
|
mapping_t *m;
|
||
|
struct lh_entry *e;
|
||
|
char *key;
|
||
|
struct json_object *newval;
|
||
|
|
||
|
m = allocate_mapping(json_object_get_object(val)->count, 1);
|
||
|
for (e = json_object_get_object(val)->head; e ? (key = (char*)e->k, newval = (struct json_object *)e->v, e) : 0; e = e->next) {
|
||
|
svalue_t mkey, *mval;
|
||
|
put_c_string(&mkey, key);
|
||
|
mval = get_map_lvalue(m, &mkey);
|
||
|
free_svalue(&mkey);
|
||
|
ldmud_json_inner_parse(mval, newval);
|
||
|
}
|
||
|
put_mapping(sp, m);
|
||
|
break;
|
||
|
}
|
||
|
case json_type_array:
|
||
|
{
|
||
|
vector_t *v;
|
||
|
struct array_list *a;
|
||
|
int size, i;
|
||
|
size = json_object_array_length(val);
|
||
|
v = allocate_array(size);
|
||
|
a = json_object_get_array(val);
|
||
|
for (i = 0; i < size; i++) {
|
||
|
ldmud_json_inner_parse(&(v->item[i]), array_list_get_idx(a, i));
|
||
|
}
|
||
|
put_array(sp, v);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
svalue_t *
|
||
|
f_json_parse (svalue_t *sp) {
|
||
|
struct json_object *parsed;
|
||
|
|
||
|
parsed = json_tokener_parse(get_txt(sp->u.str));
|
||
|
free_svalue(sp);
|
||
|
ldmud_json_inner_parse (sp, parsed);
|
||
|
/* TODO: free the object */
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
struct json_object *
|
||
|
ldmud_json_inner_serialize (svalue_t *sp) {
|
||
|
struct json_object *val;
|
||
|
switch(sp->type) {
|
||
|
case T_NUMBER:
|
||
|
val = json_object_new_int(sp->u.number);
|
||
|
break;
|
||
|
case T_STRING:
|
||
|
val = json_object_new_string(get_txt(sp->u.str));
|
||
|
break;
|
||
|
case T_POINTER:
|
||
|
{
|
||
|
int i;
|
||
|
val = json_object_new_array();
|
||
|
for (i = VEC_SIZE(sp->u.vec) - 1; i >= 0; i--)
|
||
|
json_object_array_put_idx(val, i,
|
||
|
ldmud_json_inner_serialize(&sp->u.vec->item[i]));
|
||
|
break;
|
||
|
}
|
||
|
case T_MAPPING:
|
||
|
{
|
||
|
int i;
|
||
|
val = json_object_new_object();
|
||
|
for (i = 0; i < MAP_SIZE(sp->u.map); i++)
|
||
|
walk_mapping(sp->u.map, &ldmud_json_walker, val);
|
||
|
break;
|
||
|
}
|
||
|
case T_FLOAT:
|
||
|
val = json_object_new_double(READ_DOUBLE(sp));
|
||
|
break;
|
||
|
default: /* those are unimplemented */
|
||
|
val = json_object_new_object();
|
||
|
break;
|
||
|
}
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
void ldmud_json_walker(svalue_t *key, svalue_t *val, void *parent)
|
||
|
{
|
||
|
struct json_object *obj = (struct json_object *)parent;
|
||
|
if (key->type != T_STRING)
|
||
|
errorf("json only serializes string keys\n");
|
||
|
/* NOTREACHED */
|
||
|
json_object_object_add(obj, get_txt(key->u.str),
|
||
|
ldmud_json_inner_serialize(val));
|
||
|
}
|
||
|
|
||
|
|
||
|
svalue_t *
|
||
|
f_json_serialize (svalue_t *sp) {
|
||
|
struct json_object *val;
|
||
|
val = ldmud_json_inner_serialize(sp);
|
||
|
free_svalue(sp);
|
||
|
put_c_string(sp, json_object_to_json_string(val));
|
||
|
return sp;
|
||
|
}
|
||
|
#endif /* HAS_JSON */
|
||
|
/*====================================================================*/
|
||
|
|