]> git.buserror.net Git - polintos/scott/priv.git/blob - idlcomp/types.cc
Add IFaceInfo.
[polintos/scott/priv.git] / idlcomp / types.cc
1 // Semantic actions for types and namespaces
2 //
3 // This software is copyright (c) 2006 Scott Wood <scott@buserror.net>.
4 // 
5 // This software is provided 'as-is', without any express or implied warranty.
6 // In no event will the authors or contributors be held liable for any damages
7 // arising from the use of this software.
8 // 
9 // Permission is hereby granted to everyone, free of charge, to use, copy,
10 // modify, prepare derivative works of, publish, distribute, perform,
11 // sublicense, and/or sell copies of the Software, provided that the above
12 // copyright notice and disclaimer of warranty be included in all copies or
13 // substantial portions of this software.
14
15 #include <cfloat>
16 #include <sstream>
17
18 #include <idlc.h>
19 #include <parser.h>
20 #include <lang.h>
21
22 bool Datum::verify_const()
23 {
24         assert(def.flags.field.Const);
25         
26         // TOK_INVALID should have caused an abort in an earlier pass, and
27         // TOK_DCON should have been replaced by the final constant by now.
28         
29         assert(con_type == TOK_ICON || con_type == TOK_UCON || 
30                con_type == TOK_FCON || con_type == TOK_BOOL);
31
32         if (cbt->flags.field.Float) {
33                 if (con_type == TOK_UCON)
34                         def.fcon = static_cast<double>(def.ucon);
35                 else if (con_type == TOK_ICON)
36                         def.fcon = static_cast<double>(def.icon);
37                 else if (con_type == TOK_BOOL) {
38                         yyerrorfl(name->file, name->line, 
39                                   "%s is of floating-point type, but is initialized with "
40                                   "a boolean constant.", name->c_str());
41
42                         return false;
43                 }
44
45                 con_type = TOK_FCON;
46                 
47                 if (cbt->bits == 32) {
48                         if (def.fcon > FLT_MAX || def.fcon < FLT_MIN) {
49                                 yyerrorfl(name->file, name->line, 
50                                           "%s is out of range for a single-precision FP datum.",
51                                           name->c_str());
52                                 
53                                 return false;
54                         }
55                 }
56                 
57                 const_init = true;
58                 return true;
59         }
60
61         if (con_type == TOK_FCON) {
62                 yyerrorfl(name->file, name->line, 
63                           "%s is of %s type, but is initialized with "
64                           "a floating-point constant.", 
65                           cbt->flags.field.Bool ? "boolean" : "integral",
66                           name->c_str());
67
68                 return false;
69         }
70         
71         if (cbt->flags.field.Bool) {
72                 if (con_type != TOK_BOOL) {
73                         yyerrorfl(name->file, name->line, 
74                                   "%s is of boolean type, but is initialized with "
75                                   "a non-boolean constant.", name->c_str());
76
77                         return false;
78                 }
79                 
80                 assert(def.ucon == 0 || def.ucon == 1);
81                 
82                 const_init = true;
83                 return true;
84         }
85
86         if (con_type == TOK_BOOL) {
87                 yyerrorfl(name->file, name->line, 
88                           "%s is of integral type, but is initialized with "
89                           "a boolean constant.", name->c_str());
90
91                 return false;
92         }
93
94         if (cbt->flags.field.Unsigned) {
95                 if (con_type == TOK_ICON && def.icon < 0) {
96                         yyerrorfl(name->file, name->line, 
97                                   "Initializer for \"%s\" is out of range.",
98                                   name->c_str());
99                         return false;
100                 }
101
102                 if (cbt->bits < 64) {
103                         uint64_t max = (1ULL << cbt->bits) - 1;
104                         
105                         if (def.ucon > max) {
106                                 yyerrorfl(name->file, name->line, 
107                                           "Initializer for \"%s\" is out of range.",
108                                           name->c_str());
109                                 return false;
110                         }
111                 }
112         } else {
113                 if (con_type == TOK_UCON && def.icon < 0) {
114                         yyerrorfl(name->file, name->line, 
115                                   "Initializer for \"%s\" is out of range.",
116                                   name->c_str());
117                         return false;
118                 }
119                 
120                 if (cbt->bits < 64) {
121                         // Yes, the constant should be unsigned before conversion,
122                         // as the temporary value could overflow a signed datum
123                         // before the one is subtracted if bits == 63.  It won't
124                         // matter on most machines, but you never know...
125                         
126                         int64_t max = (1ULL << (cbt->bits - 1)) - 1;
127                         
128                         if (def.icon > max || def.icon < -max + 1) {
129                                 yyerrorfl(name->file, name->line, 
130                                           "Initializer for \"%s\" is out of range.",
131                                           name->c_str());
132                                 return false;
133                         }
134                 }
135         }
136
137         const_init = true;
138         return true;
139 }
140
141 void Datum::init_const_early(Con *con)
142 {
143         if (con) {
144                 if (con->type == TOK_DCON) {
145                         assert(current_pass == 1);
146                         const_val_name = con->con.dcon;
147                 }
148
149                 def.flags.field.Const = 1;
150                 con_type = con->type;
151                 def.ucon = con->con.ucon;
152         }
153 }
154
155 void Datum::init(StrList *type, Array *ARRAY, Con *con)
156 {
157         assert(!complete);
158         
159         type_name = type;
160         cbt = NULL;
161         
162         basic = false;
163         complete = true;
164
165         set_array(ARRAY);
166         init_const_early(con);
167 }
168
169 void Datum::init(CompiledBasicType &CBT, Array *ARRAY, Con *con)
170 {
171         assert(!complete);
172         
173         def.basictype = CBT;
174         cbt = &def.basictype;
175
176         basic = true;
177         complete = true;
178         
179         set_array(ARRAY);
180         init_const_early(con);
181 }
182
183 void Datum::process_type()
184 {
185         assert(current_pass >= 4);
186
187         if (cbt)
188                 return;
189         
190         assert(!type_fq_name);
191         assert(type_sym);
192         
193         Symbol *sym = type_sym->get_concrete_sym(false);
194         type = dynamic_cast<Type *>(sym);
195         
196         if (!type) {
197                 const String *str = type_name->back();
198                 yyerrorfl(str->file, str->line, "\"%s\" is not a type.",
199                           sym->get_fq_name()->flatten()->c_str());
200
201                 throw UserError();
202         }
203         
204         BasicType *bt = dynamic_cast<BasicType *>(type->get_concrete_sym());
205         
206         if (bt) {
207                 if (!bt->def.flags.field.TypeDef)
208                         use_anon_type(bt->def);
209                 else
210                         cbt = &bt->def;
211         } else if (const_val) {
212                 const String *str = type_name->back();
213                 yyerrorfl(str->file, str->line, 
214                           "\"%s\" is not a basic type (and thus \"%s\" cannot "
215                           "be const).",
216                           type->get_fq_name()->flatten()->c_str(),
217                           name->c_str());
218                 throw UserError();
219         }
220         
221         if (!basic) {
222                 type_fq_name = type->get_fq_name()->flatten();
223                 def.type.length = type_fq_name->length();
224         }
225 }
226
227 Datum *Datum::resolve_constant_chain()
228 {
229         if (chain_traversed == traversal) {
230                 yyerrorfl(name->file, name->line, 
231                           "Initialization of \"%s\" forms an infinite loop:",
232                           get_fq_name()->flatten()->c_str());
233                 return this;
234         }
235
236         chain_traversed = traversal;
237         
238         assert(!const_val);
239         assert(!const_init);
240
241         process_type();
242
243         if (const_val_sym) {
244                 const_val = dynamic_cast<Datum *>(const_val_sym->get_concrete_sym());
245                 
246                 if (!const_val) {
247                         const String *str = const_val_name->back();
248                         yyerrorfl(str->file, str->line, "\"%s\" is not a datum.",
249                                   const_val_sym->get_fq_name()->flatten()->c_str());
250                         throw UserError();
251                 }
252                 
253                 if (!const_val->def.flags.field.Const) {
254                         const String *str = const_val_name->back();
255                         yyerrorfl(str->file, str->line, "\"%s\" is not a constant.",
256                                   const_val->get_fq_name()->flatten()->c_str());
257                         throw UserError();
258                 }
259         
260                 if (!const_val->const_init) {
261                         Datum *d = const_val->resolve_constant_chain();
262                         if (d) {
263                                 yyerrorfl(name->file, name->line, "...initializes \"%s\"",
264                                           get_fq_name()->flatten()->c_str());
265                 
266                                 if (d == this)
267                                         throw UserError();
268                                 
269                                 return d;
270                         }
271                 }
272                 
273                 assert(const_val->const_init);
274         
275                 con_type = const_val->con_type;
276                 def.ucon = const_val->def.ucon;
277         }
278
279         if (!verify_const())
280                 throw UserError();
281
282         assert(const_init);
283         return NULL;
284 }
285
286 // FIXME: Check for inline struct loops.
287 void Datum::final_analysis()
288 {
289         Struct *str = dynamic_cast<Struct *>(*type);
290         if (str && str->is_inline())
291                 set_inline();
292
293         if (array) {
294                 array->final_analysis();
295                 def.basictype.array = array->ca;
296         } else {
297                 def.basictype.array.bounds[0] = 0;
298                 def.basictype.array.bounds[1] = 0;
299         }
300         
301         BitField *bfparent = dynamic_cast<BitField *>(get_ns());
302         if (bfparent) {
303                 assert(!array);
304                 int defsize;
305                 
306                 if (basic)
307                         defsize = 1;
308                 else
309                         defsize = type->get_default_bf_size();
310                 
311                 if (defsize == -1) {
312                         yyerrorfl(name->file, name->line,
313                                   "\"%s\" may not be the named type of bitfield entry "
314                                   "\"%s\", as it is not a bitfield or enumeration.",
315                                   type->get_fq_name()->flatten()->c_str(),
316                                   get_fq_name()->flatten()->c_str());
317                 } else if (def.icon == -1) {
318                         def.ucon = defsize;
319                 } else if (def.ucon < (unsigned int)defsize) {
320                         yyerrorfl(name->file, name->line,
321                                   "Explicit size %llu on \"%s\" is too small for "
322                                   "type \"%s\", which has a minimum size of %u",
323                                   (unsigned long long)def.ucon,
324                                   get_fq_name()->flatten()->c_str(),
325                                   type->get_fq_name()->flatten()->c_str(), defsize);
326                 }
327         }
328 }
329
330 void Struct::add(Symbol *sym, bool from_import)
331 {
332         Datum *datum = dynamic_cast<Datum *>(sym);
333         
334         if (!datum && !dynamic_cast<Alias *>(sym) && 
335             !dynamic_cast<Type *>(sym) &&
336             !dynamic_cast<Method *>(sym))
337                 throw InvalidArgument();
338         
339         NameSpace::add(sym, from_import);
340
341         if (!from_import && datum && !datum->def.flags.field.Const)
342                 add_elem(datum);
343 }
344
345 void Struct::add_elem(Datum *d)
346 {
347         entries.push_back(d);
348         def.num_entries++;
349 }
350
351 Param *Param::declare(const String *name, NameSpace *parent,
352                            StrList *TYPE, CompiledParam::Flags flags,
353                            Array *array)
354 {
355         assert(parent);
356         assert(dynamic_cast<Method *>(parent));
357         
358         Param *p = new Param(name);
359         p->init(TYPE, flags, array);
360         
361         parent->add_user(p);
362         return p;
363 }
364
365 Method *Method::declare(const String *name, NameSpace *parent)
366 {
367         assert(parent);
368         assert(dynamic_cast<Interface *>(parent));
369
370         Method *m = new Method(name);
371
372         parent->add_user(m);
373         return m;
374 }
375
376 void Interface::add(Symbol *sym, bool from_import)
377 {
378         Datum *datum = dynamic_cast<Datum *>(sym);
379         Method *method = dynamic_cast<Method *>(sym);
380
381         if (!datum && !method && 
382             !dynamic_cast<Alias *>(sym) && 
383             !dynamic_cast<Type *>(sym))
384                 throw InvalidArgument();
385
386         if (datum && !datum->def.flags.field.Const)
387                 throw InvalidArgument();
388         
389         NameSpace::add(sym, from_import);
390         
391         if (!from_import && method)
392                 add_elem(method);
393 }
394
395 // FIXME: check for duplicate and looping inheritance
396 void Interface::add_elem(Method *method)
397 {
398         methods.push_back(method);
399         def.num_methods++;
400 }
401
402 void Enum::add(Symbol *sym, bool from_import)
403 {
404         Datum *datum = dynamic_cast<Datum *>(sym);
405
406         // It also must be const unsigned, but the const won't
407         // have been initialized yet.
408
409         if (!datum)
410                 throw InvalidArgument();
411         
412         NameSpace::add(sym, from_import);
413         
414         if (!from_import)
415                 add_elem(datum);
416 }
417
418 void Enum::add_elem(Datum *datum)
419 {
420         entries.push_back(datum);
421         def.num_entries++;
422 }
423
424 void BitField::add(Symbol *sym, bool from_import)
425 {
426         Datum *datum = dynamic_cast<Datum *>(sym);
427
428         if (!datum && !dynamic_cast<Enum *>(sym) &&
429             !dynamic_cast<BitField *>(sym))
430                 throw InvalidArgument();
431
432         NameSpace::add(sym, from_import);
433
434         if (!from_import && datum)
435                 add_elem(datum);
436 }
437
438 void BitField::add_elem(Datum *datum)
439 {
440         entries.push_back(datum);
441         def.num_entries++;
442 }
443
444 void Method::add(Symbol *sym, bool from_import)
445 {
446         Param *param = dynamic_cast<Param *>(sym);
447
448         if (!param)
449                 throw InvalidArgument();
450
451         NameSpace::add(sym, from_import);
452         if (!from_import)
453                 add_elem(param);
454 }
455
456 void Method::add_elem(Param *param)
457 {
458         entries.push_back(param);
459         def.num_entries++;
460 }
461
462 // Use this rather than lookup_sym if you want to check for built-in types.
463 // If only constructed types are valid, use lookup_sym.  If basic_types_only
464 // is set, then NULL will be returned if sl does not describe a built-in
465 // basic type (such as int, flong, octet, etc).  This option is used so that
466 // the first-pass code in idlparse.y can decide whether to create a BasicType
467 // or an Alias/TypeDef.
468
469 Type *lookup_type(StrList *sl, NameSpace *ctx, bool basic_types_only)
470 {
471         Type *type;
472         int token = sl->front()->token;
473
474         // These tokens aren't valid types, but the names can be
475         // used as identifiers.
476         switch (token) {
477                 case TOK_ASYNC:
478                 case TOK_INOUT:
479                 case TOK_OUT:
480                         token = TOK_IDENT;
481         }
482         
483         // Treat it as a user-defined type if it's either not a reserved word,
484         // or if there are multiple components.
485
486         if (token == TOK_IDENT || sl->front() != sl->back()) {
487                 if (basic_types_only)
488                         return false;
489         
490                 Symbol *sym = lookup_sym(toplevel, sl, ctx);
491                 type = dynamic_cast<Type *>(sym->get_concrete_sym(false));
492                 
493                 if (!type) {
494                         const String *str = sl->back();
495                         yyerrorfl(str->file, str->line, "\"%s\" is not a type.",
496                                   sym->get_fq_name()->flatten()->c_str());
497
498                         throw UserError();
499                 }
500         } else {
501                 CompiledBasicType cbt;
502                 memset(&cbt, 0, sizeof(cbt));
503                 
504                 switch (token) {
505                         case TOK_BOOL:
506                                 cbt.bits = 0;
507                                 cbt.flags.field.Bool = 1;
508                                 break;
509                         
510                         case TOK_OCTET:
511                         case TOK_CHAR:
512                                 cbt.bits = 8;
513                                 cbt.flags.field.Unsigned = 1;
514                                 break;
515                         
516                         case TOK_SHORT:
517                                 cbt.bits = 16;
518                                 break;
519                         
520                         case TOK_INT:
521                                 cbt.bits = 32;
522                                 break;
523                         
524                         case TOK_LONG:
525                                 cbt.bits = 64;
526                                 break;
527                         
528                         case TOK_USHORT:
529                                 cbt.bits = 16;
530                                 cbt.flags.field.Unsigned = 1;
531                                 break;
532                         
533                         case TOK_UINT:
534                                 cbt.bits = 32;
535                                 cbt.flags.field.Unsigned = 1;
536                                 break;
537                         
538                         case TOK_ULONG:
539                                 cbt.bits = 64;
540                                 cbt.flags.field.Unsigned = 1;
541                                 break;
542                         
543                         case TOK_FSHORT:
544                                 cbt.bits = 32;
545                                 cbt.flags.field.Float = 1;
546                                 break;
547                         
548                         case TOK_FLONG:
549                                 cbt.bits = 64;
550                                 cbt.flags.field.Float = 1;
551                                 break;
552                         
553                         default:
554                                 fprintf(stderr, "token %d\n", token);
555                                 BUG();
556                 }
557                 
558                 type = BasicType::declare(sl->front(), NULL, cbt);
559         }
560         
561         return type;
562 }
563
564 void declare_data(NameSpace *ns, StrList *ids, StrList *type,
565                   Array *array, StrList *attr)
566 {
567         bool is_inline = false;
568         bool is_immutable = false;
569         
570         if (attr) {
571                 for (StrList::iterator i = attr->begin(); i != attr->end(); ++i) {
572                         const String *this_attr = *i;
573         
574                         switch (this_attr->token) {
575                                 case TOK_INLINE:
576                                         is_inline = true;
577                                         break;
578                                 
579                                 case TOK_IMMUTABLE:
580                                         is_immutable = true;
581                                         break;
582                                 
583                                 default:
584                                         yyerrorfl(this_attr->file, this_attr->line,
585                                                   "Invalid attribute \"%s\"", (*i)->c_str());
586                         }
587                 }
588         }
589         
590         for (StrList::iterator i = ids->begin(); i != ids->end(); ++i) {
591                 try {
592                         Datum *d = Datum::declare(*i, ns, type, array);
593                         
594                         if (is_inline)
595                                 d->set_inline();
596                         
597                         if (is_immutable)
598                                 d->set_immutable();
599                 }
600         
601                 catch (UserError) {
602                         // An error has already been displayed, but try to
603                         // continue and find more errors.
604                 }
605         }
606 }
607
608 void declare_aliases(NameSpace *ns, StrList *ids, StrList *type,
609                      bool is_typedef)
610 {
611         StrList::iterator i;
612
613         for (i = ids->begin(); i != ids->end(); ++i) {
614                 try {
615                         if (is_typedef)
616                                 TypeDef::declare(*i, ns, type);
617                         else
618                                 Alias::declare(*i, ns, type);
619                 }
620         
621                 catch (UserError) {
622                         // An error has already been displayed, but try to
623                         // continue and find more errors.
624                 }
625         }
626 }
627
628 void declare_basictypes(NameSpace *ns, StrList *ids, 
629                         BasicType *type, bool is_typedef)
630 {
631         assert(!type->def.flags.field.TypeDef);
632         StrList::iterator i;
633         
634         for (i = ids->begin(); i != ids->end(); ++i) {
635                 try {
636                         BasicType *bt =
637                                 BasicType::declare(*i, ns, type->def);
638                         
639                         bt->def.flags.field.TypeDef = is_typedef;
640                 }
641         
642                 catch (UserError) {
643                         // An error has already been displayed, but try to
644                         // continue and find more errors.
645                 }
646         }
647 }
648
649 Array::Array(NameSpace *LOOKUP_CTX)
650 {
651         lookup_ctx = LOOKUP_CTX;
652         
653         for (int i = 0; i < 2; i++) {
654                 cons[i].con.ucon = 0;
655                 cons[i].type = TOK_UCON;
656         }
657 }
658
659 void Array::set_unbounded()
660 {
661         for (int i = 0; i < 2; i++)
662                 cons[i].type = TOK_NONE;
663 }
664
665 void Array::final_analysis()
666 {
667         for (int i = 0; i < 2; i++) {
668                 if (cons[i].type == TOK_NONE) {
669                         if (i == 0)
670                                 ca.bounds[0] = 0;
671                         else
672                                 ca.bounds[1] = -1;
673                         
674                         continue;
675                 }
676         
677                 if (cons[i].type == TOK_DCON) {
678                         Symbol *sym = lookup_sym(toplevel, dcons[i], lookup_ctx);
679                         datums[i] = dynamic_cast<Datum *>(sym);
680                         if (!datums[i]) {
681                                 const String *str = dcons[i]->back();
682                                 yyerrorfl(str->file, str->line, "\"%s\" is not a Datum.",
683                                           sym->get_fq_name()->flatten()->c_str());
684                                 continue;
685                         }
686                         
687                         ca.bounds[i] = datums[i]->get_ucon(dcons[i]->back());
688                         cons[i].type = datums[i]->con_type;
689                 } else {
690                         ca.bounds[i] = cons[i].con.ucon;
691                 }
692                 
693                 if (cons[i].type == TOK_FCON) {
694                         yyerrorfl(strs[i]->file, strs[i]->line, 
695                                   "\"%s\" is not an integral Datum.",
696                                   strs[i]->c_str());
697                         throw UserError();
698                 }
699
700                 if (ca.bounds[i] < 0) {
701                         yyerrorfl(strs[i]->file, strs[i]->line, 
702                                   "\"%s\" %s.", strs[i]->c_str(),
703                                   cons[i].type == TOK_UCON ?
704                                   "does not fit in a signed 64-bit integer" :
705                                   "is negative");
706                         throw UserError();
707                 }
708         }
709         
710         if (ca.bounds[1] >= 0 && ca.bounds[0] > ca.bounds[1]) {
711                 yyerrorfl(strs[0]->file, strs[0]->line,
712                           "\"%s\" is larger than \"%s\".",
713                           strs[0]->c_str(), strs[1]->c_str());
714                 throw UserError();
715         }
716 }
717
718 void Array::set_bound(Con &con, int bound)
719 {
720         assert(current_pass == 1);
721
722         if (con.type == TOK_FCON) {
723                 yyerrorf("Array limits must be integers.");
724                 throw UserError();
725         }
726         
727         if (con.type == TOK_ICON && con.con.icon <= 0) {
728                 yyerrorf("Array limits must be positive");
729                 throw UserError();
730         }
731
732         if (con.type == TOK_UCON && con.con.icon <= 0) {
733                 yyerrorf("Array limits must fit in a signed 64-bit integer");
734                 throw UserError();
735         }
736         
737         cons[bound] = con;
738         
739         if (con.type == TOK_DCON) {
740                 assert(con.con.dcon);
741                 dcons[bound] = con.con.dcon;
742                 strs[bound] = con.con.dcon->flatten();
743         } else {
744                 std::ostringstream ss(std::ostringstream::out);
745                 
746                 switch (con.type) {
747                         case TOK_FCON:
748                                 ss << con.con.fcon;
749                                 break;
750                         
751                         case TOK_UCON:
752                                 ss << con.con.ucon;
753                                 break;
754                         
755                         case TOK_ICON:
756                                 ss << con.con.icon;
757                                 break;
758                         
759                         case TOK_NONE:
760                                 if (bound == 0)
761                                         ss << "0";
762
763                                 break;
764                         
765                         default:
766                                 BUG();
767                 }
768                 
769                 strs[bound] = new String(ss.str().c_str(), cur_input_file, 
770                                          curline, con.type);
771         }
772 }
773
774
775 void UserNameSpace::output_lang(LangCallback *lcb, int arg1, void *arg2)
776 {
777         lcb->output(this, arg1, arg2);
778 }
779
780 void BasicType::output_lang(LangCallback *lcb, int arg1, void *arg2)
781 {
782         lcb->output(this, arg1, arg2);
783 }
784
785 void Interface::output_lang(LangCallback *lcb, int arg1, void *arg2)
786 {
787         lcb->output(this, arg1, arg2);
788 }
789
790 void Struct::output_lang(LangCallback *lcb, int arg1, void *arg2)
791 {
792         lcb->output(this, arg1, arg2);
793 }
794
795 void BitField::output_lang(LangCallback *lcb, int arg1, void *arg2)
796 {
797         lcb->output(this, arg1, arg2);
798 }
799
800 void Enum::output_lang(LangCallback *lcb, int arg1, void *arg2)
801 {
802         lcb->output(this, arg1, arg2);
803 }
804
805 void Alias::output_lang(LangCallback *lcb, int arg1, void *arg2)
806 {
807         lcb->output(this, arg1, arg2);
808 }
809
810 void TypeDef::output_lang(LangCallback *lcb, int arg1, void *arg2)
811 {
812         lcb->output(this, arg1, arg2);
813 }
814
815 void Datum::output_lang(LangCallback *lcb, int arg1, void *arg2)
816 {
817         if (def.flags.field.Const)
818                 lcb->output(this, arg1, arg2);
819 }
820
821 void Method::output_lang(LangCallback *lcb, int arg1, void *arg2)
822 {
823 }
824
825 void Param::output_lang(LangCallback *lcb, int arg1, void *arg2)
826 {
827 }
828
829 int Interface::all_supers_traversal;