1 /**
2  * \file confeval.c
3  * \ingroup Configuration
4  * \brief Configuration helper functions
5  * \author Jaroslav Kysela <perex@perex.cz>
6  * \date 2021
7  *
8  * Configuration string evaluation.
9  *
10  * See the \ref confarg_math page for more details.
11  */
12 /*
13  *  Configuration string evaluation
14  *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>,
15  *			  Jaroslav Kysela <perex@perex.cz>
16  *
17  *
18  *   This library is free software; you can redistribute it and/or modify
19  *   it under the terms of the GNU Lesser General Public License as
20  *   published by the Free Software Foundation; either version 2.1 of
21  *   the License, or (at your option) any later version.
22  *
23  *   This program is distributed in the hope that it will be useful,
24  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
25  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26  *   GNU Lesser General Public License for more details.
27  *
28  *   You should have received a copy of the GNU Lesser General Public
29  *   License along with this library; if not, write to the Free Software
30  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
31  *
32  */
33 
34 #include "local.h"
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <ctype.h>
39 #include <limits.h>
40 
41 #ifndef DOC_HIDDEN
42 typedef long long value_type_t;
43 #endif /* DOC_HIDDEN */
44 
_find_end_of_expression(const char *s, char begin, char end)45 static const char *_find_end_of_expression(const char *s, char begin, char end)
46 {
47 	int count = 1;
48 	while (*s) {
49 		if (*s == begin) {
50 			count++;
51 		} else if (*s == end) {
52 			count--;
53 			if (count == 0)
54 				return s + 1;
55 		}
56 		s++;
57 	}
58 	return NULL;
59 }
60 
_parse_integer(value_type_t *val, const char **s)61 static int _parse_integer(value_type_t *val, const char **s)
62 {
63 	long long v;
64 	char *end;
65 
66 	errno = 0;
67 	v = strtoll(*s, &end, 0);
68 	if (errno)
69 		return -errno;
70 	*val = v;
71 	if (((long long)*val) != v)
72 		return -ERANGE;
73 	*s = end;
74 	return 0;
75 }
76 
_to_integer(value_type_t *val, snd_config_t *c)77 static int _to_integer(value_type_t *val, snd_config_t *c)
78 {
79 	int err;
80 
81 	switch(snd_config_get_type(c)) {
82 	case SND_CONFIG_TYPE_INTEGER:
83 		{
84 			long v;
85 			err = snd_config_get_integer(c, &v);
86 			if (err >= 0)
87 				*val = v;
88 		}
89 		break;
90 	case SND_CONFIG_TYPE_INTEGER64:
91 		{
92 			long long v;
93 			err = snd_config_get_integer64(c, &v);
94 			if (err >= 0) {
95 				*val = v;
96 				if (((long long)*val) != v)
97 					return -ERANGE;
98 				return 0;
99 			}
100 		}
101 		break;
102 	case SND_CONFIG_TYPE_STRING:
103 		{
104 			const char *s;
105 			long long v;
106 			err = snd_config_get_string(c, &s);
107 			if (err >= 0) {
108 				err = safe_strtoll(s, &v);
109 				if (err >= 0) {
110 					*val = v;
111 					if (((long long)*val) != v)
112 						return -ERANGE;
113 					return 0;
114 				}
115 			}
116 		}
117 		break;
118 	default:
119 		return -EINVAL;
120 	}
121 	return err;
122 }
123 
124 #ifndef DOC_HIDDEN
_snd_eval_string(snd_config_t **dst, const char *s, snd_config_expand_fcn_t fcn, void *private_data)125 int _snd_eval_string(snd_config_t **dst, const char *s,
126 		     snd_config_expand_fcn_t fcn, void *private_data)
127 {
128 	snd_config_t *tmp;
129 	const char *save, *e;
130 	char *m;
131 	value_type_t left, right;
132 	int err, c, op, off;
133 	enum {
134 		LEFT,
135 		OP,
136 		RIGHT,
137 		END
138 	} pos;
139 
140 	while (*s && *s <= ' ') s++;
141 	save = s;
142 	pos = LEFT;
143 	op = 0;
144 	while (*s) {
145 		while (*s && *s <= ' ') s++;
146 		c = *s;
147 		if (c == '\0')
148 			break;
149 		if (pos == END) {
150 			SNDERR("unexpected expression tail '%s'", s);
151 			return -EINVAL;
152 		}
153 		if (pos == OP) {
154 			switch (c) {
155 				case '+':
156 				case '-':
157 				case '*':
158 				case '/':
159 				case '%':
160 				case '|':
161 				case '&': op = c; break;
162 				default:
163 					SNDERR("unknown operation '%c'", c);
164 					return -EINVAL;
165 			}
166 			pos = RIGHT;
167 			s++;
168 			continue;
169 		}
170 		if (c == '(') {
171 			e = _find_end_of_expression(s + 1, '(', ')');
172 			off = 1;
173 			goto _expr;
174 		} else if (c == '$') {
175 			if (s[1] == '[') {
176 				e = _find_end_of_expression(s + 2, '[', ']');
177 				off = 2;
178   _expr:
179 				if (e == NULL)
180 					return -EINVAL;
181 				m = malloc(e - s - (off - 1));
182 				if (m == NULL)
183 					return -ENOMEM;
184 				memcpy(m, s + off, e - s - off);
185 				m[e - s - (off + 1)] = '\0';
186 				err = _snd_eval_string(&tmp, m, fcn, private_data);
187 				free(m);
188 				if (err < 0)
189 					return err;
190 				s = e;
191 				if (*s)
192 					s++;
193 			} else {
194 				e = s + 1;
195 				while (*e) {
196 					if (!isalnum(*e) && *e != '_')
197 						break;
198 					e++;
199 				}
200 				m = malloc(e - s);
201 				if (m == NULL)
202 					return -ENOMEM;
203 				memcpy(m, s + 1, e - s - 1);
204 				m[e - s - 1] = '\0';
205 				err = fcn(&tmp, m, private_data);
206 				free(m);
207 				if (err < 0)
208 					return err;
209 				if (tmp == NULL) {
210 					err = snd_config_imake_integer(&tmp, NULL, 0);
211 					if (err < 0)
212 						return err;
213 				}
214 				s = e;
215 			}
216 			err = _to_integer(op == LEFT ? &left : &right, tmp);
217 			snd_config_delete(tmp);
218 		} else if (c == '-' || (c >= '0' && c <= '9')) {
219 			err = _parse_integer(op == LEFT ? &left : &right, &s);
220 		} else {
221 			return -EINVAL;
222 		}
223 		if (err < 0)
224 			return err;
225 		pos = op == LEFT ? OP : END;
226 	}
227 	if (pos != OP && pos != END) {
228 		SNDERR("incomplete expression '%s'", save);
229 		return -EINVAL;
230 	}
231 
232 	if (pos == END) {
233 		switch (op) {
234 		case '+': left = left + right; break;
235 		case '-': left = left - right; break;
236 		case '*': left = left * right; break;
237 		case '/': left = left / right; break;
238 		case '%': left = left % right; break;
239 		case '|': left = left | right; break;
240 		case '&': left = left & right; break;
241 		default: return -EINVAL;
242 		}
243 	}
244 
245 	if (left > INT_MAX || left < INT_MIN)
246 		return snd_config_imake_integer64(dst, NULL, left);
247 	else
248 		return snd_config_imake_integer(dst, NULL, left);
249 }
250 #endif /* DOC_HIDDEN */
251 
252 /**
253  * \brief Evaluate an math expression in the string
254  * \param[out] dst The function puts the handle to the new configuration
255  *                 node at the address specified by \a dst.
256  * \param[in] s A string to evaluate
257  * \param[in] fcn A function to get the variable contents
258  * \param[in] private_data A private value for the variable contents function
259  * \return 0 if successful, otherwise a negative error code.
260  */
snd_config_evaluate_string(snd_config_t **dst, const char *s, snd_config_expand_fcn_t fcn, void *private_data)261 int snd_config_evaluate_string(snd_config_t **dst, const char *s,
262 			       snd_config_expand_fcn_t fcn, void *private_data)
263 {
264 	assert(dst && s);
265 	int err;
266 
267 	if (*s != '$')
268 		return -EINVAL;
269 	if (s[1] == '[') {
270 		err = _snd_eval_string(dst, s, fcn, private_data);
271 		if (err < 0)
272 			SNDERR("wrong expression '%s'", s);
273 	} else {
274 		err = fcn(dst, s + 1, private_data);
275 	}
276 	return err;
277 }
278