]> git.sur5r.net Git - cc65/blob - src/ca65/macro.c
Fixed a problem with {} enclosed token lists and implemented them for
[cc65] / src / ca65 / macro.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                  macro.c                                  */
4 /*                                                                           */
5 /*                    Macros for the ca65 macroassembler                     */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 1998-2004 Ullrich von Bassewitz                                       */
10 /*               Römerstraße 52                                              */
11 /*               D-70794 Filderstadt                                         */
12 /* EMail:        uz@cc65.org                                                 */
13 /*                                                                           */
14 /*                                                                           */
15 /* This software is provided 'as-is', without any expressed or implied       */
16 /* warranty.  In no event will the authors be held liable for any damages    */
17 /* arising from the use of this software.                                    */
18 /*                                                                           */
19 /* Permission is granted to anyone to use this software for any purpose,     */
20 /* including commercial applications, and to alter it and redistribute it    */
21 /* freely, subject to the following restrictions:                            */
22 /*                                                                           */
23 /* 1. The origin of this software must not be misrepresented; you must not   */
24 /*    claim that you wrote the original software. If you use this software   */
25 /*    in a product, an acknowledgment in the product documentation would be  */
26 /*    appreciated but is not required.                                       */
27 /* 2. Altered source versions must be plainly marked as such, and must not   */
28 /*    be misrepresented as being the original software.                      */
29 /* 3. This notice may not be removed or altered from any source              */
30 /*    distribution.                                                          */
31 /*                                                                           */
32 /*****************************************************************************/
33
34
35
36 #include <stdio.h>
37 #include <string.h>
38
39 /* common */
40 #include "check.h"
41 #include "hashstr.h"
42 #include "hashtab.h"
43 #include "xmalloc.h"
44
45 /* ca65 */
46 #include "condasm.h"
47 #include "error.h"
48 #include "global.h"
49 #include "instr.h"
50 #include "istack.h"
51 #include "nexttok.h"
52 #include "pseudo.h"
53 #include "toklist.h"
54 #include "macro.h"
55
56
57
58 /*****************************************************************************/
59 /*                                 Forwards                                  */
60 /*****************************************************************************/
61
62
63
64 static unsigned HT_GenHash (const void* Key);
65 /* Generate the hash over a key. */
66
67 static const void* HT_GetKey (void* Entry);
68 /* Given a pointer to the user entry data, return a pointer to the key */
69
70 static HashNode* HT_GetHashNode (void* Entry);
71 /* Given a pointer to the user entry data, return a pointer to the hash node */
72
73 static int HT_Compare (const void* Key1, const void* Key2);
74 /* Compare two keys. The function must return a value less than zero if
75  * Key1 is smaller than Key2, zero if both are equal, and a value greater
76  * than zero if Key1 is greater then Key2.
77  */
78
79
80
81 /*****************************************************************************/
82 /*                                   Data                                    */
83 /*****************************************************************************/
84
85
86
87 /* Struct that describes an identifer (macro param, local list) */
88 typedef struct IdDesc IdDesc;
89 struct IdDesc {
90     IdDesc*         Next;       /* Linked list */
91     char            Id [1];     /* Identifier, dynamically allocated */
92 };
93
94
95
96 /* Struct that describes a macro definition */
97 typedef struct Macro Macro;
98 struct Macro {
99     HashNode        Node;       /* Hash list node */
100     Macro*          List;       /* List of all macros */
101     unsigned        LocalCount; /* Count of local symbols */
102     IdDesc*         Locals;     /* List of local symbols */
103     unsigned        ParamCount; /* Parameter count of macro */
104     IdDesc*         Params;     /* Identifiers of macro parameters */
105     unsigned        TokCount;   /* Number of tokens for this macro */
106     TokNode*        TokRoot;    /* Root of token list */
107     TokNode*        TokLast;    /* Pointer to last token in list */
108     unsigned char   Style;      /* Macro style */
109     char            Name [1];   /* Macro name, dynamically allocated */
110 };
111
112 /* Hash table functions */
113 static const HashFunctions HashFunc = {
114     HT_GenHash,
115     HT_GetKey,
116     HT_GetHashNode,
117     HT_Compare
118 };
119
120 /* Macro hash table */
121 static HashTable MacroTab = STATIC_HASHTABLE_INITIALIZER (117, &HashFunc);
122
123 /* Global macro data */
124 static Macro*   MacroRoot = 0;  /* List of all macros */
125
126 /* Structs that holds data for a macro expansion */
127 typedef struct MacExp MacExp;
128 struct MacExp {
129     MacExp*     Next;           /* Pointer to next expansion */
130     Macro*      M;              /* Which macro do we expand? */
131     unsigned    IfSP;           /* .IF stack pointer at start of expansion */
132     TokNode*    Exp;            /* Pointer to current token */
133     TokNode*    Final;          /* Pointer to final token */
134     unsigned    LocalStart;     /* Start of counter for local symbol names */
135     unsigned    ParamCount;     /* Number of actual parameters */
136     TokNode**   Params;         /* List of actual parameters */
137     TokNode*    ParamExp;       /* Node for expanding parameters */
138 };
139
140 /* Number of active macro expansions */
141 static unsigned MacExpansions = 0;
142
143 /* Flag if a macro expansion should get aborted */
144 static int DoMacAbort = 0;
145
146 /* Counter to create local names for symbols */
147 static unsigned LocalName = 0;
148
149
150
151 /*****************************************************************************/
152 /*                           Hash table functions                            */
153 /*****************************************************************************/
154
155
156
157 static unsigned HT_GenHash (const void* Key)
158 /* Generate the hash over a key. */
159 {
160     return HashStr (Key);
161 }
162
163
164
165 static const void* HT_GetKey (void* Entry)
166 /* Given a pointer to the user entry data, return a pointer to the index */
167 {
168     return ((Macro*) Entry)->Name;
169 }
170
171
172
173 static HashNode* HT_GetHashNode (void* Entry)
174 /* Given a pointer to the user entry data, return a pointer to the hash node */
175 {
176     return &((Macro*) Entry)->Node;
177 }
178
179
180
181 static int HT_Compare (const void* Key1, const void* Key2)
182 /* Compare two keys. The function must return a value less than zero if
183  * Key1 is smaller than Key2, zero if both are equal, and a value greater
184  * than zero if Key1 is greater then Key2.
185  */
186 {
187     return strcmp (Key1, Key2);
188 }
189
190
191
192 /*****************************************************************************/
193 /*                                   Code                                    */
194 /*****************************************************************************/
195
196
197
198 static IdDesc* NewIdDesc (const char* Id)
199 /* Create a new IdDesc, initialize and return it */
200 {
201     /* Allocate memory */
202     unsigned Len = strlen (Id);
203     IdDesc* I = xmalloc (sizeof (IdDesc) + Len);
204
205     /* Initialize the struct */
206     I->Next = 0;
207     memcpy (I->Id, Id, Len);
208     I->Id [Len] = '\0';
209
210     /* Return the new struct */
211     return I;
212 }
213
214
215
216 static Macro* NewMacro (const char* Name, unsigned char Style)
217 /* Generate a new macro entry, initialize and return it */
218 {
219     /* Allocate memory */
220     unsigned Len = strlen (Name);
221     Macro* M = xmalloc (sizeof (Macro) + Len);
222
223     /* Initialize the macro struct */
224     InitHashNode (&M->Node, M);
225     M->LocalCount = 0;
226     M->Locals     = 0;
227     M->ParamCount = 0;
228     M->Params     = 0;
229     M->TokCount   = 0;
230     M->TokRoot    = 0;
231     M->TokLast    = 0;
232     M->Style      = Style;
233     memcpy (M->Name, Name, Len+1);
234
235     /* Insert the macro into the global macro list */
236     M->List = MacroRoot;
237     MacroRoot = M;
238
239     /* Insert the macro into the hash table */
240     HT_Insert (&MacroTab, &M->Node);
241
242     /* Return the new macro struct */
243     return M;
244 }
245
246
247
248 static MacExp* NewMacExp (Macro* M)
249 /* Create a new expansion structure for the given macro */
250 {
251     unsigned I;
252
253     /* Allocate memory */
254     MacExp* E = xmalloc (sizeof (MacExp));
255
256     /* Initialize the data */
257     E->M          = M;
258     E->IfSP       = GetIfStack ();
259     E->Exp        = M->TokRoot;
260     E->Final      = 0;
261     E->LocalStart = LocalName;
262     LocalName    += M->LocalCount;
263     E->ParamCount = 0;
264     E->Params     = xmalloc (M->ParamCount * sizeof (TokNode*));
265     E->ParamExp   = 0;
266     for (I = 0; I < M->ParamCount; ++I) {
267         E->Params [I] = 0;
268     }
269
270     /* One macro expansion more */
271     ++MacExpansions;
272
273     /* Return the new macro expansion */
274     return E;
275 }
276
277
278
279 static void FreeMacExp (MacExp* E)
280 /* Remove and free the current macro expansion */
281 {
282     unsigned I;
283
284     /* One macro expansion less */
285     --MacExpansions;
286
287     /* Free the parameter list */
288     for (I = 0; I < E->ParamCount; ++I) {
289         xfree (E->Params [I]);
290     }
291     xfree (E->Params);
292
293     /* Free the final token if we have one */
294     if (E->Final) {
295         FreeTokNode (E->Final);
296     }
297
298     /* Free the structure itself */
299     xfree (E);
300 }
301
302
303
304 static void MacSkipDef (unsigned Style)
305 /* Skip a macro definition */
306 {
307     if (Style == MAC_STYLE_CLASSIC) {
308         /* Skip tokens until we reach the final .endmacro */
309         while (Tok != TOK_ENDMACRO && Tok != TOK_EOF) {
310             NextTok ();
311         }
312         if (Tok != TOK_EOF) {
313             SkipUntilSep ();
314         } else {
315             Error ("`.ENDMACRO' expected");
316         }
317     } else {
318         /* Skip until end of line */
319         SkipUntilSep ();
320     }
321 }
322
323
324
325 void MacDef (unsigned Style)
326 /* Parse a macro definition */
327 {
328     Macro* M;
329     TokNode* T;
330     int HaveParams;
331
332     /* We expect a macro name here */
333     if (Tok != TOK_IDENT) {
334         Error ("Identifier expected");
335         MacSkipDef (Style);
336         return;
337     } else if (!UbiquitousIdents && FindInstruction (SVal) >= 0) {
338         /* The identifier is a name of a 6502 instruction, which is not
339          * allowed if not explicitly enabled.
340          */
341         Error ("Cannot use an instruction as macro name");
342         MacSkipDef (Style);
343         return;
344     }
345
346     /* Did we already define that macro? */
347     if (HT_Find (&MacroTab, SVal) != 0) {
348         /* Macro is already defined */
349         Error ("A macro named `%s' is already defined", SVal);
350         /* Skip tokens until we reach the final .endmacro */
351         MacSkipDef (Style);
352         return;
353     }
354
355     /* Define the macro */
356     M = NewMacro (SVal, Style);
357
358     /* Switch to raw token mode and skip the macro name */
359     EnterRawTokenMode ();
360     NextTok ();
361
362     /* If we have a DEFINE style macro, we may have parameters in braces,
363      * otherwise we may have parameters without braces.
364      */
365     if (Style == MAC_STYLE_CLASSIC) {
366         HaveParams = 1;
367     } else {
368         if (Tok == TOK_LPAREN) {
369             HaveParams = 1;
370             NextTok ();
371         } else {
372             HaveParams = 0;
373         }
374     }
375
376     /* Parse the parameter list */
377     if (HaveParams) {
378
379         while (Tok == TOK_IDENT) {
380
381             /* Create a struct holding the identifier */
382             IdDesc* I = NewIdDesc (SVal);
383
384             /* Insert the struct into the list, checking for duplicate idents */
385             if (M->ParamCount == 0) {
386                 M->Params = I;
387             } else {
388                 IdDesc* List = M->Params;
389                 while (1) {
390                     if (strcmp (List->Id, SVal) == 0) {
391                         Error ("Duplicate symbol `%s'", SVal);
392                     }
393                     if (List->Next == 0) {
394                         break;
395                     } else {
396                         List = List->Next;
397                     }
398                 }
399                 List->Next = I;
400             }
401             ++M->ParamCount;
402
403             /* Skip the name */
404             NextTok ();
405
406             /* Maybe there are more params... */
407             if (Tok == TOK_COMMA) {
408                 NextTok ();
409             } else {
410                 break;
411             }
412         }
413     }
414
415     /* For class macros, we expect a separator token, for define style macros,
416      * we expect the closing paren.
417      */
418     if (Style == MAC_STYLE_CLASSIC) {
419         ConsumeSep ();
420     } else if (HaveParams) {
421         ConsumeRParen ();
422     }
423
424     /* Preparse the macro body. We will read the tokens until we reach end of
425      * file, or a .endmacro (or end of line for DEFINE style macros) and store
426      * them into an token list internal to the macro. For classic macros, there
427      * the .LOCAL command is detected and removed at this time.
428      */
429     while (1) {
430
431         /* Check for end of macro */
432         if (Style == MAC_STYLE_CLASSIC) {
433             /* In classic macros, only .endmacro is allowed */
434             if (Tok == TOK_ENDMACRO) {
435                 /* Done */
436                 break;
437             }
438             /* May not have end of file in a macro definition */
439             if (Tok == TOK_EOF) {
440                 Error ("`.ENDMACRO' expected");
441                 goto Done;
442             }
443         } else {
444             /* Accept a newline or end of file for new style macros */
445             if (TokIsSep (Tok)) {
446                 break;
447             }
448         }
449
450         /* Check for a .LOCAL declaration */
451         if (Tok == TOK_LOCAL && Style == MAC_STYLE_CLASSIC) {
452
453             while (1) {
454
455                 IdDesc* I;
456
457                 /* Skip .local or comma */
458                 NextTok ();
459
460                 /* Need an identifer */
461                 if (Tok != TOK_IDENT) {
462                     Error ("Identifier expected");
463                     SkipUntilSep ();
464                     break;
465                 }
466
467                 /* Put the identifier into the locals list and skip it */
468                 I = NewIdDesc (SVal);
469                 I->Next = M->Locals;
470                 M->Locals = I;
471                 ++M->LocalCount;
472                 NextTok ();
473
474                 /* Check for end of list */
475                 if (Tok != TOK_COMMA) {
476                     break;
477                 }
478
479             }
480
481             /* We need end of line after the locals */
482             ConsumeSep ();
483             continue;
484         }
485
486         /* Create a token node for the current token */
487         T = NewTokNode ();
488
489         /* If the token is an ident, check if it is a local parameter */
490         if (Tok == TOK_IDENT) {
491             unsigned Count = 0;
492             IdDesc* I = M->Params;
493             while (I) {
494                 if (strcmp (I->Id, SVal) == 0) {
495                     /* Local param name, replace it */
496                     T->Tok  = TOK_MACPARAM;
497                     T->IVal = Count;
498                     break;
499                 }
500                 ++Count;
501                 I = I->Next;
502             }
503         }
504
505         /* Insert the new token in the list */
506         if (M->TokCount == 0) {
507             /* First token */
508             M->TokRoot = M->TokLast = T;
509         } else {
510             /* We have already tokens */
511             M->TokLast->Next = T;
512             M->TokLast = T;
513         }
514         ++M->TokCount;
515
516         /* Read the next token */
517         NextTok ();
518     }
519
520     /* Skip the .endmacro for a classic macro */
521     if (Style == MAC_STYLE_CLASSIC) {
522         NextTok ();
523     }
524
525 Done:
526     /* Switch out of raw token mode */
527     LeaveRawTokenMode ();
528 }
529
530
531
532 static int MacExpand (void* Data)
533 /* If we're currently expanding a macro, set the the scanner token and
534  * attribute to the next value and return true. If we are not expanding
535  * a macro, return false.
536  */
537 {
538     /* Cast the Data pointer to the actual data structure */
539     MacExp* Mac = (MacExp*) Data;
540
541     /* Check if we should abort this macro */
542     if (DoMacAbort) {
543
544         /* Reset the flag */
545         DoMacAbort = 0;
546
547         /* Abort any open .IF statements in this macro expansion */
548         CleanupIfStack (Mac->IfSP);
549
550         /* Terminate macro expansion */
551         goto MacEnd;
552     }
553
554     /* We're expanding a macro. Check if we are expanding one of the
555      * macro parameters.
556      */
557     if (Mac->ParamExp) {
558
559         /* Ok, use token from parameter list */
560         TokSet (Mac->ParamExp);
561
562         /* Set pointer to next token */
563         Mac->ParamExp = Mac->ParamExp->Next;
564
565         /* Done */
566         return 1;
567
568     }
569
570     /* We're not expanding macro parameters. Check if we have tokens left from
571      * the macro itself.
572      */
573     if (Mac->Exp) {
574
575         /* Use next macro token */
576         TokSet (Mac->Exp);
577
578         /* Set pointer to next token */
579         Mac->Exp = Mac->Exp->Next;
580
581         /* Is it a request for actual parameter count? */
582         if (Tok == TOK_PARAMCOUNT) {
583             Tok  = TOK_INTCON;
584             IVal = Mac->ParamCount;
585             return 1;
586         }
587
588         /* Is it the name of a macro parameter? */
589         if (Tok == TOK_MACPARAM) {
590
591             /* Start to expand the parameter token list */
592             Mac->ParamExp = Mac->Params [IVal];
593
594             /* Recursive call to expand the parameter */
595             return MacExpand (Mac);
596         }
597
598         /* If it's an identifier, it may in fact be a local symbol */
599         if (Tok == TOK_IDENT && Mac->M->LocalCount) {
600             /* Search for the local symbol in the list */
601             unsigned Index = 0;
602             IdDesc* I = Mac->M->Locals;
603             while (I) {
604                 if (strcmp (SVal, I->Id) == 0) {
605                     /* This is in fact a local symbol, change the name. Be sure
606                      * to generate a local label name if the original name was
607                      * a local label, and also generate a name that cannot be
608                      * generated by a user.
609                      */
610                     unsigned PrefixLen = (I->Id[0] == LocalStart);
611                     sprintf (SVal, "%.*sLOCAL-MACRO-SYMBOL-%04X", PrefixLen,
612                              I->Id, Mac->LocalStart + Index);
613                     break;
614                 }
615                 /* Next symbol */
616                 ++Index;
617                 I = I->Next;
618             }
619
620             /* Done */
621             return 1;
622         }
623
624         /* The token was successfully set */
625         return 1;
626
627     }
628
629     /* No more macro tokens. Do we have a final token? */
630     if (Mac->Final) {
631
632         /* Set the final token and remove it */
633         TokSet (Mac->Final);
634         FreeTokNode (Mac->Final);
635         Mac->Final = 0;
636
637         /* The token was successfully set */
638         return 1;
639
640     }
641
642 MacEnd:
643     /* End of macro expansion */
644     FreeMacExp (Mac);
645
646     /* Pop the input function */
647     PopInput ();
648
649     /* No token available */
650     return 0;
651 }
652
653
654
655 static void StartExpClassic (Macro* M)
656 /* Start expanding the classic macro M */
657 {
658     MacExp*     E;
659     enum Token  Term;
660
661
662     /* Skip the macro name */
663     NextTok ();
664
665     /* Create a structure holding expansion data */
666     E = NewMacExp (M);
667
668     /* Read the actual parameters */
669     while (!TokIsSep (Tok)) {
670
671         TokNode* Last;
672
673         /* Check for maximum parameter count */
674         if (E->ParamCount >= M->ParamCount) {
675             ErrorSkip ("Too many macro parameters");
676             break;
677         }
678
679         /* The macro may optionally be enclosed in curly braces */
680         Term = GetTokListTerm (TOK_COMMA);
681
682         /* Read tokens for one parameter, accept empty params */
683         Last = 0;
684         while (Tok != Term && Tok != TOK_SEP) {
685
686             TokNode* T;
687
688             /* Check for end of file */
689             if (Tok == TOK_EOF) {
690                 Error ("Unexpected end of file");
691                 FreeMacExp (E);
692                 return;
693             }
694
695             /* Get the next token in a node */
696             T = NewTokNode ();
697
698             /* Insert it into the list */
699             if (Last == 0) {
700                 E->Params [E->ParamCount] = T;
701             } else {
702                 Last->Next = T;
703             }
704             Last = T;
705
706             /* And skip it... */
707             NextTok ();
708         }
709
710         /* One parameter more */
711         ++E->ParamCount;
712
713         /* If the macro argument was enclosed in curly braces, end-of-line
714          * is an error. Skip the closing curly brace.
715          */
716         if (Term == TOK_RCURLY) {
717             if (Tok == TOK_SEP) {
718                 Error ("End of line encountered within macro argument");
719                 break;
720             }
721             NextTok ();
722         }
723
724         /* Check for a comma */
725         if (Tok == TOK_COMMA) {
726             NextTok ();
727         } else {
728             break;
729         }
730     }
731
732     /* We must be at end of line now, otherwise something is wrong */
733     ExpectSep ();
734
735     /* Insert a new token input function */
736     PushInput (MacExpand, E, ".MACRO");
737 }
738
739
740
741 static void StartExpDefine (Macro* M)
742 /* Start expanding a DEFINE style macro */
743 {
744     /* Create a structure holding expansion data */
745     MacExp* E = NewMacExp (M);
746
747     /* A define style macro must be called with as many actual parameters
748      * as there are formal ones. Get the parameter count.
749      */
750     unsigned Count = M->ParamCount;
751
752     /* Skip the current token */
753     NextTok ();
754
755     /* Read the actual parameters */
756     while (Count--) {
757
758         TokNode*   Last;
759
760         /* The macro may optionally be enclosed in curly braces */
761         enum Token Term = GetTokListTerm (TOK_COMMA);
762
763         /* Check if there is really a parameter */
764         if (TokIsSep (Tok) || Tok == Term) {
765             ErrorSkip ("Macro parameter #%u is empty", E->ParamCount+1);
766             FreeMacExp (E);
767             return;
768         }
769
770         /* Read tokens for one parameter */
771         Last = 0;
772         do {
773
774             TokNode* T;
775
776             /* Get the next token in a node */
777             T = NewTokNode ();
778
779             /* Insert it into the list */
780             if (Last == 0) {
781                 E->Params [E->ParamCount] = T;
782             } else {
783                 Last->Next = T;
784             }
785             Last = T;
786
787             /* And skip it... */
788             NextTok ();
789
790         } while (Tok != Term && !TokIsSep (Tok));
791
792         /* One parameter more */
793         ++E->ParamCount;
794
795         /* If the macro argument was enclosed in curly braces, end-of-line
796          * is an error. Skip the closing curly brace.
797          */
798         if (Term == TOK_RCURLY) {
799             if (TokIsSep (Tok)) {
800                 Error ("End of line encountered within macro argument");
801                 break;
802             }
803             NextTok ();
804         }
805
806         /* Check for a comma */
807         if (Count > 0) {
808             if (Tok == TOK_COMMA) {
809                 NextTok ();
810             } else {
811                 Error ("`,' expected");
812             }
813         }
814     }
815
816     /* Macro expansion will overwrite the current token. This is a problem
817      * for define style macros since these are called from the scanner level.
818      * To avoid it, remember the current token and re-insert it, once macro
819      * expansion is done.
820      */
821     E->Final = NewTokNode ();
822
823     /* Insert a new token input function */
824     PushInput (MacExpand, E, ".DEFINE");
825 }
826
827
828
829 void MacExpandStart (void)
830 /* Start expanding the macro in SVal */
831 {
832     /* Search for the macro */
833     Macro* M = HT_FindEntry (&MacroTab, SVal);
834     CHECK (M != 0);
835
836     /* Call the apropriate subroutine */
837     switch (M->Style) {
838         case MAC_STYLE_CLASSIC: StartExpClassic (M);    break;
839         case MAC_STYLE_DEFINE:  StartExpDefine (M);     break;
840         default:                Internal ("Invalid macro style: %d", M->Style);
841     }
842 }
843
844
845
846 void MacAbort (void)
847 /* Abort the current macro expansion */
848 {
849     /* Must have an expansion */
850     CHECK (MacExpansions > 0);
851
852     /* Set a flag so macro expansion will terminate on the next call */
853     DoMacAbort = 1;
854 }
855
856
857
858 int IsMacro (const char* Name)
859 /* Return true if the given name is the name of a macro */
860 {
861     return (HT_Find (&MacroTab, Name) != 0);
862 }
863
864
865
866 int IsDefine (const char* Name)
867 /* Return true if the given name is the name of a define style macro */
868 {
869     Macro* M = HT_FindEntry (&MacroTab, Name);
870     return (M != 0 && M->Style == MAC_STYLE_DEFINE);
871 }
872
873
874
875 int InMacExpansion (void)
876 /* Return true if we're currently expanding a macro */
877 {
878     return (MacExpansions > 0);
879 }
880
881
882
883
884
885