Home | History | Annotate | Line # | Download | only in citrus
      1 /* $NetBSD: citrus_prop.c,v 1.6 2022/04/19 20:32:14 rillig Exp $ */
      2 
      3 /*-
      4  * Copyright (c)2006 Citrus Project,
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  *
     28  */
     29 
     30 #include <sys/cdefs.h>
     31 #if defined(LIBC_SCCS) && !defined(lint)
     32 __RCSID("$NetBSD: citrus_prop.c,v 1.6 2022/04/19 20:32:14 rillig Exp $");
     33 #endif /* LIBC_SCCS and not lint */
     34 
     35 #include <assert.h>
     36 #include <errno.h>
     37 #include <limits.h>
     38 #include <stdbool.h>
     39 #include <stddef.h>
     40 #include <stdio.h>
     41 #include <stdint.h>
     42 #include <stdlib.h>
     43 #include <string.h>
     44 
     45 #include "citrus_namespace.h"
     46 #include "citrus_bcs.h"
     47 #include "citrus_region.h"
     48 #include "citrus_memstream.h"
     49 #include "citrus_prop.h"
     50 
     51 typedef struct {
     52 	_citrus_prop_type_t type;
     53 	union {
     54 		const char *str;
     55 		int chr;
     56 		bool boolean;
     57 		uint64_t num;
     58 	} u;
     59 } _citrus_prop_object_t;
     60 
     61 static __inline void
     62 _citrus_prop_object_init(_citrus_prop_object_t *obj, _citrus_prop_type_t type)
     63 {
     64 	_DIAGASSERT(obj != NULL);
     65 
     66 	obj->type = type;
     67 	memset(&obj->u, 0, sizeof(obj->u));
     68 }
     69 
     70 static __inline void
     71 _citrus_prop_object_uninit(_citrus_prop_object_t *obj)
     72 {
     73 	_DIAGASSERT(obj != NULL);
     74 
     75 	if (obj->type == _CITRUS_PROP_STR)
     76 		free(__UNCONST(obj->u.str));
     77 }
     78 
     79 static const char *xdigit = "0123456789ABCDEF";
     80 
     81 #define _CITRUS_PROP_READ_UINT_COMMON(_func_, _type_, _max_)		\
     82 static int								\
     83 _citrus_prop_read_##_func_##_common(struct _memstream * __restrict ms,	\
     84 	_type_ * __restrict result, int base)				\
     85 {									\
     86 	_type_ acc, cutoff;						\
     87 	int n, ch, cutlim;						\
     88 	char *p;							\
     89 									\
     90 	_DIAGASSERT(ms != NULL);					\
     91 	_DIAGASSERT(result != NULL);					\
     92 									\
     93 	acc = (_type_)0;						\
     94 	cutoff = _max_ / base;						\
     95 	cutlim = _max_ % base;						\
     96 	for (;;) {							\
     97 		ch = _memstream_getc(ms);				\
     98 		p = strchr(xdigit, _bcs_toupper(ch));			\
     99 		if (p == NULL || (n = (p - xdigit)) >= base)		\
    100 			break;						\
    101 		if (acc > cutoff || (acc == cutoff && n > cutlim))	\
    102 			break;						\
    103 		acc *= base;						\
    104 		acc += n;						\
    105 	}								\
    106 	_memstream_ungetc(ms, ch);					\
    107 	*result = acc;							\
    108 	return 0;							\
    109 }
    110 _CITRUS_PROP_READ_UINT_COMMON(chr, int, UCHAR_MAX)
    111 _CITRUS_PROP_READ_UINT_COMMON(num, uint64_t, UINT64_MAX)
    112 #undef _CITRUS_PROP_READ_UINT_COMMON
    113 
    114 #define _CITRUS_PROP_READ_INT(_func_, _type_)			\
    115 static int							\
    116 _citrus_prop_read_##_func_(struct _memstream * __restrict ms,	\
    117 	_citrus_prop_object_t * __restrict obj)			\
    118 {								\
    119 	int ch, neg, base;					\
    120 								\
    121 	_DIAGASSERT(ms != NULL);				\
    122 	_DIAGASSERT(obj != NULL);				\
    123 								\
    124 	_memstream_skip_ws(ms);					\
    125 	ch = _memstream_getc(ms);				\
    126 	neg = 0;						\
    127 	switch (ch) {						\
    128 	case '-':						\
    129 		neg = 1;					\
    130 	case '+':						\
    131 		ch = _memstream_getc(ms);			\
    132 	}							\
    133 	base = 10;						\
    134 	if (ch == '0') {					\
    135 		base -= 2;					\
    136 		ch = _memstream_getc(ms);			\
    137 		if (ch == 'x' || ch == 'X') {			\
    138 			ch = _memstream_getc(ms);		\
    139 			if (_bcs_isxdigit(ch) == 0) {		\
    140 				_memstream_ungetc(ms, ch);	\
    141 				obj->u._func_ = 0;		\
    142 				return 0;			\
    143 			}					\
    144 			base += 8;				\
    145 		}						\
    146 	} else if (_bcs_isdigit(ch) == 0)			\
    147 		return EINVAL;					\
    148 	_memstream_ungetc(ms, ch);				\
    149 	return _citrus_prop_read_##_func_##_common		\
    150 	    (ms, &obj->u._func_, base);				\
    151 }
    152 _CITRUS_PROP_READ_INT(chr, int)
    153 _CITRUS_PROP_READ_INT(num, uint64_t)
    154 #undef _CITRUS_PROP_READ_INT
    155 
    156 static int
    157 _citrus_prop_read_character_common(struct _memstream * __restrict ms,
    158 	int * __restrict result)
    159 {
    160 	int ch, base;
    161 
    162 	_DIAGASSERT(ms != NULL);
    163 	_DIAGASSERT(result != NULL);
    164 
    165 	ch = _memstream_getc(ms);
    166 	if (ch != '\\') {
    167 		*result = ch;
    168 	} else {
    169 		ch = _memstream_getc(ms);
    170 		base = 16;
    171 		switch (ch) {
    172 		case 'a': *result = '\a'; break;
    173 		case 'b': *result = '\b'; break;
    174 		case 'f': *result = '\f'; break;
    175 		case 'n': *result = '\n'; break;
    176 		case 'r': *result = '\r'; break;
    177 		case 't': *result = '\t'; break;
    178 		case 'v': *result = '\v'; break;
    179 		/*FALLTHROUGH*/
    180 		case '0': case '1': case '2': case '3':
    181 		case '4': case '5': case '6': case '7':
    182 			_memstream_ungetc(ms, ch);
    183 			base -= 8;
    184 		case 'x':
    185 			return _citrus_prop_read_chr_common(ms, result, base);
    186 
    187 		default:
    188 			/* unknown escape */
    189 			*result = ch;
    190 		}
    191 	}
    192 	return 0;
    193 }
    194 
    195 static int
    196 _citrus_prop_read_character(struct _memstream * __restrict ms,
    197 	_citrus_prop_object_t * __restrict obj)
    198 {
    199 	int ch, errnum;
    200 
    201 	_DIAGASSERT(ms != NULL);
    202 	_DIAGASSERT(obj != NULL);
    203 
    204 	_memstream_skip_ws(ms);
    205 	ch = _memstream_getc(ms);
    206 	if (ch != '\'') {
    207 		_memstream_ungetc(ms, ch);
    208 		return _citrus_prop_read_chr(ms, obj);
    209 	}
    210 	errnum = _citrus_prop_read_character_common(ms, &ch);
    211 	if (errnum != 0)
    212 		return errnum;
    213 	obj->u.chr = ch;
    214 	ch = _memstream_getc(ms);
    215 	if (ch != '\'')
    216 		return EINVAL;
    217 	return 0;
    218 }
    219 
    220 static int
    221 _citrus_prop_read_bool(struct _memstream * __restrict ms,
    222 	_citrus_prop_object_t * __restrict obj)
    223 {
    224 	_DIAGASSERT(ms != NULL);
    225 	_DIAGASSERT(obj != NULL);
    226 
    227 	_memstream_skip_ws(ms);
    228 	switch (_bcs_tolower(_memstream_getc(ms))) {
    229 	case 't':
    230 		if (_bcs_tolower(_memstream_getc(ms)) == 'r' &&
    231 		    _bcs_tolower(_memstream_getc(ms)) == 'u' &&
    232 		    _bcs_tolower(_memstream_getc(ms)) == 'e') {
    233 			obj->u.boolean = true;
    234 			return 0;
    235 		}
    236 		break;
    237 	case 'f':
    238 		if (_bcs_tolower(_memstream_getc(ms)) == 'a' &&
    239 		    _bcs_tolower(_memstream_getc(ms)) == 'l' &&
    240 		    _bcs_tolower(_memstream_getc(ms)) == 's' &&
    241 		    _bcs_tolower(_memstream_getc(ms)) == 'e') {
    242 			obj->u.boolean = false;
    243 			return 0;
    244 		}
    245 	}
    246 	return EINVAL;
    247 }
    248 
    249 static int
    250 _citrus_prop_read_str(struct _memstream * __restrict ms,
    251 	_citrus_prop_object_t * __restrict obj)
    252 {
    253 	int errnum, quot, ch;
    254 	char *s, *t;
    255 #define _CITRUS_PROP_STR_BUFSIZ	512
    256 	size_t n, m;
    257 
    258 	_DIAGASSERT(ms != NULL);
    259 	_DIAGASSERT(obj != NULL);
    260 
    261 	m = _CITRUS_PROP_STR_BUFSIZ;
    262 	s = malloc(m);
    263 	if (s == NULL)
    264 		return ENOMEM;
    265 	n = 0;
    266 	_memstream_skip_ws(ms);
    267 	quot = _memstream_getc(ms);
    268 	switch (quot) {
    269 	case EOF:
    270 		goto done;
    271 	case '\\':
    272 		_memstream_ungetc(ms, quot);
    273 		quot = EOF;
    274 	/*FALLTHROUGH*/
    275 	case '\"': case '\'':
    276 		break;
    277 	default:
    278 		s[n] = quot;
    279 		++n, --m;
    280 		quot = EOF;
    281 	}
    282 	for (;;) {
    283 		if (m < 1) {
    284 			m = _CITRUS_PROP_STR_BUFSIZ;
    285 			t = realloc(s, n + m);
    286 			if (t == NULL) {
    287 				free(s);
    288 				return ENOMEM;
    289 			}
    290 			s = t;
    291 		}
    292 		ch = _memstream_getc(ms);
    293 		if (quot == ch || (quot == EOF &&
    294 		    (ch == ';' || _bcs_isspace(ch)))) {
    295 done:
    296 			s[n] = '\0';
    297 			obj->u.str = (const char *)s;
    298 			return 0;
    299 		}
    300 		_memstream_ungetc(ms, ch);
    301 		errnum = _citrus_prop_read_character_common(ms, &ch);
    302 		if (errnum != 0)
    303 			return errnum;
    304 		s[n] = ch;
    305 		++n, --m;
    306 	}
    307 	free(s);
    308 	return EINVAL;
    309 #undef _CITRUS_PROP_STR_BUFSIZ
    310 }
    311 
    312 typedef int (*_citrus_prop_read_type_t)(struct _memstream * __restrict,
    313 	_citrus_prop_object_t * __restrict);
    314 
    315 static const _citrus_prop_read_type_t readers[] = {
    316 	_citrus_prop_read_bool,
    317 	_citrus_prop_read_str,
    318 	_citrus_prop_read_character,
    319 	_citrus_prop_read_num,
    320 };
    321 
    322 static __inline int
    323 _citrus_prop_read_symbol(struct _memstream * __restrict ms,
    324 	char * __restrict s, size_t n)
    325 {
    326 	int ch;
    327 	size_t m;
    328 
    329 	_DIAGASSERT(ms != NULL);
    330 	_DIAGASSERT(s != NULL);
    331 	_DIAGASSERT(n > 0);
    332 
    333 	for (m = 0; m < n; ++m) {
    334 		ch = _memstream_getc(ms);
    335 		if (ch != '_' && _bcs_isalnum(ch) == 0)
    336 			goto name_found;
    337 		s[m] = ch;
    338 	}
    339 	ch = _memstream_getc(ms);
    340 	if (ch == '_' || _bcs_isalnum(ch) != 0)
    341 		return EINVAL;
    342 
    343 name_found:
    344 	_memstream_ungetc(ms, ch);
    345 	s[m] = '\0';
    346 
    347 	return 0;
    348 }
    349 
    350 static int
    351 _citrus_prop_parse_element(struct _memstream * __restrict ms,
    352 	const _citrus_prop_hint_t * __restrict hints,
    353 	void * __restrict context)
    354 {
    355 	int ch, errnum;
    356 #define _CITRUS_PROP_HINT_NAME_LEN_MAX	255
    357 	char name[_CITRUS_PROP_HINT_NAME_LEN_MAX + 1];
    358 	const _citrus_prop_hint_t *hint;
    359 	_citrus_prop_object_t ostart, oend;
    360 
    361 	_DIAGASSERT(ms != NULL);
    362 	_DIAGASSERT(hints != NULL);
    363 
    364 	errnum = _citrus_prop_read_symbol(ms, name, sizeof(name));
    365 	if (errnum != 0)
    366 		return errnum;
    367 	for (hint = hints; hint->name != NULL; ++hint) {
    368 		if (_citrus_bcs_strcasecmp(name, hint->name) == 0)
    369 			goto hint_found;
    370 	}
    371 	return EINVAL;
    372 
    373 hint_found:
    374 	_memstream_skip_ws(ms);
    375 	ch = _memstream_getc(ms);
    376 	if (ch != '=' && ch != ':')
    377 		_memstream_ungetc(ms, ch);
    378 	do {
    379 		_citrus_prop_object_init(&ostart, hint->type);
    380 		_citrus_prop_object_init(&oend, hint->type);
    381 		errnum = (*readers[hint->type])(ms, &ostart);
    382 		if (errnum != 0)
    383 			return errnum;
    384 		_memstream_skip_ws(ms);
    385 		ch = _memstream_getc(ms);
    386 		switch (hint->type) {
    387 		case _CITRUS_PROP_BOOL:
    388 		case _CITRUS_PROP_STR:
    389 			break;
    390 		default:
    391 			if (ch != '-')
    392 				break;
    393 			errnum = (*readers[hint->type])(ms, &oend);
    394 			if (errnum != 0)
    395 				return errnum;
    396 			_memstream_skip_ws(ms);
    397 			ch = _memstream_getc(ms);
    398 		}
    399 #define CALL0(_func_)					\
    400 do {							\
    401 	_DIAGASSERT(hint->cb._func_.func != NULL);	\
    402 	errnum = (*hint->cb._func_.func)(context,	\
    403 	    hint->name,	ostart.u._func_);		\
    404 } while (0)
    405 #define CALL1(_func_)					\
    406 do {							\
    407 	_DIAGASSERT(hint->cb._func_.func != NULL);	\
    408 	errnum = (*hint->cb._func_.func)(context,	\
    409 	    hint->name,	ostart.u._func_, oend.u._func_);\
    410 } while (0)
    411 
    412 		switch (hint->type) {
    413 
    414 		case _CITRUS_PROP_BOOL:
    415 			CALL0(boolean);
    416 			break;
    417 
    418 		case _CITRUS_PROP_STR:
    419 			CALL0(str);
    420 			break;
    421 
    422 		case _CITRUS_PROP_CHR:
    423 			CALL1(chr);
    424 			break;
    425 
    426 		case _CITRUS_PROP_NUM:
    427 			CALL1(num);
    428 			break;
    429 
    430 		default:
    431 			abort();
    432 			/*NOTREACHED*/
    433 		}
    434 #undef CALL0
    435 #undef CALL1
    436 		_citrus_prop_object_uninit(&ostart);
    437 		_citrus_prop_object_uninit(&oend);
    438 		if (errnum != 0)
    439 			return errnum;
    440 	} while (ch == ',');
    441 	if (ch != ';')
    442 		_memstream_ungetc(ms, ch);
    443 	return 0;
    444 }
    445 
    446 int
    447 _citrus_prop_parse_variable(const _citrus_prop_hint_t * __restrict hints,
    448 	void * __restrict context, const void *var, size_t lenvar)
    449 {
    450 	struct _memstream ms;
    451 	int errnum, ch;
    452 
    453 	_DIAGASSERT(hints != NULL);
    454 
    455 	_memstream_bind_ptr(&ms, __UNCONST(var), lenvar);
    456 	for (;;) {
    457 		_memstream_skip_ws(&ms);
    458 		ch = _memstream_getc(&ms);
    459 		if (ch == EOF || ch == '\0')
    460 			break;
    461 		_memstream_ungetc(&ms, ch);
    462 		errnum = _citrus_prop_parse_element(&ms, hints, context);
    463 		if (errnum != 0)
    464 			return errnum;
    465 	}
    466 	return 0;
    467 }
    468