Commit | Line | Data |
---|---|---|
d44e3c4f | 1 | /****************************************************************************** |
2 | * Copyright (c) 2000-2016 Ericsson Telecom AB | |
3 | * All rights reserved. This program and the accompanying materials | |
4 | * are made available under the terms of the Eclipse Public License v1.0 | |
5 | * which accompanies this distribution, and is available at | |
6 | * http://www.eclipse.org/legal/epl-v10.html | |
7 | * | |
8 | * Contributors: | |
9 | * Balasko, Jeno | |
10 | * Baranyi, Botond | |
11 | * Delic, Adam | |
12 | * Forstner, Matyas | |
13 | * Raduly, Csaba | |
19700695 | 14 | * Szabo, Bence Janos |
d44e3c4f | 15 | * Szabo, Janos Zoltan – initial implementation |
16 | * Zalanyi, Balazs Andor | |
17 | * | |
18 | ******************************************************************************/ | |
970ed795 EL |
19 | #include "PatternString.hh" |
20 | #include "../../common/pattern.hh" | |
21 | #include "../CompilerError.hh" | |
22 | #include "../Code.hh" | |
3abe9331 | 23 | #include "../../common/JSON_Tokenizer.hh" |
970ed795 EL |
24 | |
25 | #include "TtcnTemplate.hh" | |
26 | ||
27 | namespace Ttcn { | |
28 | ||
29 | // ================================= | |
30 | // ===== PatternString::ps_elem_t | |
31 | // ================================= | |
32 | ||
33 | struct PatternString::ps_elem_t { | |
34 | enum kind_t { | |
35 | PSE_STR, | |
36 | PSE_REF, | |
37 | PSE_REFDSET | |
38 | } kind; | |
39 | union { | |
40 | string *str; | |
41 | Ttcn::Reference *ref; | |
42 | }; | |
43 | ps_elem_t(kind_t p_kind, const string& p_str); | |
44 | ps_elem_t(kind_t p_kind, Ttcn::Reference *p_ref); | |
45 | ps_elem_t(const ps_elem_t& p); | |
46 | ~ps_elem_t(); | |
47 | ps_elem_t* clone() const; | |
48 | void set_fullname(const string& p_fullname); | |
49 | void set_my_scope(Scope *p_scope); | |
50 | void chk_ref(PatternString::pstr_type_t pstr_type, Type::expected_value_t expected_value); | |
51 | void set_code_section(GovernedSimple::code_section_t p_code_section); | |
52 | }; | |
53 | ||
54 | PatternString::ps_elem_t::ps_elem_t(kind_t p_kind, const string& p_str) | |
55 | : kind(p_kind) | |
56 | { | |
57 | str = new string(p_str); | |
58 | } | |
59 | ||
60 | PatternString::ps_elem_t::ps_elem_t(kind_t p_kind, Ttcn::Reference *p_ref) | |
61 | : kind(p_kind) | |
62 | { | |
63 | if (!p_ref) FATAL_ERROR("PatternString::ps_elem_t::ps_elem_t()"); | |
64 | ref = p_ref; | |
65 | } | |
66 | ||
67 | PatternString::ps_elem_t::~ps_elem_t() | |
68 | { | |
69 | switch(kind) { | |
70 | case PSE_STR: | |
71 | delete str; | |
72 | break; | |
73 | case PSE_REF: | |
74 | case PSE_REFDSET: | |
75 | delete ref; | |
76 | break; | |
77 | } // switch kind | |
78 | } | |
79 | ||
80 | PatternString::ps_elem_t* PatternString::ps_elem_t::clone() const | |
81 | { | |
82 | FATAL_ERROR("PatternString::ps_elem_t::clone"); | |
83 | } | |
84 | ||
85 | void PatternString::ps_elem_t::set_fullname(const string& p_fullname) | |
86 | { | |
87 | switch(kind) { | |
88 | case PSE_REF: | |
89 | case PSE_REFDSET: | |
90 | ref->set_fullname(p_fullname); | |
91 | break; | |
92 | default: | |
93 | ; | |
94 | } // switch kind | |
95 | } | |
96 | ||
97 | void PatternString::ps_elem_t::set_my_scope(Scope *p_scope) | |
98 | { | |
99 | switch(kind) { | |
100 | case PSE_REF: | |
101 | case PSE_REFDSET: | |
102 | ref->set_my_scope(p_scope); | |
103 | break; | |
104 | default: | |
105 | ; | |
106 | } // switch kind | |
107 | } | |
108 | ||
109 | void PatternString::ps_elem_t::chk_ref(PatternString::pstr_type_t pstr_type, Type::expected_value_t expected_value) | |
110 | { | |
111 | if (kind != PSE_REF) FATAL_ERROR("PatternString::ps_elem_t::chk_ref()"); | |
112 | Value* v = 0; | |
113 | Value* v_last = 0; | |
114 | Common::Assignment* ass = ref->get_refd_assignment(); | |
115 | if (!ass) | |
116 | return; | |
117 | Ttcn::FieldOrArrayRefs* t_subrefs = ref->get_subrefs(); | |
118 | Type* ref_type = ass->get_Type()->get_type_refd_last()->get_field_type( | |
119 | t_subrefs, expected_value); | |
120 | Type::typetype_t tt; | |
121 | switch (pstr_type) { | |
122 | case PatternString::CSTR_PATTERN: | |
123 | tt = Type::T_CSTR; | |
124 | if (ref_type->get_typetype() != Type::T_CSTR) | |
125 | TTCN_pattern_error("Type of the referenced %s '%s' should be " | |
126 | "'charstring'", ass->get_assname(), ref->get_dispname().c_str()); | |
127 | break; | |
128 | case PatternString::USTR_PATTERN: | |
129 | tt = ref_type->get_typetype(); | |
130 | if (tt != Type::T_CSTR && tt != Type::T_USTR) | |
131 | TTCN_pattern_error("Type of the referenced %s '%s' should be either " | |
132 | "'charstring' or 'universal charstring'", ass->get_assname(), | |
133 | ref->get_dispname().c_str()); | |
134 | break; | |
135 | default: | |
136 | FATAL_ERROR("Unknown pattern string type"); | |
137 | } | |
138 | Type* refcheckertype = Type::get_pooltype(tt); | |
139 | switch (ass->get_asstype()) { | |
140 | case Common::Assignment::A_MODULEPAR_TEMP: | |
141 | case Common::Assignment::A_VAR_TEMPLATE: | |
142 | // error reporting moved up | |
143 | break; | |
144 | case Common::Assignment::A_TEMPLATE: { | |
145 | Template* templ = ass->get_Template(); | |
146 | refcheckertype->chk_this_template_ref(templ); | |
147 | refcheckertype->chk_this_template_generic(templ, INCOMPLETE_ALLOWED, | |
148 | OMIT_ALLOWED, ANY_OR_OMIT_ALLOWED, SUB_CHK, NOT_IMPLICIT_OMIT, 0); | |
149 | switch (templ->get_templatetype()) { | |
150 | case Template::SPECIFIC_VALUE: | |
151 | v_last = templ->get_specific_value(); | |
152 | break; | |
153 | case Template::CSTR_PATTERN: { | |
154 | Ttcn::PatternString* ps = templ->get_cstr_pattern(); | |
155 | if (!ps->has_refs()) | |
156 | v_last = ps->get_value(); | |
157 | break; } | |
158 | case Template::USTR_PATTERN: { | |
159 | Ttcn::PatternString* ps = templ->get_ustr_pattern(); | |
160 | if (!ps->has_refs()) | |
161 | v_last = ps->get_value(); | |
162 | break; } | |
163 | default: | |
164 | TTCN_pattern_error("Unable to resolve referenced '%s' to character " | |
165 | "string type. '%s' template cannot be used.", | |
166 | ref->get_dispname().c_str(), templ->get_templatetype_str()); | |
167 | break; | |
168 | } | |
169 | break; } | |
170 | default: { | |
171 | Reference *t_ref = ref->clone(); | |
172 | t_ref->set_location(*ref); | |
173 | v = new Value(Value::V_REFD, t_ref); | |
174 | v->set_my_governor(refcheckertype); | |
175 | v->set_my_scope(ref->get_my_scope()); | |
176 | v->set_location(*ref); | |
177 | refcheckertype->chk_this_value(v, 0, expected_value, | |
178 | INCOMPLETE_NOT_ALLOWED, OMIT_NOT_ALLOWED, SUB_CHK); | |
179 | v_last = v->get_value_refd_last(); | |
180 | } | |
181 | } | |
182 | if (v_last && (v_last->get_valuetype() == Value::V_CSTR || | |
183 | v_last->get_valuetype() == Value::V_USTR)) { | |
184 | // the reference points to a constant | |
185 | // substitute the reference with the known value | |
186 | delete ref; | |
187 | kind = PSE_STR; | |
188 | if (v_last->get_valuetype() == Value::V_CSTR) | |
189 | str = new string(v_last->get_val_str()); | |
190 | else | |
191 | str = new string(v_last->get_val_ustr().get_stringRepr_for_pattern()); | |
192 | } | |
193 | delete v; | |
194 | } | |
195 | ||
196 | void PatternString::ps_elem_t::set_code_section | |
197 | (GovernedSimple::code_section_t p_code_section) | |
198 | { | |
199 | switch(kind) { | |
200 | case PSE_REF: | |
201 | case PSE_REFDSET: | |
202 | ref->set_code_section(p_code_section); | |
203 | break; | |
204 | default: | |
205 | ; | |
206 | } // switch kind | |
207 | } | |
208 | ||
209 | // ================================= | |
210 | // ===== PatternString | |
211 | // ================================= | |
212 | ||
213 | PatternString::PatternString(const PatternString& p) | |
214 | : Node(p), my_scope(0), pattern_type(p.pattern_type) | |
215 | { | |
216 | size_t nof_elems = p.elems.size(); | |
217 | for (size_t i = 0; i < nof_elems; i++) elems.add(p.elems[i]->clone()); | |
218 | } | |
219 | ||
220 | PatternString::ps_elem_t *PatternString::get_last_elem() const | |
221 | { | |
222 | if (elems.empty()) return 0; | |
223 | ps_elem_t *last_elem = elems[elems.size() - 1]; | |
224 | if (last_elem->kind == ps_elem_t::PSE_STR) return last_elem; | |
225 | else return 0; | |
226 | } | |
227 | ||
228 | PatternString::~PatternString() | |
229 | { | |
230 | size_t nof_elems = elems.size(); | |
231 | for (size_t i = 0; i < nof_elems; i++) delete elems[i]; | |
232 | elems.clear(); | |
233 | delete cstr_value; | |
234 | } | |
235 | ||
236 | PatternString *PatternString::clone() const | |
237 | { | |
238 | return new PatternString(*this); | |
239 | } | |
240 | ||
241 | void PatternString::set_fullname(const string& p_fullname) | |
242 | { | |
243 | Node::set_fullname(p_fullname); | |
244 | size_t nof_elems = elems.size(); | |
245 | for(size_t i = 0; i < nof_elems; i++) elems[i]->set_fullname(p_fullname); | |
246 | } | |
247 | ||
248 | void PatternString::set_my_scope(Scope *p_scope) | |
249 | { | |
250 | my_scope = p_scope; | |
251 | size_t nof_elems = elems.size(); | |
252 | for (size_t i = 0; i < nof_elems; i++) elems[i]->set_my_scope(p_scope); | |
253 | } | |
254 | ||
255 | void PatternString::set_code_section | |
256 | (GovernedSimple::code_section_t p_code_section) | |
257 | { | |
258 | size_t nof_elems = elems.size(); | |
259 | for (size_t i = 0; i < nof_elems; i++) | |
260 | elems[i]->set_code_section(p_code_section); | |
261 | } | |
262 | ||
263 | void PatternString::addChar(char c) | |
264 | { | |
265 | ps_elem_t *last_elem = get_last_elem(); | |
266 | if (last_elem) *last_elem->str += c; | |
267 | else elems.add(new ps_elem_t(ps_elem_t::PSE_STR, string(c))); | |
268 | } | |
269 | ||
270 | void PatternString::addString(const char *p_str) | |
271 | { | |
272 | ps_elem_t *last_elem = get_last_elem(); | |
273 | if (last_elem) *last_elem->str += p_str; | |
274 | else elems.add(new ps_elem_t(ps_elem_t::PSE_STR, string(p_str))); | |
275 | } | |
276 | ||
277 | void PatternString::addString(const string& p_str) | |
278 | { | |
279 | ps_elem_t *last_elem = get_last_elem(); | |
280 | if (last_elem) *last_elem->str += p_str; | |
281 | else elems.add(new ps_elem_t(ps_elem_t::PSE_STR, p_str)); | |
282 | } | |
19700695 | 283 | |
284 | void PatternString::addStringUSI(char **usi_str, const size_t size) | |
285 | { | |
286 | ustring s = ustring((const char**)usi_str, size); | |
287 | ps_elem_t *last_elem = get_last_elem(); | |
288 | if (last_elem) *last_elem->str += s.get_stringRepr_for_pattern().c_str(); | |
289 | else elems.add(new ps_elem_t(ps_elem_t::PSE_STR, s.get_stringRepr_for_pattern())); | |
290 | } | |
970ed795 EL |
291 | |
292 | void PatternString::addRef(Ttcn::Reference *p_ref) | |
293 | { | |
294 | elems.add(new ps_elem_t(ps_elem_t::PSE_REF, p_ref)); | |
295 | } | |
296 | ||
297 | void PatternString::addRefdCharSet(Ttcn::Reference *p_ref) | |
298 | { | |
299 | elems.add(new ps_elem_t(ps_elem_t::PSE_REFDSET, p_ref)); | |
300 | } | |
301 | ||
302 | string PatternString::get_full_str() const | |
303 | { | |
304 | string s; | |
305 | for(size_t i=0; i<elems.size(); i++) { | |
306 | ps_elem_t *pse=elems[i]; | |
307 | switch(pse->kind) { | |
308 | case ps_elem_t::PSE_STR: | |
309 | s+=*pse->str; | |
310 | break; | |
311 | case ps_elem_t::PSE_REFDSET: | |
312 | s+="\\N"; | |
313 | /* no break */ | |
314 | case ps_elem_t::PSE_REF: | |
315 | s+='{'; | |
316 | s+=pse->ref->get_dispname(); | |
317 | s+='}'; | |
318 | } // switch kind | |
319 | } // for | |
320 | return s; | |
321 | } | |
322 | ||
323 | void PatternString::set_pattern_type(pstr_type_t p_type) { | |
324 | pattern_type = p_type; | |
325 | } | |
326 | ||
327 | PatternString::pstr_type_t PatternString::get_pattern_type() const { | |
328 | return pattern_type; | |
329 | } | |
330 | ||
331 | bool PatternString::has_refs() const | |
332 | { | |
333 | for (size_t i = 0; i < elems.size(); i++) { | |
334 | switch (elems[i]->kind) { | |
335 | case ps_elem_t::PSE_REF: | |
336 | case ps_elem_t::PSE_REFDSET: | |
337 | return true; | |
338 | default: | |
339 | break; | |
340 | } | |
341 | } | |
342 | return false; | |
343 | } | |
344 | ||
345 | void PatternString::chk_refs(Type::expected_value_t expected_value) | |
346 | { | |
347 | for(size_t i=0; i<elems.size(); i++) { | |
348 | ps_elem_t *pse=elems[i]; | |
349 | switch(pse->kind) { | |
350 | case ps_elem_t::PSE_STR: | |
351 | break; | |
352 | case ps_elem_t::PSE_REFDSET: | |
353 | /* actually, not supported */ | |
354 | break; | |
355 | case ps_elem_t::PSE_REF: | |
356 | pse->chk_ref(pattern_type, expected_value); | |
357 | break; | |
358 | } // switch kind | |
359 | } // for | |
360 | } | |
361 | ||
362 | /** \todo implement */ | |
363 | void PatternString::chk_recursions(ReferenceChain&) | |
364 | { | |
365 | ||
366 | } | |
367 | ||
368 | void PatternString::chk_pattern() | |
369 | { | |
370 | string str; | |
371 | for (size_t i = 0; i < elems.size(); i++) { | |
372 | ps_elem_t *pse = elems[i]; | |
373 | if (pse->kind != ps_elem_t::PSE_STR) | |
374 | FATAL_ERROR("PatternString::chk_pattern()"); | |
375 | str += *pse->str; | |
376 | } | |
377 | char* posix_str = 0; | |
378 | switch (pattern_type) { | |
379 | case CSTR_PATTERN: | |
380 | posix_str = TTCN_pattern_to_regexp(str.c_str()); | |
381 | break; | |
382 | case USTR_PATTERN: | |
383 | posix_str = TTCN_pattern_to_regexp_uni(str.c_str()); | |
384 | } | |
385 | Free(posix_str); | |
386 | } | |
387 | ||
388 | bool PatternString::chk_self_ref(Common::Assignment *lhs) | |
389 | { | |
390 | for (size_t i = 0, e = elems.size(); i < e; ++i) { | |
391 | ps_elem_t *pse = elems[i]; | |
392 | switch (pse->kind) { | |
393 | case ps_elem_t::PSE_STR: | |
394 | break; | |
395 | case ps_elem_t::PSE_REFDSET: | |
396 | /* actually, not supported */ | |
397 | break; | |
398 | case ps_elem_t::PSE_REF: { | |
399 | Ttcn::Assignment *ass = pse->ref->get_refd_assignment(); | |
400 | if (ass == lhs) return true; | |
401 | break; } | |
402 | } // switch | |
403 | } | |
404 | return false; | |
405 | } | |
406 | ||
407 | void PatternString::join_strings() | |
408 | { | |
409 | // points to the previous string element otherwise it is NULL | |
410 | ps_elem_t *prev_str = 0; | |
411 | for (size_t i = 0; i < elems.size(); ) { | |
412 | ps_elem_t *pse = elems[i]; | |
413 | if (pse->kind == ps_elem_t::PSE_STR) { | |
414 | const string& str = *pse->str; | |
415 | if (str.size() > 0) { | |
416 | // the current element is a non-empty string | |
417 | if (prev_str) { | |
418 | // append str to prev_str and drop pse | |
419 | *prev_str->str += str; | |
420 | delete pse; | |
421 | elems.replace(i, 1); | |
422 | // don't increment i | |
423 | } else { | |
424 | // keep pse for the next iteration | |
425 | prev_str = pse; | |
426 | i++; | |
427 | } | |
428 | } else { | |
429 | // the current element is an empty string | |
430 | // simply drop it | |
431 | delete pse; | |
432 | elems.replace(i, 1); | |
433 | // don't increment i | |
434 | } | |
435 | } else { | |
436 | // pse is not a string | |
437 | // forget prev_str | |
438 | prev_str = 0; | |
439 | i++; | |
440 | } | |
441 | } | |
442 | } | |
443 | ||
444 | string PatternString::create_charstring_literals(Common::Module *p_mod) | |
445 | { | |
446 | /* The cast is there for the benefit of OPTIONAL<CHARSTRING>, because | |
447 | * it doesn't have operator+(). Only the first member needs the cast | |
448 | * (the others will be automagically converted to satisfy | |
449 | * CHARSTRING::operator+(const CHARSTRING&) ) */ | |
450 | string s; | |
451 | if (pattern_type == CSTR_PATTERN) | |
452 | s = "CHARSTRING_template(STRING_PATTERN, (CHARSTRING)"; | |
453 | else | |
454 | s = "UNIVERSAL_CHARSTRING_template(STRING_PATTERN, (CHARSTRING)"; | |
455 | size_t nof_elems = elems.size(); | |
456 | if (nof_elems > 0) { | |
457 | // the pattern is not empty | |
458 | for (size_t i = 0; i < nof_elems; i++) { | |
459 | if (i > 0) s += " + "; | |
460 | ps_elem_t *pse = elems[i]; | |
461 | switch (pse->kind) { | |
462 | case ps_elem_t::PSE_STR: | |
463 | s += p_mod->add_charstring_literal(*pse->str); | |
464 | break; | |
465 | case ps_elem_t::PSE_REFDSET: | |
466 | /* actually, not supported */ | |
467 | FATAL_ERROR("PatternString::create_charstring_literals()"); | |
468 | break; | |
469 | case ps_elem_t::PSE_REF: { | |
470 | expression_struct expr; | |
471 | Code::init_expr(&expr); | |
472 | pse->ref->generate_code(&expr); | |
473 | if (expr.preamble || expr.postamble) | |
474 | FATAL_ERROR("PatternString::create_charstring_literals()"); | |
475 | s += expr.expr; | |
476 | Common::Assignment* assign = pse->ref->get_refd_assignment(); | |
477 | ||
478 | if ((assign->get_asstype() == Common::Assignment::A_TEMPLATE | |
479 | || assign->get_asstype() == Common::Assignment::A_MODULEPAR_TEMP | |
480 | || assign->get_asstype() == Common::Assignment::A_VAR_TEMPLATE)) | |
481 | { | |
482 | if ((assign->get_Type()->get_typetype() == Type::T_CSTR | |
483 | || assign->get_Type()->get_typetype() == Type::T_USTR)) { | |
484 | s += ".get_single_value()"; | |
485 | } | |
486 | else { | |
487 | s += ".valueof()"; | |
488 | } | |
489 | } | |
490 | ||
491 | Code::free_expr(&expr); | |
492 | break; } | |
493 | } // switch kind | |
494 | } // for | |
495 | } else { | |
496 | // empty pattern: create an empty string literal for it | |
497 | s += p_mod->add_charstring_literal(string()); | |
498 | } | |
499 | s += ')'; | |
500 | return s; | |
501 | } | |
502 | ||
503 | void PatternString::dump(unsigned level) const | |
504 | { | |
505 | DEBUG(level, "%s", get_full_str().c_str()); | |
506 | } | |
507 | ||
508 | Common::Value* PatternString::get_value() { | |
509 | if (!cstr_value && !has_refs()) | |
510 | cstr_value = new Common::Value(Common::Value::V_CSTR, | |
511 | new string(get_full_str())); | |
512 | return cstr_value; | |
513 | } | |
3abe9331 | 514 | |
515 | char* PatternString::convert_to_json() | |
516 | { | |
517 | string pstr = get_value()->get_val_str(); | |
518 | ||
519 | // convert the pattern into an extended regular expression | |
520 | char* regex_str = NULL; | |
521 | if (CSTR_PATTERN == pattern_type) { | |
522 | regex_str = TTCN_pattern_to_regexp(pstr.c_str()); | |
523 | } | |
524 | else { // USTR_PATTERN | |
525 | // handle the unicode characters in \q{g,p,r,c} format | |
526 | string utf8str; | |
527 | for (size_t i = 0; i < pstr.size(); ++i) { | |
528 | if ('\\' == pstr[i]) { | |
529 | if ('q' == pstr[i + 1]) { | |
530 | // extract the unicode character | |
531 | unsigned int group, plane, row, cell; | |
532 | i = pstr.find('{', i + 1); | |
533 | sscanf(pstr.c_str() + i + 1, "%u", &group); | |
534 | i = pstr.find(',', i + 1); | |
535 | sscanf(pstr.c_str() + i + 1, "%u", &plane); | |
536 | i = pstr.find(',', i + 1); | |
537 | sscanf(pstr.c_str() + i + 1, "%u", &row); | |
538 | i = pstr.find(',', i + 1); | |
539 | sscanf(pstr.c_str() + i + 1, "%u", &cell); | |
540 | i = pstr.find('}', i + 1); | |
541 | ||
542 | // convert the character to UTF-8 format | |
543 | utf8str += ustring_to_uft8(ustring(group, plane, row, cell)); | |
544 | continue; | |
545 | } | |
546 | else if ('\\' == pstr[i + 1]) { | |
547 | // must be handled separately, so we don't confuse \\q with \q | |
548 | ++i; | |
549 | utf8str += '\\'; | |
550 | } | |
551 | } | |
552 | utf8str += pstr[i]; | |
553 | } | |
554 | ||
555 | // use the pattern converter for charstrings, the pattern should be in UTF-8 | |
556 | // format now (setting the 2nd parameter will make sure that no error | |
557 | // messages are displayed for extended ASCII characters) | |
558 | regex_str = TTCN_pattern_to_regexp(utf8str.c_str(), true); | |
559 | } | |
560 | ||
561 | return convert_to_json_string(regex_str); | |
562 | } | |
970ed795 EL |
563 | |
564 | } // namespace Ttcn | |
565 | ||
566 | // ================================= | |
567 | // ===== TTCN_pattern_XXXX | |
568 | // ================================= | |
569 | ||
570 | /* These functions are used by common charstring pattern parser. */ | |
571 | ||
572 | void TTCN_pattern_error(const char *fmt, ...) | |
573 | { | |
574 | char *msg=mcopystr("Charstring pattern: "); | |
575 | msg=mputstr(msg, fmt); | |
576 | va_list args; | |
577 | va_start(args, fmt); | |
578 | Common::Error_Context::report_error(0, msg, args); | |
579 | va_end(args); | |
580 | Free(msg); | |
581 | } | |
582 | ||
583 | void TTCN_pattern_warning(const char *fmt, ...) | |
584 | { | |
585 | char *msg=mcopystr("Charstring pattern: "); | |
586 | msg=mputstr(msg, fmt); | |
587 | va_list args; | |
588 | va_start(args, fmt); | |
589 | Common::Error_Context::report_warning(0, msg, args); | |
590 | va_end(args); | |
591 | Free(msg); | |
592 | } |