/src/freeradius-server/src/lib/unlang/switch.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 355fbca719c22c9144aa12f3c2c975fdee7c2bb1 $ |
19 | | * |
20 | | * @file unlang/switch.c |
21 | | * @brief Unlang "switch" keyword evaluation. |
22 | | * |
23 | | * @copyright 2006-2019 The FreeRADIUS server project |
24 | | */ |
25 | | RCSID("$Id: 355fbca719c22c9144aa12f3c2c975fdee7c2bb1 $") |
26 | | |
27 | | #include <freeradius-devel/server/rcode.h> |
28 | | #include "group_priv.h" |
29 | | #include "switch_priv.h" |
30 | | #include "xlat_priv.h" |
31 | | |
32 | | static unlang_action_t unlang_switch(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
33 | 0 | { |
34 | 0 | unlang_t *found; |
35 | |
|
36 | 0 | unlang_group_t *switch_g; |
37 | 0 | unlang_switch_t *switch_gext; |
38 | |
|
39 | 0 | fr_value_box_t const *box = NULL; |
40 | |
|
41 | 0 | fr_pair_t *vp; |
42 | | |
43 | | /* |
44 | | * Mock up an unlang_cast_t. Note that these on-stack |
45 | | * buffers are the reason why case_cmp(), case_hash(), |
46 | | * and case_to_key() use direct casts, and not the |
47 | | * "generic to x" functions. |
48 | | */ |
49 | 0 | tmpl_t case_vpt = (tmpl_t) { |
50 | 0 | .type = TMPL_TYPE_DATA, |
51 | 0 | }; |
52 | 0 | unlang_case_t my_case = (unlang_case_t) { |
53 | 0 | .group = (unlang_group_t) { |
54 | 0 | .self = (unlang_t) { |
55 | 0 | .type = UNLANG_TYPE_CASE, |
56 | 0 | }, |
57 | 0 | }, |
58 | 0 | .vpt = &case_vpt, |
59 | 0 | }; |
60 | |
|
61 | 0 | switch_g = unlang_generic_to_group(frame->instruction); |
62 | 0 | switch_gext = unlang_group_to_switch(switch_g); |
63 | |
|
64 | 0 | found = NULL; |
65 | | |
66 | | /* |
67 | | * The attribute doesn't exist. We can skip |
68 | | * directly to the default 'case' statement. |
69 | | */ |
70 | 0 | if (tmpl_is_attr(switch_gext->vpt)) { |
71 | 0 | if (tmpl_find_vp(&vp, request, switch_gext->vpt) < 0) { |
72 | 0 | found = switch_gext->default_case; |
73 | 0 | goto do_null_case; |
74 | 0 | } else { |
75 | 0 | box = &vp->data; |
76 | 0 | } |
77 | | |
78 | | /* |
79 | | * Expand the template if necessary, so that it |
80 | | * is evaluated once instead of for each 'case' |
81 | | * statement. |
82 | | */ |
83 | 0 | } else if (tmpl_is_xlat(switch_gext->vpt) || |
84 | 0 | tmpl_is_exec(switch_gext->vpt)) { |
85 | 0 | ssize_t slen; |
86 | |
|
87 | 0 | slen = tmpl_aexpand_type(unlang_interpret_frame_talloc_ctx(request), &box, FR_TYPE_VALUE_BOX, |
88 | 0 | request, switch_gext->vpt); |
89 | 0 | if (slen < 0) { |
90 | 0 | RDEBUG("Switch failed expanding %s - %s", switch_gext->vpt->name, fr_strerror()); |
91 | 0 | goto find_null_case; |
92 | 0 | } |
93 | 0 | } else if (!fr_cond_assert_msg(0, "Invalid tmpl type %s", tmpl_type_to_str(switch_gext->vpt->type))) { |
94 | 0 | return UNLANG_ACTION_FAIL; |
95 | 0 | } |
96 | | |
97 | | /* |
98 | | * case_gext->vpt.data.literal is an in-line box, so we |
99 | | * have to make a shallow copy of its contents. |
100 | | * |
101 | | * Note: We do not pass a ctx here as we don't want to |
102 | | * create a reference. |
103 | | */ |
104 | 0 | fr_value_box_copy_shallow(NULL, &case_vpt.data.literal, box); |
105 | 0 | found = fr_htrie_find(switch_gext->ht, &my_case); |
106 | 0 | if (!found) { |
107 | 0 | find_null_case: |
108 | 0 | found = switch_gext->default_case; |
109 | 0 | } |
110 | |
|
111 | 0 | do_null_case: |
112 | | /* |
113 | | * Nothing found. Just continue, and ignore the "switch" |
114 | | * statement. |
115 | | */ |
116 | 0 | if (!found) { |
117 | 0 | if (box) { |
118 | 0 | RWDEBUG("Failed to find 'case' target for value %pV", box); |
119 | 0 | } else { |
120 | 0 | RWDEBUG("Failed to find 'default' target when expansion of %s returning no value", |
121 | 0 | switch_gext->vpt->name); |
122 | 0 | } |
123 | 0 | return UNLANG_ACTION_EXECUTE_NEXT; |
124 | 0 | } |
125 | | |
126 | 0 | if (unlang_interpret_push(NULL, request, found, FRAME_CONF(RLM_MODULE_NOT_SET, UNLANG_SUB_FRAME), UNLANG_NEXT_STOP) < 0) { |
127 | 0 | RETURN_UNLANG_ACTION_FATAL; |
128 | 0 | } |
129 | | |
130 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
131 | 0 | } |
132 | | |
133 | | |
134 | | static unlang_action_t unlang_case(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
135 | 0 | { |
136 | 0 | unlang_group_t *g = unlang_generic_to_group(frame->instruction); |
137 | |
|
138 | 0 | if (unlang_list_empty(&g->children)) RETURN_UNLANG_NOOP; |
139 | | |
140 | 0 | return unlang_group(p_result, request, frame); |
141 | 0 | } |
142 | | |
143 | | |
144 | | static unlang_t *unlang_compile_case(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci) |
145 | 0 | { |
146 | 0 | CONF_SECTION *cs = cf_item_to_section(ci); |
147 | 0 | int i; |
148 | 0 | char const *name2; |
149 | 0 | unlang_t *c; |
150 | 0 | unlang_group_t *case_g; |
151 | 0 | unlang_case_t *case_gext; |
152 | 0 | tmpl_t *vpt = NULL; |
153 | 0 | tmpl_rules_t t_rules; |
154 | | |
155 | | /* |
156 | | * We allow unknown attributes here. |
157 | | */ |
158 | 0 | t_rules = *(unlang_ctx->rules); |
159 | 0 | t_rules.attr.allow_unknown = true; |
160 | 0 | RULES_VERIFY(&t_rules); |
161 | | |
162 | 0 | if (!parent || (parent->type != UNLANG_TYPE_SWITCH)) { |
163 | 0 | cf_log_err(cs, "\"case\" statements may only appear within a \"switch\" section"); |
164 | 0 | cf_log_err(ci, DOC_KEYWORD_REF(case)); |
165 | 0 | return NULL; |
166 | 0 | } |
167 | | |
168 | | /* |
169 | | * case THING means "match THING" |
170 | | * case means "match anything" |
171 | | */ |
172 | 0 | name2 = cf_section_name2(cs); |
173 | 0 | if (name2) { |
174 | 0 | ssize_t slen; |
175 | 0 | fr_token_t quote; |
176 | 0 | unlang_group_t *switch_g; |
177 | 0 | unlang_switch_t *switch_gext; |
178 | |
|
179 | 0 | switch_g = unlang_generic_to_group(parent); |
180 | 0 | switch_gext = unlang_group_to_switch(switch_g); |
181 | |
|
182 | 0 | fr_assert(switch_gext->vpt != NULL); |
183 | | |
184 | | /* |
185 | | * We need to cast case values to match |
186 | | * what we're switching over, otherwise |
187 | | * integers of different widths won't |
188 | | * match. |
189 | | */ |
190 | 0 | t_rules.cast = tmpl_expanded_type(switch_gext->vpt); |
191 | | |
192 | | /* |
193 | | * Need to pass the attribute from switch |
194 | | * to tmpl rules so we can convert the |
195 | | * case string to an integer value. |
196 | | */ |
197 | 0 | if (tmpl_is_attr(switch_gext->vpt)) { |
198 | 0 | fr_dict_attr_t const *da = tmpl_attr_tail_da(switch_gext->vpt); |
199 | 0 | if (da->flags.has_value) t_rules.enumv = da; |
200 | 0 | } |
201 | |
|
202 | 0 | quote = cf_section_name2_quote(cs); |
203 | |
|
204 | 0 | slen = tmpl_afrom_substr(cs, &vpt, |
205 | 0 | &FR_SBUFF_IN_STR(name2), |
206 | 0 | quote, |
207 | 0 | NULL, |
208 | 0 | &t_rules); |
209 | 0 | if (!vpt) { |
210 | 0 | cf_canonicalize_error(cs, slen, "Failed parsing argument to 'case'", name2); |
211 | 0 | return NULL; |
212 | 0 | } |
213 | | |
214 | | /* |
215 | | * Bare word strings are attribute references |
216 | | */ |
217 | 0 | if (tmpl_is_attr(vpt) || tmpl_is_attr_unresolved(vpt)) { |
218 | 0 | fail_attr: |
219 | 0 | cf_log_err(cs, "arguments to 'case' statements MUST NOT be attribute references."); |
220 | 0 | goto fail; |
221 | 0 | } |
222 | | |
223 | 0 | if (!tmpl_is_data(vpt) || tmpl_is_data_unresolved(vpt)) { |
224 | 0 | cf_log_err(cs, "arguments to 'case' statements MUST be static data."); |
225 | 0 | fail: |
226 | 0 | talloc_free(vpt); |
227 | 0 | return NULL; |
228 | 0 | } |
229 | | |
230 | | /* |
231 | | * References to unresolved attributes are forbidden. They are no longer "bare word |
232 | | * strings". |
233 | | */ |
234 | 0 | if ((quote == T_BARE_WORD) && (tmpl_value_type(vpt) == FR_TYPE_STRING)) { |
235 | 0 | goto fail_attr; |
236 | 0 | } |
237 | |
|
238 | 0 | } /* else it's a default 'case' statement */ |
239 | | |
240 | | /* |
241 | | * If we were asked to match something, then we MUST |
242 | | * match it, even if the section is empty. Otherwise we |
243 | | * will silently skip the match, and then fall through to |
244 | | * the "default" statement. |
245 | | */ |
246 | 0 | c = unlang_compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_CASE); |
247 | 0 | if (!c) { |
248 | 0 | talloc_free(vpt); |
249 | 0 | return NULL; |
250 | 0 | } |
251 | | |
252 | 0 | case_g = unlang_generic_to_group(c); |
253 | 0 | case_gext = unlang_group_to_case(case_g); |
254 | 0 | case_gext->vpt = talloc_steal(case_gext, vpt); |
255 | | |
256 | | /* |
257 | | * Set all of its codes to return, so that |
258 | | * when we pick a 'case' statement, we don't |
259 | | * fall through to processing the next one. |
260 | | */ |
261 | 0 | for (i = 0; i < RLM_MODULE_NUMCODES; i++) c->actions.actions[i] = MOD_ACTION_RETURN; |
262 | |
|
263 | 0 | return c; |
264 | 0 | } |
265 | | |
266 | | static int8_t case_cmp(void const *one, void const *two) |
267 | 0 | { |
268 | 0 | unlang_case_t const *a = (unlang_case_t const *) one; /* may not be talloc'd! See switch.c */ |
269 | 0 | unlang_case_t const *b = (unlang_case_t const *) two; /* may not be talloc'd! */ |
270 | |
|
271 | 0 | return fr_value_box_cmp(tmpl_value(a->vpt), tmpl_value(b->vpt)); |
272 | 0 | } |
273 | | |
274 | | static uint32_t case_hash(void const *data) |
275 | 0 | { |
276 | 0 | unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */ |
277 | |
|
278 | 0 | return fr_value_box_hash(tmpl_value(a->vpt)); |
279 | 0 | } |
280 | | |
281 | | static int case_to_key(uint8_t **out, size_t *outlen, void const *data) |
282 | 0 | { |
283 | 0 | unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */ |
284 | |
|
285 | 0 | return fr_value_box_to_key(out, outlen, tmpl_value(a->vpt)); |
286 | 0 | } |
287 | | |
288 | | static unlang_t *unlang_compile_switch(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci) |
289 | 0 | { |
290 | 0 | CONF_SECTION *cs = cf_item_to_section(ci); |
291 | 0 | CONF_ITEM *subci; |
292 | 0 | fr_token_t token; |
293 | 0 | char const *name1, *name2; |
294 | 0 | char const *type_name; |
295 | |
|
296 | 0 | unlang_group_t *g; |
297 | 0 | unlang_switch_t *gext; |
298 | |
|
299 | 0 | unlang_t *c; |
300 | 0 | ssize_t slen; |
301 | |
|
302 | 0 | tmpl_rules_t t_rules; |
303 | |
|
304 | 0 | fr_type_t type; |
305 | 0 | fr_htrie_type_t htype; |
306 | | |
307 | | /* |
308 | | * We allow unknown attributes here. |
309 | | */ |
310 | 0 | t_rules = *(unlang_ctx->rules); |
311 | 0 | t_rules.attr.allow_unknown = true; |
312 | 0 | RULES_VERIFY(&t_rules); |
313 | | |
314 | 0 | name2 = cf_section_name2(cs); |
315 | 0 | if (!name2) { |
316 | 0 | cf_log_err(cs, "You must specify a variable to switch over for 'switch'"); |
317 | 0 | print_url: |
318 | 0 | cf_log_err(ci, DOC_KEYWORD_REF(switch)); |
319 | 0 | return NULL; |
320 | 0 | } |
321 | | |
322 | 0 | if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE; |
323 | | |
324 | 0 | g = unlang_group_allocate(parent, cs, UNLANG_TYPE_SWITCH); |
325 | 0 | if (!g) return NULL; |
326 | | |
327 | 0 | gext = unlang_group_to_switch(g); |
328 | | |
329 | | /* |
330 | | * Create the template. All attributes and xlats are |
331 | | * defined by now. |
332 | | * |
333 | | * The 'case' statements need g->vpt filled out to ensure |
334 | | * that the data types match. |
335 | | */ |
336 | 0 | token = cf_section_name2_quote(cs); |
337 | |
|
338 | 0 | if ((token == T_BARE_WORD) && (name2[0] != '%')) { |
339 | 0 | slen = tmpl_afrom_attr_substr(gext, NULL, &gext->vpt, |
340 | 0 | &FR_SBUFF_IN_STR(name2), |
341 | 0 | NULL, |
342 | 0 | &t_rules); |
343 | 0 | } else { |
344 | 0 | slen = tmpl_afrom_substr(gext, &gext->vpt, |
345 | 0 | &FR_SBUFF_IN_STR(name2), |
346 | 0 | token, |
347 | 0 | NULL, |
348 | 0 | &t_rules); |
349 | 0 | } |
350 | 0 | if (!gext->vpt) { |
351 | 0 | cf_canonicalize_error(cs, slen, "Failed parsing argument to 'switch'", name2); |
352 | 0 | talloc_free(g); |
353 | 0 | return NULL; |
354 | 0 | } |
355 | | |
356 | 0 | c = unlang_group_to_generic(g); |
357 | 0 | c->name = "switch"; |
358 | 0 | c->debug_name = talloc_typed_asprintf(c, "switch %s", name2); |
359 | | |
360 | | /* |
361 | | * Fixup the template before compiling the children. |
362 | | * This is so that compile_case() can do attribute type |
363 | | * checks / casts against us. |
364 | | */ |
365 | 0 | if (!pass2_fixup_tmpl(g, &gext->vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) { |
366 | 0 | talloc_free(g); |
367 | 0 | return NULL; |
368 | 0 | } |
369 | | |
370 | 0 | if (tmpl_is_list(gext->vpt)) { |
371 | 0 | cf_log_err(cs, "Cannot use list for 'switch' statement"); |
372 | 0 | error: |
373 | 0 | talloc_free(g); |
374 | 0 | goto print_url; |
375 | 0 | } |
376 | | |
377 | 0 | if (tmpl_contains_regex(gext->vpt)) { |
378 | 0 | cf_log_err(cs, "Cannot use regular expression for 'switch' statement"); |
379 | 0 | goto error; |
380 | 0 | } |
381 | | |
382 | 0 | if (tmpl_is_data(gext->vpt)) { |
383 | 0 | cf_log_err(cs, "Cannot use constant data for 'switch' statement"); |
384 | 0 | goto error; |
385 | 0 | } |
386 | | |
387 | 0 | if (tmpl_is_xlat(gext->vpt)) { |
388 | 0 | xlat_exp_head_t *xlat = tmpl_xlat(gext->vpt); |
389 | |
|
390 | 0 | if (xlat->flags.constant || xlat->flags.pure) { |
391 | 0 | cf_log_err(cs, "Cannot use constant data for 'switch' statement"); |
392 | 0 | goto error; |
393 | 0 | } |
394 | 0 | } |
395 | | |
396 | | |
397 | 0 | if (tmpl_needs_resolving(gext->vpt)) { |
398 | 0 | cf_log_err(cs, "Cannot resolve key for 'switch' statement"); |
399 | 0 | goto error; |
400 | 0 | } |
401 | | |
402 | 0 | type_name = cf_section_argv(cs, 0); /* AFTER name1, name2 */ |
403 | 0 | if (type_name) { |
404 | 0 | type = fr_table_value_by_str(fr_type_table, type_name, FR_TYPE_NULL); |
405 | | |
406 | | /* |
407 | | * Should have been caught in cf_file.c, process_switch() |
408 | | */ |
409 | 0 | fr_assert(type != FR_TYPE_NULL); |
410 | 0 | fr_assert(fr_type_is_leaf(type)); |
411 | |
|
412 | 0 | do_cast: |
413 | 0 | if (tmpl_cast_set(gext->vpt, type) < 0) { |
414 | 0 | cf_log_perr(cs, "Failed setting cast type"); |
415 | 0 | goto error; |
416 | 0 | } |
417 | |
|
418 | 0 | } else { |
419 | | /* |
420 | | * Get the return type of the tmpl. If we don't know, |
421 | | * mash it all to string. |
422 | | */ |
423 | 0 | type = tmpl_data_type(gext->vpt); |
424 | 0 | if ((type == FR_TYPE_NULL) || (type == FR_TYPE_VOID)) { |
425 | 0 | type = FR_TYPE_STRING; |
426 | 0 | goto do_cast; |
427 | 0 | } |
428 | 0 | } |
429 | | |
430 | 0 | htype = fr_htrie_hint(type); |
431 | 0 | if (htype == FR_HTRIE_INVALID) { |
432 | 0 | cf_log_err(cs, "Invalid data type '%s' used for 'switch' statement", |
433 | 0 | fr_type_to_str(type)); |
434 | 0 | goto error; |
435 | 0 | } |
436 | | |
437 | 0 | gext->ht = fr_htrie_alloc(gext, htype, |
438 | 0 | (fr_hash_t) case_hash, |
439 | 0 | (fr_cmp_t) case_cmp, |
440 | 0 | (fr_trie_key_t) case_to_key, |
441 | 0 | NULL); |
442 | 0 | if (!gext->ht) { |
443 | 0 | cf_log_err(cs, "Failed initializing internal data structures"); |
444 | 0 | talloc_free(g); |
445 | 0 | return NULL; |
446 | 0 | } |
447 | | |
448 | | /* |
449 | | * Walk through the children of the switch section, |
450 | | * ensuring that they're all 'case' statements, and then compiling them. |
451 | | */ |
452 | 0 | for (subci = cf_item_next(cs, NULL); |
453 | 0 | subci != NULL; |
454 | 0 | subci = cf_item_next(cs, subci)) { |
455 | 0 | CONF_SECTION *subcs; |
456 | 0 | unlang_t *single; |
457 | 0 | unlang_case_t *case_gext; |
458 | |
|
459 | 0 | if (!cf_item_is_section(subci)) { |
460 | 0 | if (!cf_item_is_pair(subci)) continue; |
461 | | |
462 | 0 | cf_log_err(subci, "\"switch\" sections can only have \"case\" subsections"); |
463 | 0 | goto error; |
464 | 0 | } |
465 | | |
466 | 0 | subcs = cf_item_to_section(subci); /* can't return NULL */ |
467 | 0 | name1 = cf_section_name1(subcs); |
468 | |
|
469 | 0 | if (strcmp(name1, "case") != 0) { |
470 | | /* |
471 | | * We finally support "default" sections for "switch". |
472 | | */ |
473 | 0 | if (strcmp(name1, "default") == 0) { |
474 | 0 | if (cf_section_name2(subcs) != NULL) { |
475 | 0 | cf_log_err(subci, "\"default\" sections cannot have a match argument"); |
476 | 0 | goto error; |
477 | 0 | } |
478 | 0 | goto handle_default; |
479 | 0 | } |
480 | | |
481 | 0 | cf_log_err(subci, "\"switch\" sections can only have \"case\" subsections"); |
482 | 0 | goto error; |
483 | 0 | } |
484 | | |
485 | 0 | name2 = cf_section_name2(subcs); |
486 | 0 | if (!name2) { |
487 | 0 | handle_default: |
488 | 0 | if (gext->default_case) { |
489 | 0 | cf_log_err(subci, "Cannot have two 'default' case statements"); |
490 | 0 | goto error; |
491 | 0 | } |
492 | 0 | } |
493 | | |
494 | | /* |
495 | | * Compile the subsection. |
496 | | */ |
497 | 0 | single = unlang_compile_case(c, unlang_ctx, subci); |
498 | 0 | if (!single) goto error; |
499 | | |
500 | 0 | fr_assert(single->type == UNLANG_TYPE_CASE); |
501 | | |
502 | | /* |
503 | | * Remember the "default" section, and insert the |
504 | | * non-default "case" into the htrie. |
505 | | */ |
506 | 0 | case_gext = unlang_group_to_case(unlang_generic_to_group(single)); |
507 | 0 | if (!case_gext->vpt) { |
508 | 0 | gext->default_case = single; |
509 | |
|
510 | 0 | } else if (!fr_htrie_insert(gext->ht, single)) { |
511 | 0 | single = fr_htrie_find(gext->ht, single); |
512 | | |
513 | | /* |
514 | | * @todo - look up the key and get the previous one? |
515 | | */ |
516 | 0 | cf_log_err(subci, "Failed inserting 'case' statement. Is there a duplicate?"); |
517 | |
|
518 | 0 | if (single) cf_log_err(unlang_generic_to_group(single)->cs, "Duplicate may be here."); |
519 | |
|
520 | 0 | goto error; |
521 | 0 | } |
522 | | |
523 | 0 | unlang_list_insert_tail(&g->children, single); |
524 | 0 | } |
525 | | |
526 | 0 | return c; |
527 | 0 | } |
528 | | |
529 | | void unlang_switch_init(void) |
530 | 0 | { |
531 | 0 | unlang_register(&(unlang_op_t) { |
532 | 0 | .name = "switch", |
533 | 0 | .type = UNLANG_TYPE_SWITCH, |
534 | 0 | .flag = UNLANG_OP_FLAG_DEBUG_BRACES, |
535 | |
|
536 | 0 | .compile = unlang_compile_switch, |
537 | 0 | .interpret = unlang_switch, |
538 | |
|
539 | 0 | .unlang_size = sizeof(unlang_switch_t), |
540 | 0 | .unlang_name = "unlang_switch_t", |
541 | |
|
542 | 0 | .pool_headers = TMPL_POOL_DEF_HEADERS, |
543 | 0 | .pool_len = TMPL_POOL_DEF_LEN |
544 | 0 | }); |
545 | | |
546 | |
|
547 | 0 | unlang_register(&(unlang_op_t){ |
548 | 0 | .name = "case", |
549 | 0 | .type = UNLANG_TYPE_CASE, |
550 | 0 | .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_BREAK_POINT, |
551 | |
|
552 | 0 | .compile = unlang_compile_case, |
553 | 0 | .interpret = unlang_case, |
554 | |
|
555 | 0 | .unlang_size = sizeof(unlang_case_t), |
556 | 0 | .unlang_name = "unlang_case_t", |
557 | 0 | }); |
558 | 0 | } |