FreeBASIC  0.91.0
pp-define.bas
Go to the documentation of this file.
1 '' pre-processor #define parsing (including macros)
2 ''
3 '' chng: dec/2004 written [v1ctor]
4 
5 
6 #include once "fb.bi"
7 #include once "fbint.bi"
8 #include once "lex.bi"
9 #include once "parser.bi"
10 #include once "pp.bi"
11 #include once "list.bi"
12 #include once "dstr.bi"
13 
14 #define LEX_FLAGS (LEXCHECK_NOWHITESPC or _
19 
20 type LEXPP_ARG
21  union
22  text as DZSTRING
23  textw as DWSTRING
24  end union
25 end type
26 
27 type LEXPP_ARGTB
28  tb(0 to FB_MAXDEFINEARGS-1) as LEXPP_ARG
29 end type
30 
31 ''::::
32 sub ppDefineInit( )
33 
34  listInit( @pp.argtblist, 8, len( LEXPP_ARGTB ), LIST_FLAGS_NOCLEAR )
35 
36 end sub
37 
38 ''::::
39 sub ppDefineEnd( )
40 
41  listEnd( @pp.argtblist )
42 
43 end sub
44 
45 sub hReportMacroError(byval s as FBSYMBOL ptr, byval errnum as integer)
46  errReportEx( errnum, "expanding: " + *symbGetName( s ) )
47 end sub
48 
49 function isMacroAllowed(byval s as FBSYMBOL ptr) as integer
50  '' The va_arg() and va_next() built in macros aren't supported with -gen gcc
51  '' Error recovery: continue parsing as usual
52  if (pp.skipping = FALSE) then
53  if (s->def.flags and FB_DEFINE_FLAGS_NOGCC) then
54  if( env.clopt.backend = FB_BACKEND_GCC ) then
55  errReport(FB_ERRMSG_STMTUNSUPPORTEDINGCC)
56  return FALSE
57  end if
58  end if
59  end if
60  return TRUE
61 end function
62 
63 '':::::
64 function hLoadMacro _
65  ( _
66  byval s as FBSYMBOL ptr _
67  ) as integer
68 
69  dim as FB_DEFPARAM ptr param = any, nextparam = any
70  dim as FB_DEFTOK ptr dt = any
71  dim as FBTOKEN t = any
72  dim as LEXPP_ARGTB ptr argtb = any
73  dim as integer prntcnt = any, num = any, reached_vararg = any, is_variadic = any
74  dim as zstring ptr argtext = any
75  static as string text
76 
77  function = -1
78 
79  '' '('?
80  if( lexCurrentChar( TRUE ) <> CHAR_LPRNT ) then
81  '' not an error, macro can be passed as param to other macros
82  exit function
83  end if
84 
85  if (isMacroAllowed(s) = FALSE) then
86  exit function
87  end if
88 
89  lexEatChar( )
90 
91  '' allocate a new arg list (support recursion)
92  param = symbGetDefineHeadParam( s )
93  if( param <> NULL ) then
94  argtb = listNewNode( @pp.argtblist )
95  else
96  argtb = NULL
97  end if
98 
99  prntcnt = 1
100  reached_vararg = FALSE
101 
102  '' Variadic macro?
103  is_variadic = ((s->def.flags and FB_DEFINE_FLAGS_VARIADIC) <> 0)
104 
105  '' for each arg
106  num = 0 '' num represents the current last cleared/used entry in the argtb
107  do
108  if( argtb ) then
109  '' argtb entries must be cleared! (it's a NOCLEAR list)
110  DZstrZero( argtb->tb(num).text )
111  end if
112 
113  nextparam = symbGetDefParamNext( param )
114 
115  '' Last param?
116  if( nextparam = NULL ) then
117  reached_vararg = is_variadic
118  end if
119 
120  '' read text until a comma or right-parentheses is found
121  do
122  lexNextToken( @t, LEXCHECK_NOWHITESPC or _
125  LEXCHECK_NOPERIOD )
126 
127  select case as const t.id
128  '' (
129  case CHAR_LPRNT
130  prntcnt += 1
131 
132  '' )
133  case CHAR_RPRNT
134  prntcnt -= 1
135  '' Closing ')'?
136  if( prntcnt = 0 ) then
137  exit do
138  end if
139 
140  '' ,
141  case CHAR_COMMA
142  '' A comma indicates the next arg, so we should
143  '' end the current arg now, unless we're at the
144  '' "..." vararg, which just "absorbs" everything
145  '' until the closing ')'.
146  if( prntcnt = 1 ) then
147  if( reached_vararg = FALSE ) then
148  exit do
149  end if
150  end if
151 
152  case FB_TK_EOL, FB_TK_EOF
153  hReportMacroError( s, FB_ERRMSG_EXPECTEDRPRNT )
154  '' Recovery: pretend to be at the closing ')'
155  prntcnt = 0
156  exit do
157  end select
158 
159  if( argtb <> NULL ) then
160  if( t.dtype <> FB_DATATYPE_WCHAR ) then
161  DZstrConcatAssign( argtb->tb(num).text, t.text )
162  else
163  DZstrConcatAssignW( argtb->tb(num).text, t.textw )
164  end if
165  end if
166  loop
167 
168  if( argtb ) then
169  with( argtb->tb(num) )
170  '' Arguments are allowed to be empty, so must check for NULL
171  if( .text.data ) then
172  '' Trim space
173  if( (.text.data[0][0] = CHAR_SPACE) or _
174  (.text.data[0][len( *.text.data )-1] = CHAR_SPACE) ) then
175  DZstrAssign( .text, trim( *.text.data ) )
176  end if
177  end if
178  end with
179  end if
180 
181  '' Reached closing parentheses?
182  if( prntcnt = 0 ) then
183  '' End of param list not yet reached?
184  if( nextparam ) then
185  '' Too few args specified. This is an error, unless it's
186  '' only the "..." vararg param that wasn't given any arg.
187 
188  '' Not the last param, or not variadic?
189  if( (symbGetDefParamNext( nextparam ) <> NULL) or (not is_variadic) ) then
190  hReportMacroError( s, FB_ERRMSG_ARGCNTMISMATCH )
191  end if
192 
193  '' Clear any missing args
194  assert( num < (symbGetDefineParams( s ) - 1) )
195  do
196  num += 1
197  '' argtb entries must be cleared! (it's a NOCLEAR list)
198  DZstrZero( argtb->tb(num).text )
199  loop while( num < (symbGetDefineParams( s ) - 1) )
200  end if
201 
202  exit do
203  end if
204 
205  '' Reached end of param list?
206  if( nextparam = NULL ) then
207  '' Too many args specified
208  hReportMacroError( s, FB_ERRMSG_ARGCNTMISMATCH )
209  '' error recovery: skip until next ')'
210  hSkipUntil( CHAR_RPRNT, TRUE, LEX_FLAGS )
211  exit do
212  end if
213 
214  '' Next
215  param = nextparam
216  num += 1
217  loop
218 
219  text = ""
220 
221  if( argtb ) then
222  dt = symbGetDefineHeadToken( s )
223  do while( dt )
224  select case as const( symbGetDefTokType( dt ) )
225  '' parameter?
226  case FB_DEFTOK_TYPE_PARAM
227  assert( symbGetDefTokParamNum( dt ) <= num )
228  argtext = argtb->tb( symbGetDefTokParamNum( dt ) ).text.data
229 
230  '' Only if not empty ("..." param can be empty)
231  if( argtext <> NULL ) then
232  text += *argtext
233  end if
234 
235  '' stringize parameter?
236  case FB_DEFTOK_TYPE_PARAMSTR
237  assert( symbGetDefTokParamNum( dt ) <= num )
238  argtext = argtb->tb( symbGetDefTokParamNum( dt ) ).text.data
239 
240  '' Only if not empty ("..." param can be empty)
241  if( argtext <> NULL ) then
242  '' don't escape, preserve the sequencies as-is
243  text += "$" + QUOTE
244  text += hReplace( argtext, QUOTE, QUOTE + QUOTE )
245  text += QUOTE
246  else
247  '' If it's empty, produce an empty string ("")
248  text += """"""
249  end if
250 
251  '' ordinary text..
252  case FB_DEFTOK_TYPE_TEX
253  text += *symbGetDefTokText( dt )
254 
255  '' unicode text?
256  case FB_DEFTOK_TYPE_TEXW
257  text += str( *symbGetDefTokTextW( dt ) )
258  end select
259 
260  '' next
261  dt = symbGetDefTokNext( dt )
262  loop
263 
264  '' free args text
265  do while( num > 0 )
266  num -= 1
267  DZstrAssign( argtb->tb(num).text, NULL )
268  loop
269 
270  listDelNode( @pp.argtblist, argtb )
271  end if
272 
273  if( lex.ctx->deflen = 0 ) then
274  DZstrAssign( lex.ctx->deftext, text )
275  else
276  DZstrAssign( lex.ctx->deftext, text + *lex.ctx->defptr )
277  end if
278 
279  function = len( text )
280 
281 end function
282 
283 ''::::
284 function hLoadDefine _
285  ( _
286  byval s as FBSYMBOL ptr _
287  ) as integer
288 
289  static as string text
290  dim as integer lgt = any
291 
292  function = FALSE
293 
294  '' define has args?
295  if( symbGetDefineParams( s ) > 0 ) then
296 
297  lgt = hLoadMacro( s )
298  if( lgt = -1 ) then
299  exit function
300  end if
301 
302  '' no args
303  else
304 
305  '' should we call a function to get definition text?
306  if( symbGetDefineCallback( s ) <> NULL ) then
307  '' call function
308  if( bit( symbGetDefineFlags( s ), 0 ) = 0 ) then
309  text = "$" + QUOTE + symbGetDefineCallback( s )( ) + QUOTE
310  else
311  text = symbGetDefineCallback( s )( )
312  end if
313 
314  if( lex.ctx->deflen = 0 ) then
315  DZstrAssign( lex.ctx->deftext, text )
316  else
317  DZstrAssign( lex.ctx->deftext, text + *lex.ctx->defptr )
318  end if
319 
320  lgt = len( text )
321 
322  '' just load text as-is
323  else
324 
325  '' arg-less macro?
326  if( symbGetDefineIsArgless( s ) ) then
327  '' '('?
328  if( lexCurrentChar( TRUE ) <> CHAR_LPRNT ) then
329  '' not an error, macro can be passed as param to other macros
330  exit function
331  end if
332  lexEatChar( )
333 
334  '' ')'
335  if( lexCurrentChar( TRUE ) <> CHAR_RPRNT ) then
336  errReport( FB_ERRMSG_EXPECTEDRPRNT )
337  else
338  lexEatChar( )
339  end if
340  end if
341 
342  if( symbGetType( s ) <> FB_DATATYPE_WCHAR ) then
343  if( lex.ctx->deflen = 0 ) then
344  DZstrAssign( lex.ctx->deftext, symbGetDefineText( s ) )
345  else
346  DZstrAssign( lex.ctx->deftext, _
347  *symbGetDefineText( s ) + *lex.ctx->defptr )
348  end if
349 
350  else
351  if( lex.ctx->deflen = 0 ) then
352  DZstrAssignW( lex.ctx->deftext, symbGetDefineTextW( s ) )
353  else
354  DZstrAssign( lex.ctx->deftext, _
355  str( *symbGetDefineTextW( s ) ) + *lex.ctx->defptr )
356  end if
357  end if
358 
359  lgt = symbGetLen( s )
360  end if
361 
362  end if
363 
364  ''
365  lex.ctx->defptr = lex.ctx->deftext.data
366  lex.ctx->deflen += lgt
367 
368  '' force a re-read
369  lex.ctx->currchar = cuint( INVALID )
370 
371  function = TRUE
372 
373 end function
374 
375 function hLoadMacroW _
376  ( _
377  byval s as FBSYMBOL ptr _
378  ) as integer
379 
380  dim as FB_DEFPARAM ptr param = any, nextparam = any
381  dim as FB_DEFTOK ptr dt = any
382  dim as FBTOKEN t = any
383  dim as LEXPP_ARGTB ptr argtb = any
384  dim as integer prntcnt = any, lgt = any, num = any, reached_vararg = any, is_variadic = any
385  dim as wstring ptr argtext = any
386  static as DWSTRING text
387 
388  function = -1
389 
390  '' '('?
391  if( lexCurrentChar( TRUE ) <> CHAR_LPRNT ) then
392  '' not an error, macro can be passed as param to other macros
393  exit function
394  end if
395 
396  if (isMacroAllowed(s) = FALSE) then
397  exit function
398  end if
399 
400  lexEatChar( )
401 
402  '' allocate a new arg list (because the recursivity)
403  param = symbGetDefineHeadParam( s )
404  if( param <> NULL ) then
405  argtb = listNewNode( @pp.argtblist )
406  else
407  argtb = NULL
408  end if
409 
410  prntcnt = 1
411  reached_vararg = FALSE
412 
413  '' Variadic macro?
414  is_variadic = ((s->def.flags and FB_DEFINE_FLAGS_VARIADIC) <> 0)
415 
416  '' for each arg
417  num = 0 '' num represents the current last cleared/used entry in the argtb
418  do
419  if( argtb ) then
420  '' argtb entries must be cleared! (it's a NOCLEAR list)
421  DWstrZero( argtb->tb(num).textw )
422  end if
423 
424  nextparam = symbGetDefParamNext( param )
425 
426  '' Last param?
427  if( nextparam = NULL ) then
428  reached_vararg = is_variadic
429  end if
430 
431  '' read text until a comma or right-parentheses is found
432  do
433  lexNextToken( @t, LEXCHECK_NOWHITESPC or _
436  LEXCHECK_NOPERIOD )
437 
438  select case as const t.id
439  '' (
440  case CHAR_LPRNT
441  prntcnt += 1
442 
443  '' )
444  case CHAR_RPRNT
445  prntcnt -= 1
446  '' Closing ')'?
447  if( prntcnt = 0 ) then
448  exit do
449  end if
450 
451  '' ,
452  case CHAR_COMMA
453  '' A comma indicates the next arg, so we should
454  '' end the current arg now, unless we're at the
455  '' "..." vararg, which just "absorbs" everything
456  '' until the closing ')'.
457  if( prntcnt = 1 ) then
458  if( reached_vararg = FALSE ) then
459  exit do
460  end if
461  end if
462 
463  ''
464  case FB_TK_EOL, FB_TK_EOF
465  hReportMacroError( s, FB_ERRMSG_EXPECTEDRPRNT )
466  '' Recovery: pretend to be at the closing ')'
467  prntcnt = 0
468  exit do
469  end select
470 
471  if( argtb <> NULL ) then
472  if( t.dtype <> FB_DATATYPE_WCHAR ) then
473  DWstrConcatAssignA( argtb->tb(num).textw, t.text )
474  else
475  DWstrConcatAssign( argtb->tb(num).textw, t.textw )
476  end if
477  end if
478  loop
479 
480  if( argtb ) then
481  with( argtb->tb(num) )
482  '' Arguments are allowed to be empty, so must check for NULL
483  if( .textw.data ) then
484  '' Trim space
485  if( (.textw.data[0][0] = CHAR_SPACE) or _
486  (.textw.data[0][len( *.textw.data )-1] = CHAR_SPACE) ) then
487  DWstrAssign( .textw, trim( *.textw.data ) )
488  end if
489  end if
490  end with
491  end if
492 
493  '' Reached closing parentheses?
494  if( prntcnt = 0 ) then
495  '' End of param list not yet reached?
496  if( nextparam ) then
497  '' Too few args specified. This is an error, unless it's
498  '' only the "..." vararg param that wasn't given any arg.
499 
500  '' Not the last param, or not variadic?
501  if( (symbGetDefParamNext( nextparam ) <> NULL) or (not is_variadic) ) then
502  hReportMacroError( s, FB_ERRMSG_ARGCNTMISMATCH )
503  end if
504 
505  '' Clear any missing args
506  assert( num < (symbGetDefineParams( s ) - 1) )
507  do
508  num += 1
509  '' argtb entries must be cleared! (it's a NOCLEAR list)
510  DWstrZero( argtb->tb(num).textw )
511  loop while( num < (symbGetDefineParams( s ) - 1) )
512  end if
513 
514  exit do
515  end if
516 
517  '' Reached end of param list?
518  if( nextparam = NULL ) then
519  '' Too many args specified
520  hReportMacroError( s, FB_ERRMSG_ARGCNTMISMATCH )
521  '' error recovery: skip until next ')'
522  hSkipUntil( CHAR_RPRNT, TRUE, LEX_FLAGS )
523  exit do
524  end if
525 
526  '' Next
527  param = nextparam
528  num += 1
529  loop
530 
531  '' text = ""
532  DWstrAssign( text, NULL )
533 
534  if( argtb ) then
535  dt = symbGetDefineHeadToken( s )
536  do while( dt )
537  select case as const( symbGetDefTokType( dt ) )
538  '' parameter?
539  case FB_DEFTOK_TYPE_PARAM
540  assert( symbGetDefTokParamNum( dt ) <= num )
541  argtext = argtb->tb( symbGetDefTokParamNum( dt ) ).textw.data
542 
543  '' Only if not empty ("..." param can be empty)
544  if( argtext <> NULL ) then
545  DWstrConcatAssign( text, argtext )
546  end if
547 
548  '' stringize parameter?
549  case FB_DEFTOK_TYPE_PARAMSTR
550  assert( symbGetDefTokParamNum( dt ) <= num )
551  argtext = argtb->tb( symbGetDefTokParamNum( dt ) ).textw.data
552 
553  '' Only if not empty ("..." param can be empty)
554  if( argtext <> NULL ) then
555  '' don't escape, preserve the sequencies as-is
556  DWstrConcatAssign( text, "$" + QUOTE )
557  DWstrConcatAssign( text, *hReplaceW( argtext, QUOTE, QUOTE + QUOTE ) )
558  DWstrConcatAssign( text, QUOTE )
559  end if
560 
561  '' ordinary text..
562  case FB_DEFTOK_TYPE_TEX
563  DWstrConcatAssignA( text, symbGetDefTokText( dt ) )
564 
565  '' unicode text?
566  case FB_DEFTOK_TYPE_TEXW
567  DWstrConcatAssign( text, symbGetDefTokTextW( dt ) )
568  end select
569 
570  '' next
571  dt = symbGetDefTokNext( dt )
572  loop
573 
574  '' free args text
575  do while( num > 0 )
576  num -= 1
577  DWstrAssign( argtb->tb(num).textw, NULL )
578  loop
579 
580  listDelNode( @pp.argtblist, argtb )
581  end if
582 
583  if( lex.ctx->deflen = 0 ) then
584  DWstrAssign( lex.ctx->deftextw, text.data )
585  else
586  DWstrAssign( lex.ctx->deftextw, *text.data + *lex.ctx->defptrw )
587  end if
588 
589  function = len( *text.data )
590 
591 end function
592 
593 ''::::
594 function hLoadDefineW _
595  ( _
596  byval s as FBSYMBOL ptr _
597  ) as integer
598 
599  static as DWSTRING text
600  dim as integer lgt = any
601 
602  function = FALSE
603 
604  '' define has args?
605  if( symbGetDefineParams( s ) > 0 ) then
606 
607  lgt = hLoadMacroW( s )
608  if( lgt = -1 ) then
609  exit function
610  end if
611 
612  '' no args
613  else
614 
615  '' should we call a function to get definition text?
616  if( symbGetDefineCallback( s ) <> NULL ) then
617  '' call function
618  if( bit( symbGetDefineFlags( s ), 0 ) = 0 ) then
619  DWstrAssignA( text, "$" + QUOTE + symbGetDefineCallback( s )( ) + QUOTE )
620  else
621  DWstrAssignA( text, symbGetDefineCallback( s )( ) )
622  end if
623 
624  if( lex.ctx->deflen = 0 ) then
625  DWstrAssign( lex.ctx->deftextw, text.data )
626  else
627  DWstrAssign( lex.ctx->deftextw, *text.data + *lex.ctx->defptrw )
628  end if
629 
630  lgt = len( *text.data )
631 
632  '' just load text as-is
633  else
634  '' arg-less macro?
635  if( symbGetDefineIsArgless( s ) ) then
636  '' '('?
637  if( lexCurrentChar( TRUE ) <> CHAR_LPRNT ) then
638  '' not an error, macro can be passed as param to other macros
639  exit function
640  end if
641  lexEatChar( )
642 
643  '' ')'
644  if( lexCurrentChar( TRUE ) <> CHAR_RPRNT ) then
645  errReport( FB_ERRMSG_EXPECTEDRPRNT )
646  else
647  lexEatChar( )
648  end if
649  end if
650 
651  if( symbGetType( s ) <> FB_DATATYPE_WCHAR ) then
652  if( lex.ctx->deflen = 0 ) then
653  DWstrAssignA( lex.ctx->deftextw, symbGetDefineText( s ) )
654  else
655  DWstrAssign( lex.ctx->deftextw, _
656  wstr( *symbGetDefineText( s ) ) + *lex.ctx->defptrw )
657  end if
658 
659  else
660  if( lex.ctx->deflen = 0 ) then
661  DWstrAssign( lex.ctx->deftextw, symbGetDefineTextW( s ) )
662  else
663  DWstrAssign( lex.ctx->deftextw, _
664  *symbGetDefineTextW( s ) + *lex.ctx->defptrw )
665  end if
666  end if
667 
668  lgt = symbGetLen( s )
669  end if
670 
671  end if
672 
673  ''
674  lex.ctx->defptrw = lex.ctx->deftextw.data
675  lex.ctx->deflen += lgt
676 
677  function = TRUE
678 
679 end function
680 
681 ''::::
682 function ppDefineLoad _
683  ( _
684  byval s as FBSYMBOL ptr _
685  ) as integer
686 
687  '' recursion?
688  if( s = lex.ctx->currmacro ) then
689  errReport( FB_ERRMSG_RECURSIVEMACRO )
690  '' error recovery: skip
691  hSkipUntil( INVALID, FALSE, LEX_FLAGS )
692  return TRUE
693  end if
694 
695  '' only one level
696  if( lex.ctx->currmacro = NULL ) then
697  lex.ctx->currmacro = s
698  end if
699 
700  if( env.inf.format = FBFILE_FORMAT_ASCII ) then
701  function = hLoadDefine( s )
702  else
703  function = hLoadDefineW( s )
704  end if
705 
706  '' force a re-read
707  lex.ctx->currchar = cuint( INVALID )
708 
709 end function
710 
711 '':::::
712 function hRtrimMacroText _
713  ( _
714  byval tokhead as FB_DEFTOK ptr, _
715  byval toktail as FB_DEFTOK ptr _
716  ) as FB_DEFTOK ptr
717 
718  '' remove the white-spaces
719  do while( toktail <> NULL )
720  '' not ascii text?
721  if( symbGetDefTokType( toktail ) <> FB_DEFTOK_TYPE_TEX ) then
722  exit do
723  end if
724 
725  select case as const (*symbGetDefTokText( toktail ))[0]
726  '' space or nl?
727  case CHAR_SPACE, CHAR_TAB, CHAR_LF
728  toktail = symbDelDefineTok( toktail )
729 
730  case else
731  exit do
732  end select
733  loop
734 
735  if( toktail = NULL ) then
736  function = NULL
737  else
738  function = tokhead
739  end if
740 
741 end function
742 
743 '':::::
744 function hReadMacroText _
745  ( _
746  byval args as integer, _
747  byval paramhead as FB_DEFPARAM ptr, _
748  byval ismultiline as integer _
749  ) as FB_DEFTOK ptr
750 
751  static as zstring * FB_MAXNAMELEN+1 arg
752  dim as FB_DEFPARAM ptr param = any
753  dim as FB_DEFTOK ptr toktail = NULL, tokhead = NULL
754  dim as integer addquotes = any, nestedcnt = 0
755 
756  do
757  addquotes = FALSE
758 
759  select case as const lexGetToken( LEX_FLAGS )
760  case FB_TK_EOF
761  if( ismultiline ) then
762  errReport( FB_ERRMSG_EXPECTEDMACRO )
763  end if
764 
765  exit do
766 
767  case FB_TK_EOL
768  if( ismultiline = FALSE ) then
769  exit do
770  end if
771 
772  '' multi-line, only add if it's not at the beginning
773  if( tokhead <> NULL ) then
774  toktail = symbAddDefineTok( toktail, FB_DEFTOK_TYPE_TEX )
775  '' just lf
776  ZstrAssign( @toktail->text, LFCHAR )
777  end if
778 
779  lexSkipToken( LEX_FLAGS )
780 
781  continue do
782 
783  case FB_TK_COMMENT, FB_TK_REM
784  if( ismultiline = FALSE ) then
785  exit do
786  end if
787 
788  do
789  lexSkipToken( LEX_FLAGS )
790 
791  select case lexGetToken( LEX_FLAGS )
792  case FB_TK_EOL, FB_TK_EOF
793  exit do
794  end select
795  loop
796 
797  continue do
798 
799  case CHAR_SHARP
800  select case lexGetLookAhead( 1, (LEX_FLAGS or LEXCHECK_KWDNAMESPC) and _
801  (not LEXCHECK_NOWHITESPC) )
802  '' '##'?
803  case CHAR_SHARP
804  lexSkipToken( LEX_FLAGS )
805  lexSkipToken( LEX_FLAGS )
806  continue do
807 
808  '' '#' macro?
809  case FB_TK_PP_MACRO
810  if( ismultiline ) then
811  nestedcnt += 1
812  end if
813 
814  '' '#' endmacro?
815  case FB_TK_PP_ENDMACRO
816  if( ismultiline ) then
817  '' not nested?
818  if( nestedcnt = 0 ) then
819  lexSkipToken( LEX_FLAGS )
820  '' no LEX_FLAGS, white-spaces must be skipped
821  lexSkipToken( )
822 
823  tokhead = hRtrimMacroText( tokhead, toktail )
824 
825  exit do
826  end if
827 
828  nestedcnt -= 1
829  end if
830 
831  '' '#' id?
832  case FB_TK_ID
833  '' note: using the PP hashtb here, non-PP keyword will be ID's
834  lexSkipToken( LEX_FLAGS )
835  addquotes = TRUE
836  end select
837 
838  '' white space?
839  case CHAR_SPACE, CHAR_TAB
840 
841  '' only add if it's not at the beginning
842  if( tokhead <> NULL ) then
843  toktail = symbAddDefineTok( toktail, FB_DEFTOK_TYPE_TEX )
844  '' condensed to a single white-space
845  ZstrAssign( @toktail->text, " " )
846  end if
847 
848  lexSkipToken( LEX_FLAGS )
849 
850  continue do
851 
852  end select
853 
854  select case as const lexGetClass( LEX_FLAGS )
855  '' string literal? preserve quotes
856  case FB_TKCLASS_STRLITERAL
857 
858  '' ascii?
859  if( env.inf.format = FBFILE_FORMAT_ASCII ) then
860  toktail = symbAddDefineTok( toktail, FB_DEFTOK_TYPE_TEX )
861  if( tokhead = NULL ) then
862  tokhead = toktail
863  end if
864 
865  ZstrAssign( @toktail->text, lexGetText( ) )
866 
867  '' unicode..
868  else
869  toktail = symbAddDefineTok( toktail, FB_DEFTOK_TYPE_TEXW )
870  if( tokhead = NULL ) then
871  tokhead = toktail
872  end if
873 
874  WstrAssign( @toktail->textw, lexGetTextW( ) )
875  end if
876 
877  lexSkipToken( LEX_FLAGS )
878 
879  '' identifier? check if it's a parameter (params can be keywords too)
880  case FB_TKCLASS_IDENTIFIER, FB_TKCLASS_KEYWORD, FB_TKCLASS_QUIRKWD
881  toktail = symbAddDefineTok( toktail, FB_DEFTOK_TYPE_TEX )
882  if( tokhead = NULL ) then
883  tokhead = toktail
884  end if
885 
886  arg = ucase( *lexGetText( ) )
887 
888  '' look up..
889  param = hashLookup( @symb.def.paramhash, arg )
890 
891  '' found?
892  if( param <> NULL ) then
893  if( addquotes = FALSE ) then
894  symbSetDefTokType( toktail, FB_DEFTOK_TYPE_PARAM )
895  else
896  symbSetDefTokType( toktail, FB_DEFTOK_TYPE_PARAMSTR )
897  end if
898 
899  symbSetDefTokParamNum( toktail, symbGetDefParamNum( param ) )
900 
901  '' none matched, read as-is
902  else
903  '' restore the '#'?
904  if( addquotes ) then
905  ZstrAssign( @toktail->text, "#" )
906  ZstrConcatAssign( @toktail->text, lexGetText( ) )
907  else
908  ZstrAssign( @toktail->text, lexGetText( ) )
909  end if
910  end if
911 
912  lexSkipToken( LEX_FLAGS )
913 
914  '' anything else, read as-is
915  case else
916  toktail = symbAddDefineTok( toktail, FB_DEFTOK_TYPE_TEX )
917  if( tokhead = NULL ) then
918  tokhead = toktail
919  end if
920 
921  ZstrAssign( @toktail->text, lexGetText( ) )
922  lexSkipToken( LEX_FLAGS )
923 
924  end select
925 
926  loop
927 
928  function = tokhead
929 
930 end function
931 
932 '':::::
933 sub hReadDefineText _
934  ( _
935  byval sym as FBSYMBOL ptr, _
936  byval defname as zstring ptr, _
937  byval isargless as integer, _
938  byval ismultiline as integer _
939  )
940 
941  dim as zstring ptr text = any
942  dim as wstring ptr textw = any
943 
944  if( env.inf.format = FBFILE_FORMAT_ASCII ) then
945  '' LITERAL*
946  text = ppReadLiteral( ismultiline )
947 
948  '' already defined? if there are no differences, do nothing..
949  if( sym <> NULL ) then
950  if( (symbGetDefineParams( sym ) > 0) or _
951  (symbGetType( sym ) <> FB_DATATYPE_CHAR) ) then
952  errReportEx( FB_ERRMSG_DUPDEFINITION, defname )
953  elseif( (*symbGetDefineText( sym ) <> *text) ) then
954  errReportEx( FB_ERRMSG_DUPDEFINITION, defname )
955  end if
956  else
957  symbAddDefine( defname, text, len( *text ), isargless )
958  end if
959 
960  '' unicode..
961  else
962  '' LITERAL*
963  textw = ppReadLiteralW( ismultiline )
964 
965  '' already defined? if there are no differences, do nothing..
966  if( sym <> NULL ) then
967  if( (symbGetDefineParams( sym ) > 0) or _
968  (symbGetType( sym ) <> FB_DATATYPE_WCHAR) ) then
969  errReportEx( FB_ERRMSG_DUPDEFINITION, defname )
970  elseif( (*symbGetDefineTextW( sym ) <> *textw) ) then
971  errReportEx( FB_ERRMSG_DUPDEFINITION, defname )
972  end if
973  else
974  symbAddDefineW( defname, textw, len( *textw ), isargless )
975  end if
976 
977  end if
978 end sub
979 
980 function hMatchParamEllipsis( ) as integer
981 
982  const FLAGS = LEXCHECK_NODEFINE or LEXCHECK_NOSYMBOL
983 
984  function = FALSE
985 
986  '' '...' ?
987  if( lexGetToken( FLAGS ) = CHAR_DOT ) then
988  if( lexGetLookAhead( 1, FLAGS ) = CHAR_DOT ) then
989  if( lexGetLookAhead( 2, FLAGS ) = CHAR_DOT ) then
990  '' Skip the dots
991  lexSkipToken( FLAGS )
992  lexSkipToken( FLAGS )
993  lexSkipToken( FLAGS )
994  function = TRUE
995  end if
996  end if
997  end if
998 
999 end function
1000 
1001 '':::::
1002 '' Define = DEFINE ID (!WHITESPC '(' ID (',' ID)* ')')? LITERAL+
1003 '' | MACRO ID '(' ID (',' ID)* ')' Comment? EOL
1004 '' MacroBody*
1005 '' ENDMACRO .
1006 sub ppDefine( byval ismultiline as integer )
1007  static as zstring * FB_MAXNAMELEN+1 defname
1008  dim as integer params = any, isargless = any, flags = any, is_variadic = any
1009  dim as FB_DEFPARAM ptr paramhead = any, lastparam = any
1010  dim as FBSYMBOL ptr sym = any
1011  dim as FBSYMCHAIN ptr chain_ = any
1012  dim as FBSYMBOL ptr base_parent = any
1013  dim as FB_DEFTOK ptr tokhead = any
1014 
1015  '' note: using the PP hashtb here, so any non-PP keyword won't be found
1016 
1017  '' don't allow explicit namespaces
1018  chain_ = cIdentifier( base_parent, FB_IDOPT_ISDECL or FB_IDOPT_DEFAULT )
1019 
1020  flags = LEX_FLAGS
1021  if( ismultiline ) then
1022  flags and= not LEXCHECK_NOWHITESPC
1023  end if
1024 
1025  lexEatToken( @defname, flags )
1026 
1027  if( hIsValidSymbolName( defname ) = FALSE ) then
1028  errReport( FB_ERRMSG_EXPECTEDIDENTIFIER )
1029  exit sub
1030  end if
1031 
1032  '' contains a period? (with LEX_FLAGS it won't skip white spaces)
1033  if( lexGetToken( flags ) = CHAR_DOT ) then
1034  errReport( FB_ERRMSG_CANTINCLUDEPERIODS )
1035  end if
1036 
1037  if( chain_ <> NULL ) then
1038  sym = chain_->sym
1039  if( symbIsDefine( sym ) = FALSE ) then
1040  '' defines have no dups or respect namespaces
1041  errReportEx( FB_ERRMSG_DUPDEFINITION, @defname )
1042  '' error recovery: fake an id
1043  defname = *symbUniqueLabel( )
1044  end if
1045  else
1046  sym = NULL
1047  end if
1048 
1049  params = 0
1050  paramhead = NULL
1051  isargless = FALSE
1052  is_variadic = FALSE
1053 
1054  '' '('?
1055  if( lexGetToken( flags ) = CHAR_LPRNT ) then
1057 
1058  '' not arg-less?
1059  if( lexGetToken( LEXCHECK_NODEFINE or LEXCHECK_NOSYMBOL ) <> CHAR_RPRNT ) then
1060  lastparam = NULL
1061  do
1062  select case as const lexGetClass( )
1063  case FB_TKCLASS_IDENTIFIER, FB_TKCLASS_KEYWORD, FB_TKCLASS_QUIRKWD
1064  lastparam = symbAddDefineParam( lastparam, lexGetText( ) )
1065 
1066  case else
1067  errReport( FB_ERRMSG_EXPECTEDIDENTIFIER )
1068  '' error recovery: fake a param
1069  lastparam = symbAddDefineParam( lastparam, symbUniqueLabel( ) )
1070  end select
1071 
1072  if( lastparam = NULL ) then
1073  errReport( FB_ERRMSG_DUPDEFINITION )
1074  end if
1075 
1077 
1078  params += 1
1079  if( params >= FB_MAXDEFINEARGS ) then
1080  errReport( FB_ERRMSG_TOOMANYPARAMS )
1081  '' error recovery: skip until next ')'
1082  hSkipUntil( CHAR_RPRNT, TRUE )
1083  exit sub
1084  end if
1085 
1086  if( paramhead = NULL ) then
1087  paramhead = lastparam
1088  end if
1089 
1090  '' ','?
1091  if( lexGetToken( LEXCHECK_NODEFINE or LEXCHECK_NOSYMBOL ) <> CHAR_COMMA ) then
1092  exit do
1093  end if
1094 
1096  loop
1097 
1098  '' Check for ellipsis after the last parameter's name, before the ')'.
1099  '' (variadic macros)
1100  is_variadic = hMatchParamEllipsis( )
1101  else
1102  isargless = TRUE
1103  end if
1104 
1105  '' ')'
1106  if( lexGetToken( LEX_FLAGS ) <> CHAR_RPRNT ) then
1107  errReport( FB_ERRMSG_EXPECTEDRPRNT )
1108  '' error recovery: skip until next ')'
1109  hSkipUntil( CHAR_RPRNT, TRUE, LEX_FLAGS )
1110  else
1111  lexSkipToken( LEX_FLAGS and (not LEXCHECK_NOWHITESPC) )
1112  end if
1113  else
1114  if( ismultiline = FALSE ) then
1115  if( lexGetToken( LEX_FLAGS ) = CHAR_SPACE ) then
1116  '' skip white-spaces
1117  lexSkipToken( LEX_FLAGS and not LEXCHECK_NOWHITESPC )
1118  end if
1119  end if
1120  end if
1121 
1122  '' not a macro?
1123  if( params = 0 ) then
1124  hReadDefineText( sym, @defname, isargless, ismultiline )
1125  exit sub
1126  end if
1127 
1128  '' macro..
1129  '' already defined? can't check..
1130  if( sym <> NULL ) then
1131  errReportEx( FB_ERRMSG_DUPDEFINITION, defname )
1132  else
1133  tokhead = hReadMacroText( params, paramhead, ismultiline )
1134  symbAddDefineMacro( @defname, tokhead, params, paramhead, _
1135  iif( is_variadic, _
1136  FB_DEFINE_FLAGS_VARIADIC, _
1137  FB_DEFINE_FLAGS_NONE ) )
1138  end if
1139 end sub
1140