#include <string.h>
#include "svn_string.h"
#include "svn_error.h"
#include "private/svn_skel.h"
enum char_type {
type_nothing = 0,
type_space = 1,
type_digit = 2,
type_paren = 3,
type_name = 4
};
static const enum char_type skel_char_type[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 0, 3, 0, 0,
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static apr_size_t
getsize(const char *data, apr_size_t len,
const char **endptr, apr_size_t max)
{
apr_size_t max_prefix = max / 10;
apr_size_t max_digit = max % 10;
apr_size_t i;
apr_size_t value = 0;
for (i = 0; i < len && '0' <= data[i] && data[i] <= '9'; i++)
{
apr_size_t digit = data[i] - '0';
if (value > max_prefix
|| (value == max_prefix && digit > max_digit))
{
*endptr = 0;
return 0;
}
value = (value * 10) + digit;
}
if (i == 0)
{
*endptr = 0;
return 0;
}
else
{
*endptr = data + i;
return value;
}
}
static int
putsize(char *data, apr_size_t len, apr_size_t value)
{
apr_size_t i = 0;
do
{
if (i >= len)
return 0;
data[i] = (value % 10) + '0';
value /= 10;
i++;
}
while (value > 0);
{
int left, right;
for (left = 0, right = i-1; left < right; left++, right--)
{
char t = data[left];
data[left] = data[right];
data[right] = t;
}
}
return i;
}
static svn_error_t *
skel_err(const char *skel_type)
{
return svn_error_createf(SVN_ERR_FS_MALFORMED_SKEL, NULL,
"Malformed%s%s skeleton",
skel_type ? " " : "",
skel_type ? skel_type : "");
}
static svn_boolean_t
is_valid_proplist_skel(svn_skel_t *skel)
{
int len = svn_skel__list_length(skel);
if ((len >= 0) && (len & 1) == 0)
{
svn_skel_t *elt;
for (elt = skel->children; elt; elt = elt->next)
if (! elt->is_atom)
return FALSE;
return TRUE;
}
return FALSE;
}
static svn_skel_t *parse(const char *data, apr_size_t len,
apr_pool_t *pool);
static svn_skel_t *list(const char *data, apr_size_t len,
apr_pool_t *pool);
static svn_skel_t *implicit_atom(const char *data, apr_size_t len,
apr_pool_t *pool);
static svn_skel_t *explicit_atom(const char *data, apr_size_t len,
apr_pool_t *pool);
svn_skel_t *
svn_skel__parse(const char *data,
apr_size_t len,
apr_pool_t *pool)
{
return parse(data, len, pool);
}
static svn_skel_t *
parse(const char *data,
apr_size_t len,
apr_pool_t *pool)
{
char c;
if (len <= 0)
return NULL;
c = *data;
if (c == '(')
return list(data, len, pool);
if (skel_char_type[(unsigned char) c] == type_name)
return implicit_atom(data, len, pool);
else
return explicit_atom(data, len, pool);
}
static svn_skel_t *
list(const char *data,
apr_size_t len,
apr_pool_t *pool)
{
const char *end = data + len;
const char *list_start;
if (data >= end || *data != '(')
return NULL;
list_start = data;
data++;
{
svn_skel_t *children = NULL;
svn_skel_t **tail = &children;
for (;;)
{
svn_skel_t *element;
while (data < end
&& skel_char_type[(unsigned char) *data] == type_space)
data++;
if (data >= end)
return NULL;
if (*data == ')')
{
data++;
break;
}
element = parse(data, end - data, pool);
if (! element)
return NULL;
element->next = NULL;
*tail = element;
tail = &element->next;
data = element->data + element->len;
}
{
svn_skel_t *s = apr_pcalloc(pool, sizeof(*s));
s->is_atom = FALSE;
s->data = list_start;
s->len = data - list_start;
s->children = children;
return s;
}
}
}
static svn_skel_t *
implicit_atom(const char *data,
apr_size_t len,
apr_pool_t *pool)
{
const char *start = data;
const char *end = data + len;
svn_skel_t *s;
if (data >= end || skel_char_type[(unsigned char) *data] != type_name)
return NULL;
while (++data < end
&& skel_char_type[(unsigned char) *data] != type_space
&& skel_char_type[(unsigned char) *data] != type_paren)
;
s = apr_pcalloc(pool, sizeof(*s));
s->is_atom = TRUE;
s->data = start;
s->len = data - start;
return s;
}
static svn_skel_t *
explicit_atom(const char *data,
apr_size_t len,
apr_pool_t *pool)
{
const char *end = data + len;
const char *next;
apr_size_t size;
svn_skel_t *s;
size = getsize(data, end - data, &next, end - data);
data = next;
if (! data)
return NULL;
if (data >= end || skel_char_type[(unsigned char) *data] != type_space)
return NULL;
data++;
if (data + size > end)
return NULL;
s = apr_pcalloc(pool, sizeof(*s));
s->is_atom = TRUE;
s->data = data;
s->len = size;
return s;
}
static apr_size_t estimate_unparsed_size(const svn_skel_t *skel);
static svn_stringbuf_t *unparse(const svn_skel_t *skel,
svn_stringbuf_t *str,
apr_pool_t *pool);
svn_stringbuf_t *
svn_skel__unparse(const svn_skel_t *skel, apr_pool_t *pool)
{
svn_stringbuf_t *str;
str = apr_palloc(pool, sizeof(*str));
str->pool = pool;
str->blocksize = estimate_unparsed_size(skel) + 200;
str->data = apr_palloc(pool, str->blocksize);
str->len = 0;
return unparse(skel, str, pool);
}
static apr_size_t
estimate_unparsed_size(const svn_skel_t *skel)
{
if (skel->is_atom)
{
if (skel->len < 100)
return skel->len + 3;
else
return skel->len + 30;
}
else
{
int total_len;
svn_skel_t *child;
total_len = 2;
for (child = skel->children; child; child = child->next)
total_len += estimate_unparsed_size(child) + 1;
return total_len;
}
}
static svn_boolean_t
use_implicit(const svn_skel_t *skel)
{
if (skel->len == 0
|| skel->len >= 100)
return FALSE;
if (skel_char_type[(unsigned char) skel->data[0]] != type_name)
return FALSE;
{
apr_size_t i;
for (i = 1; i < skel->len; i++)
if (skel_char_type[(unsigned char) skel->data[i]] == type_space
|| skel_char_type[(unsigned char) skel->data[i]] == type_paren)
return FALSE;
}
return TRUE;
}
static svn_stringbuf_t *
unparse(const svn_skel_t *skel, svn_stringbuf_t *str, apr_pool_t *pool)
{
if (skel->is_atom)
{
if (use_implicit(skel))
svn_stringbuf_appendbytes(str, skel->data, skel->len);
else
{
char buf[200];
int length_len;
length_len = putsize(buf, sizeof(buf), skel->len);
SVN_ERR_ASSERT_NO_RETURN(length_len > 0);
svn_stringbuf_ensure(str, str->len + length_len + 1 + skel->len);
svn_stringbuf_appendbytes(str, buf, length_len);
str->data[str->len++] = ' ';
svn_stringbuf_appendbytes(str, skel->data, skel->len);
}
}
else
{
svn_skel_t *child;
svn_stringbuf_ensure(str, str->len + 1);
str->data[str->len++] = '(';
for (child = skel->children; child; child = child->next)
{
unparse(child, str, pool);
if (child->next)
{
svn_stringbuf_ensure(str, str->len + 1);
str->data[str->len++] = ' ';
}
}
svn_stringbuf_appendbytes(str, ")", 1);
}
return str;
}
svn_skel_t *
svn_skel__str_atom(const char *str, apr_pool_t *pool)
{
svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
skel->is_atom = TRUE;
skel->data = str;
skel->len = strlen(str);
return skel;
}
svn_skel_t *
svn_skel__mem_atom(const void *addr,
apr_size_t len,
apr_pool_t *pool)
{
svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
skel->is_atom = TRUE;
skel->data = addr;
skel->len = len;
return skel;
}
svn_skel_t *
svn_skel__make_empty_list(apr_pool_t *pool)
{
svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
return skel;
}
void
svn_skel__prepend(svn_skel_t *skel, svn_skel_t *list_skel)
{
SVN_ERR_ASSERT_NO_RETURN(! list_skel->is_atom);
skel->next = list_skel->children;
list_skel->children = skel;
}
svn_boolean_t
svn_skel__matches_atom(const svn_skel_t *skel, const char *str)
{
if (skel && skel->is_atom)
{
apr_size_t len = strlen(str);
return (skel->len == len
&& ! memcmp(skel->data, str, len));
}
return FALSE;
}
int
svn_skel__list_length(const svn_skel_t *skel)
{
int len = 0;
const svn_skel_t *child;
if ((! skel) || skel->is_atom)
return -1;
for (child = skel->children; child; child = child->next)
len++;
return len;
}
svn_error_t *
svn_skel__parse_proplist(apr_hash_t **proplist_p,
svn_skel_t *skel,
apr_pool_t *pool)
{
apr_hash_t *proplist = NULL;
svn_skel_t *elt;
if (! is_valid_proplist_skel(skel))
return skel_err("proplist");
if (skel->children)
proplist = apr_hash_make(pool);
for (elt = skel->children; elt; elt = elt->next->next)
{
svn_string_t *value = svn_string_ncreate(elt->next->data,
elt->next->len, pool);
apr_hash_set(proplist,
apr_pstrmemdup(pool, elt->data, elt->len),
elt->len,
value);
}
*proplist_p = proplist;
return SVN_NO_ERROR;
}
svn_error_t *
svn_skel__unparse_proplist(svn_skel_t **skel_p,
apr_hash_t *proplist,
apr_pool_t *pool)
{
svn_skel_t *skel = svn_skel__make_empty_list(pool);
apr_hash_index_t *hi;
if (proplist)
{
for (hi = apr_hash_first(pool, proplist); hi; hi = apr_hash_next(hi))
{
const void *key;
void *val;
apr_ssize_t klen;
svn_string_t *value;
apr_hash_this(hi, &key, &klen, &val);
value = val;
svn_skel__prepend(svn_skel__mem_atom(value->data, value->len, pool),
skel);
svn_skel__prepend(svn_skel__mem_atom(key, klen, pool), skel);
}
}
if (! is_valid_proplist_skel(skel))
return skel_err("proplist");
*skel_p = skel;
return SVN_NO_ERROR;
}