3 * Copyright (c) 2007 Brandon L Black
5 * You may distribute under the terms of either the GNU General Public
6 * License or the Artistic License, as specified in the README file.
13 These functions are related to the method resolution order of perl classes
22 Perl_mro_meta_init(pTHX_ HV* stash)
28 assert(!(HvAUX(stash)->xhv_mro_meta));
29 Newxz(newmeta, sizeof(struct mro_meta), char);
30 HvAUX(stash)->xhv_mro_meta = (struct mro_meta*)newmeta;
31 ((struct mro_meta*)newmeta)->sub_generation = 1;
33 /* Manually flag UNIVERSAL as being universal.
34 This happens early in perl booting (when universal.c
35 does the newXS calls for UNIVERSAL::*), and infects
36 other packages as they are added to UNIVERSAL's MRO
38 if(HvNAMELEN_get(stash) == 9
39 && strEQ(HEK_KEY(HvAUX(stash)->xhv_name), "UNIVERSAL")) {
40 HvMROMETA(stash)->is_universal = 1;
46 #if defined(USE_ITHREADS)
48 /* for sv_dup on new threads */
50 Perl_mro_meta_dup(pTHX_ struct mro_meta* smeta, CLONE_PARAMS* param)
53 struct mro_meta* newmeta;
57 Newx(newmeta_void, sizeof(struct mro_meta), char);
58 newmeta = (struct mro_meta*)newmeta_void;
60 newmeta->mro_which = smeta->mro_which;
61 newmeta->sub_generation = smeta->sub_generation;
62 newmeta->is_universal = smeta->is_universal;
63 newmeta->fake = smeta->fake;
64 newmeta->mro_linear_dfs = smeta->mro_linear_dfs
65 ? (AV*) SvREFCNT_inc(sv_dup((SV*)smeta->mro_linear_dfs, param))
67 newmeta->mro_linear_c3 = smeta->mro_linear_c3
68 ? (AV*) SvREFCNT_inc(sv_dup((SV*)smeta->mro_linear_c3, param))
70 newmeta->mro_isarev = smeta->mro_isarev
71 ? (HV*) SvREFCNT_inc(sv_dup((SV*)smeta->mro_isarev, param))
73 newmeta->mro_nextmethod = smeta->mro_nextmethod
74 ? (HV*) SvREFCNT_inc(sv_dup((SV*)smeta->mro_nextmethod, param))
80 #endif /* USE_ITHREADS */
83 =for apidoc mro_get_linear_isa_dfs
85 Returns the Depth-First Search linearization of @ISA
86 the given stash. The return value is a read-only AV*.
87 C<level> should be 0 (it is used internally in this
88 function's recursion).
93 Perl_mro_get_linear_isa_dfs(pTHX_ HV *stash, I32 level)
104 const char* stashname;
105 struct mro_meta* meta;
108 assert(HvAUX(stash));
110 stashname = HvNAME_get(stash);
113 "Can't linearize anonymous symbol table");
116 Perl_croak(aTHX_ "Recursive inheritance detected in package '%s'",
119 meta = HvMROMETA(stash);
120 if((retval = meta->mro_linear_dfs)) {
121 /* return cache if valid */
125 /* not in cache, make a new one */
127 av_push(retval, newSVpv(stashname, 0)); /* add ourselves at the top */
129 gvp = (GV**)hv_fetchs(stash, "ISA", FALSE);
130 av = (gvp && (gv = *gvp) && isGV_with_GP(gv)) ? GvAV(gv) : NULL;
133 HV* stored = (HV*)sv_2mortal((SV*)newHV());
135 items = AvFILLp(av) + 1;
137 SV* const sv = *svp++;
138 HV* const basestash = gv_stashsv(sv, 0);
141 if(!hv_exists_ent(stored, sv, 0)) {
142 av_push(retval, newSVsv(sv));
143 hv_store_ent(stored, sv, &PL_sv_undef, 0);
147 subrv = mro_get_linear_isa_dfs(basestash, level + 1);
148 subrv_p = AvARRAY(subrv);
149 subrv_items = AvFILLp(subrv) + 1;
150 while(subrv_items--) {
151 SV* subsv = *subrv_p++;
152 if(!hv_exists_ent(stored, subsv, 0)) {
153 av_push(retval, newSVsv(subsv));
154 hv_store_ent(stored, subsv, &PL_sv_undef, 0);
161 SvREADONLY_on(retval);
162 meta->mro_linear_dfs = retval;
167 =for apidoc mro_get_linear_isa_c3
169 Returns the C3 linearization of @ISA
170 the given stash. The return value is a read-only AV*.
171 C<level> should be 0 (it is used internally in this
172 function's recursion).
178 Perl_mro_get_linear_isa_c3(pTHX_ HV* stash, I32 level)
184 const char* stashname;
185 STRLEN stashname_len;
186 struct mro_meta* meta;
189 assert(HvAUX(stash));
191 stashname = HvNAME_get(stash);
192 stashname_len = HvNAMELEN_get(stash);
195 "Can't linearize anonymous symbol table");
198 Perl_croak(aTHX_ "Recursive inheritance detected in package '%s'",
201 meta = HvMROMETA(stash);
202 if((retval = meta->mro_linear_c3)) {
203 /* return cache if valid */
207 /* not in cache, make a new one */
210 av_push(retval, newSVpvn(stashname, stashname_len)); /* us first */
212 gvp = (GV**)hv_fetchs(stash, "ISA", FALSE);
213 isa = (gvp && (gv = *gvp) && isGV_with_GP(gv)) ? GvAV(gv) : NULL;
215 if(isa && AvFILLp(isa) >= 0) {
218 HV* tails = (HV*)sv_2mortal((SV*)newHV());
219 AV* seqs = (AV*)sv_2mortal((SV*)newAV());
220 I32 items = AvFILLp(isa) + 1;
221 SV** isa_ptr = AvARRAY(isa);
224 SV* isa_item = *isa_ptr++;
225 HV* isa_item_stash = gv_stashsv(isa_item, 0);
226 if(!isa_item_stash) {
228 av_push(isa_lin, newSVsv(isa_item));
231 isa_lin = mro_get_linear_isa_c3(isa_item_stash, level + 1); /* recursion */
233 av_push(seqs, (SV*)av_make(AvFILLp(isa_lin)+1, AvARRAY(isa_lin)));
235 av_push(seqs, (SV*)av_make(AvFILLp(isa)+1, AvARRAY(isa)));
237 seqs_ptr = AvARRAY(seqs);
238 seqs_items = AvFILLp(seqs) + 1;
239 while(seqs_items--) {
240 AV* seq = (AV*)*seqs_ptr++;
241 I32 seq_items = AvFILLp(seq);
243 SV** seq_ptr = AvARRAY(seq) + 1;
245 SV* seqitem = *seq_ptr++;
246 HE* he = hv_fetch_ent(tails, seqitem, 0, 0);
248 hv_store_ent(tails, seqitem, newSViv(1), 0);
265 SV** avptr = AvARRAY(seqs);
266 items = AvFILLp(seqs)+1;
270 if(AvFILLp(seq) < 0) continue;
271 svp = av_fetch(seq, 0, 0);
275 if((tail_entry = hv_fetch_ent(tails, cand, 0, 0))
276 && (val = HeVAL(tail_entry))
279 winner = newSVsv(cand);
280 av_push(retval, winner);
282 if(!sv_cmp(seqhead, winner)) {
284 /* this is basically shift(@seq) in void context */
285 SvREFCNT_dec(*AvARRAY(seq));
286 *AvARRAY(seq) = &PL_sv_undef;
287 AvARRAY(seq) = AvARRAY(seq) + 1;
291 if(AvFILLp(seq) < 0) continue;
292 svp = av_fetch(seq, 0, 0);
294 tail_entry = hv_fetch_ent(tails, seqhead, 0, 0);
295 val = HeVAL(tail_entry);
301 SvREFCNT_dec(retval);
302 Perl_croak(aTHX_ "Inconsistent hierarchy during C3 merge of class '%s': "
303 "merging failed on parent '%"SVf"'", stashname, SVfARG(cand));
308 SvREADONLY_on(retval);
309 meta->mro_linear_c3 = retval;
314 =for apidoc mro_get_linear_isa
316 Returns either C<mro_get_linear_isa_c3> or
317 C<mro_get_linear_isa_dfs> for the given stash,
318 dependant upon which MRO is in effect
319 for that stash. The return value is a
325 Perl_mro_get_linear_isa(pTHX_ HV *stash)
327 struct mro_meta* meta;
329 assert(HvAUX(stash));
331 meta = HvMROMETA(stash);
332 if(meta->mro_which == MRO_DFS) {
333 return mro_get_linear_isa_dfs(stash, 0);
334 } else if(meta->mro_which == MRO_C3) {
335 return mro_get_linear_isa_c3(stash, 0);
337 Perl_croak(aTHX_ "Internal error: invalid MRO!");
342 =for apidoc mro_isa_changed_in
344 Takes the neccesary steps (cache invalidations, mostly)
345 when the @ISA of the given package has changed. Invoked
346 by the C<setisa> magic, should not need to invoke directly.
351 Perl_mro_isa_changed_in(pTHX_ HV* stash)
359 struct mro_meta* meta;
362 stashname = HvNAME_get(stash);
364 /* wipe out the cached linearizations for this stash */
365 meta = HvMROMETA(stash);
366 SvREFCNT_dec((SV*)meta->mro_linear_dfs);
367 SvREFCNT_dec((SV*)meta->mro_linear_c3);
368 meta->mro_linear_dfs = NULL;
369 meta->mro_linear_c3 = NULL;
371 /* Wipe the global method cache if this package
372 is UNIVERSAL or one of its parents */
373 if(meta->is_universal)
376 /* Wipe the local method cache otherwise */
378 meta->sub_generation++;
380 /* wipe next::method cache too */
381 if(meta->mro_nextmethod) hv_clear(meta->mro_nextmethod);
383 /* Iterate the isarev (classes that are our children),
384 wiping out their linearization and method caches */
385 if((isarev = meta->mro_isarev)) {
387 while((iter = hv_iternext(isarev))) {
388 SV* revkey = hv_iterkeysv(iter);
389 HV* revstash = gv_stashsv(revkey, 0);
390 struct mro_meta* revmeta = HvMROMETA(revstash);
391 SvREFCNT_dec((SV*)revmeta->mro_linear_dfs);
392 SvREFCNT_dec((SV*)revmeta->mro_linear_c3);
393 revmeta->mro_linear_dfs = NULL;
394 revmeta->mro_linear_c3 = NULL;
395 if(!meta->is_universal)
396 revmeta->sub_generation++;
397 if(revmeta->mro_nextmethod)
398 hv_clear(revmeta->mro_nextmethod);
402 /* we're starting at the 2nd element, skipping ourselves here */
403 linear_mro = mro_get_linear_isa(stash);
404 svp = AvARRAY(linear_mro) + 1;
405 items = AvFILLp(linear_mro);
407 SV* const sv = *svp++;
408 struct mro_meta* mrometa;
411 HV* mrostash = gv_stashsv(sv, 0);
413 mrostash = gv_stashsv(sv, GV_ADD);
415 We created the package on the fly, so
416 that we could store isarev information.
417 This flag lets gv_fetchmeth know about it,
418 so that it can still generate the very useful
419 "Can't locate package Foo for @Bar::ISA" warning.
421 HvMROMETA(mrostash)->fake = 1;
424 mrometa = HvMROMETA(mrostash);
425 mroisarev = mrometa->mro_isarev;
427 /* is_universal is viral */
428 if(meta->is_universal)
429 mrometa->is_universal = 1;
432 mroisarev = mrometa->mro_isarev = newHV();
434 if(!hv_exists(mroisarev, stashname, strlen(stashname)))
435 hv_store(mroisarev, stashname, strlen(stashname), &PL_sv_yes, 0);
439 while((iter = hv_iternext(isarev))) {
440 SV* revkey = hv_iterkeysv(iter);
441 if(!hv_exists_ent(mroisarev, revkey, 0))
442 hv_store_ent(mroisarev, revkey, &PL_sv_yes, 0);
449 =for apidoc mro_method_changed_in
451 Like C<mro_isa_changed_in>, but invalidates method
452 caching on any child classes of the given stash, so
453 that they might notice the changes in this one.
455 Ideally, all instances of C<PL_sub_generation++> in
456 the perl source should be replaced by calls to this.
457 Some already are, but some are more difficult to
460 Perl has always had problems with method caches
461 getting out of sync when one directly manipulates
462 stashes via things like C<%{Foo::} = %{Bar::}> or
463 C<${Foo::}{bar} = ...> or the equivalent. If
464 you do this in core or XS code, call this afterwards
465 on the destination stash to get things back in sync.
467 If you're doing such a thing from pure perl, use
468 C<mro::method_changed_in(classname)>, which
474 Perl_mro_method_changed_in(pTHX_ HV *stash)
476 struct mro_meta* meta = HvMROMETA(stash);
480 /* If stash is UNIVERSAL, or one of UNIVERSAL's parents,
481 invalidate all method caches globally */
482 if(meta->is_universal) {
487 /* else, invalidate the method caches of all child classes,
489 if((isarev = meta->mro_isarev)) {
491 while((iter = hv_iternext(isarev))) {
492 SV* revkey = hv_iterkeysv(iter);
493 HV* revstash = gv_stashsv(revkey, 0);
494 struct mro_meta* mrometa = HvMROMETA(revstash);
495 mrometa->sub_generation++;
496 if(mrometa->mro_nextmethod)
497 hv_clear(mrometa->mro_nextmethod);
502 /* These two are static helpers for next::method and friends,
503 and re-implement a bunch of the code from pp_caller() in
504 a more efficient manner for this particular usage.
508 __dopoptosub_at(const PERL_CONTEXT *cxstk, I32 startingblock) {
510 for (i = startingblock; i >= 0; i--) {
511 if(CxTYPE((PERL_CONTEXT*)(&cxstk[i])) == CXt_SUB) return i;
517 __nextcan(pTHX_ SV* self, I32 throw_nomethod)
520 register const PERL_CONTEXT *ccstack = cxstack;
521 const PERL_SI *top_si = PL_curstackinfo;
525 const char *fq_subname;
527 STRLEN fq_subname_len;
528 STRLEN stashname_len;
536 GV* candidate = NULL;
540 struct mro_meta* selfmeta;
544 if(sv_isobject(self))
545 selfstash = SvSTASH(SvRV(self));
547 selfstash = gv_stashsv(self, 0);
551 hvname = HvNAME_get(selfstash);
553 Perl_croak(aTHX_ "Can't use anonymous symbol table for method lookup");
555 cxix = __dopoptosub_at(cxstack, cxstack_ix);
557 /* This block finds the contextually-enclosing fully-qualified subname,
558 much like looking at (caller($i))[3] until you find a real sub that
561 /* we may be in a higher stacklevel, so dig down deeper */
563 if(top_si->si_type == PERLSI_MAIN)
564 Perl_croak(aTHX_ "next::method/next::can/maybe::next::method must be used in method context");
565 top_si = top_si->si_prev;
566 ccstack = top_si->si_cxstack;
567 cxix = __dopoptosub_at(ccstack, top_si->si_cxix);
570 if(CxTYPE((PERL_CONTEXT*)(&ccstack[cxix])) != CXt_SUB
571 || (PL_DBsub && GvCV(PL_DBsub) && ccstack[cxix].blk_sub.cv == GvCV(PL_DBsub))) {
572 cxix = __dopoptosub_at(ccstack, cxix - 1);
577 const I32 dbcxix = __dopoptosub_at(ccstack, cxix - 1);
578 if (PL_DBsub && GvCV(PL_DBsub) && dbcxix >= 0 && ccstack[dbcxix].blk_sub.cv == GvCV(PL_DBsub)) {
579 if(CxTYPE((PERL_CONTEXT*)(&ccstack[dbcxix])) != CXt_SUB) {
586 cvgv = CvGV(ccstack[cxix].blk_sub.cv);
589 cxix = __dopoptosub_at(ccstack, cxix - 1);
593 /* we found a real sub here */
594 sv = sv_2mortal(newSV(0));
596 gv_efullname3(sv, cvgv, NULL);
598 fq_subname = SvPVX(sv);
599 fq_subname_len = SvCUR(sv);
601 subname = strrchr(fq_subname, ':');
603 Perl_croak(aTHX_ "next::method/next::can/maybe::next::method cannot find enclosing method");
606 subname_len = fq_subname_len - (subname - fq_subname);
607 if(subname_len == 8 && strEQ(subname, "__ANON__")) {
608 cxix = __dopoptosub_at(ccstack, cxix - 1);
614 /* If we made it to here, we found our context */
616 selfmeta = HvMROMETA(selfstash);
617 if(!(nmcache = selfmeta->mro_nextmethod)) {
618 nmcache = selfmeta->mro_nextmethod = newHV();
621 if((cache_entry = hv_fetch_ent(nmcache, sv, 0, 0))) {
622 SV* val = HeVAL(cache_entry);
623 if(val == &PL_sv_undef) {
625 Perl_croak(aTHX_ "No next::method '%s' found for %s", subname, hvname);
630 /* beyond here is just for cache misses, so perf isn't as critical */
632 stashname_len = subname - fq_subname - 2;
633 stashname = sv_2mortal(newSVpvn(fq_subname, stashname_len));
635 linear_av = mro_get_linear_isa_c3(selfstash, 0); /* has ourselves at the top of the list */
637 linear_svp = AvARRAY(linear_av);
638 items = AvFILLp(linear_av) + 1;
641 linear_sv = *linear_svp++;
643 if(sv_eq(linear_sv, stashname))
649 linear_sv = *linear_svp++;
651 curstash = gv_stashsv(linear_sv, FALSE);
653 if (!curstash || (HvMROMETA(curstash)->fake && !HvFILL(curstash))) {
654 if (ckWARN(WARN_MISC))
655 Perl_warner(aTHX_ packWARN(WARN_MISC), "Can't locate package %"SVf" for @%s::ISA",
656 (void*)linear_sv, hvname);
662 gvp = (GV**)hv_fetch(curstash, subname, subname_len, 0);
668 if (SvTYPE(candidate) != SVt_PVGV)
669 gv_init(candidate, curstash, subname, subname_len, TRUE);
670 if (SvTYPE(candidate) == SVt_PVGV && (cand_cv = GvCV(candidate)) && !GvCVGEN(candidate)) {
671 SvREFCNT_inc_simple_void_NN((SV*)cand_cv);
672 hv_store_ent(nmcache, newSVsv(sv), (SV*)cand_cv, 0);
678 hv_store_ent(nmcache, newSVsv(sv), &PL_sv_undef, 0);
680 Perl_croak(aTHX_ "No next::method '%s' found for %s", subname, hvname);
686 XS(XS_mro_get_linear_isa);
689 XS(XS_mro_get_isarev);
690 XS(XS_mro_is_universal);
691 XS(XS_mro_get_global_sub_generation);
692 XS(XS_mro_invalidate_all_method_caches);
693 XS(XS_mro_get_sub_generation);
694 XS(XS_mro_method_changed_in);
697 XS(XS_maybe_next_method);
700 Perl_boot_core_mro(pTHX)
703 static const char file[] = __FILE__;
705 newXSproto("mro::get_linear_isa", XS_mro_get_linear_isa, file, "$;$");
706 newXSproto("mro::set_mro", XS_mro_set_mro, file, "$$");
707 newXSproto("mro::get_mro", XS_mro_get_mro, file, "$");
708 newXSproto("mro::get_isarev", XS_mro_get_isarev, file, "$");
709 newXSproto("mro::is_universal", XS_mro_is_universal, file, "$");
710 newXSproto("mro::get_global_sub_generation", XS_mro_get_global_sub_generation, file, "");
711 newXSproto("mro::invalidate_all_method_caches", XS_mro_invalidate_all_method_caches, file, "");
712 newXSproto("mro::get_sub_generation", XS_mro_get_sub_generation, file, "$");
713 newXSproto("mro::method_changed_in", XS_mro_method_changed_in, file, "$");
714 newXS("next::can", XS_next_can, file);
715 newXS("next::method", XS_next_method, file);
716 newXS("maybe::next::method", XS_maybe_next_method, file);
719 XS(XS_mro_get_linear_isa) {
728 if(items < 1 || items > 2)
729 Perl_croak(aTHX_ "Usage: mro::get_linear_isa(classname [, type ])");
732 class_stash = gv_stashsv(classname, 0);
733 if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
736 char* which = SvPV_nolen(ST(1));
737 if(strEQ(which, "dfs"))
738 RETVAL = mro_get_linear_isa_dfs(class_stash, 0);
739 else if(strEQ(which, "c3"))
740 RETVAL = mro_get_linear_isa_c3(class_stash, 0);
742 Perl_croak(aTHX_ "Invalid mro name: '%s'", which);
745 RETVAL = mro_get_linear_isa(class_stash);
748 ST(0) = newRV_inc((SV*)RETVAL);
761 struct mro_meta* meta;
766 Perl_croak(aTHX_ "Usage: mro::set_mro(classname, type)");
769 whichstr = SvPV_nolen(ST(1));
770 class_stash = gv_stashsv(classname, GV_ADD);
771 if(!class_stash) Perl_croak(aTHX_ "Cannot create class: '%"SVf"'!", SVfARG(classname));
772 meta = HvMROMETA(class_stash);
774 if(strEQ(whichstr, "dfs"))
776 else if(strEQ(whichstr, "c3"))
779 Perl_croak(aTHX_ "Invalid mro name: '%s'", whichstr);
781 if(meta->mro_which != which) {
782 meta->mro_which = which;
783 /* Only affects local method cache, not
784 even child classes */
785 meta->sub_generation++;
786 if(meta->mro_nextmethod)
787 hv_clear(meta->mro_nextmethod);
800 struct mro_meta* meta;
805 Perl_croak(aTHX_ "Usage: mro::get_mro(classname)");
808 class_stash = gv_stashsv(classname, 0);
809 if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
810 meta = HvMROMETA(class_stash);
812 if(meta->mro_which == MRO_DFS)
813 ST(0) = sv_2mortal(newSVpvn("dfs", 3));
815 ST(0) = sv_2mortal(newSVpvn("c3", 2));
820 XS(XS_mro_get_isarev)
831 Perl_croak(aTHX_ "Usage: mro::get_isarev(classname)");
835 class_stash = gv_stashsv(classname, 0);
836 if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
840 if((isarev = HvMROMETA(class_stash)->mro_isarev)) {
843 while((iter = hv_iternext(isarev)))
844 XPUSHs(hv_iterkeysv(iter));
851 XS(XS_mro_is_universal)
861 Perl_croak(aTHX_ "Usage: mro::get_mro(classname)");
864 class_stash = gv_stashsv(classname, 0);
865 if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
867 HvMROMETA(class_stash)->is_universal
872 XS(XS_mro_get_global_sub_generation)
880 Perl_croak(aTHX_ "Usage: mro::get_global_sub_generation()");
882 ST(0) = sv_2mortal(newSViv(PL_sub_generation));
886 XS(XS_mro_invalidate_all_method_caches)
894 Perl_croak(aTHX_ "Usage: mro::invalidate_all_method_caches()");
901 XS(XS_mro_get_sub_generation)
911 Perl_croak(aTHX_ "Usage: mro::get_sub_generation(classname)");
914 class_stash = gv_stashsv(classname, 0);
915 if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
917 ST(0) = sv_2mortal(newSViv(HvMROMETA(class_stash)->sub_generation));
921 XS(XS_mro_method_changed_in)
931 Perl_croak(aTHX_ "Usage: mro::method_changed_in(classname)");
935 class_stash = gv_stashsv(classname, 0);
936 if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
938 mro_method_changed_in(class_stash);
948 SV* methcv = __nextcan(aTHX_ self, 0);
951 PERL_UNUSED_VAR(items);
953 if(methcv == &PL_sv_undef) {
954 ST(0) = &PL_sv_undef;
957 ST(0) = sv_2mortal(newRV_inc(methcv));
968 SV* methcv = __nextcan(aTHX_ self, 1);
973 call_sv(methcv, GIMME_V);
976 XS(XS_maybe_next_method)
981 SV* methcv = __nextcan(aTHX_ self, 0);
985 if(methcv == &PL_sv_undef) {
986 ST(0) = &PL_sv_undef;
991 call_sv(methcv, GIMME_V);
996 * c-indentation-style: bsd
998 * indent-tabs-mode: t
1001 * ex: set ts=8 sts=4 sw=4 noet: