FreeBASIC  0.91.0
pp.bas
Go to the documentation of this file.
1 '' pre-processor top-level module
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 
12 #define LEX_FLAGS (LEXCHECK_NOWHITESPC or _
17 
18 type SYMBKWD
19  name as const zstring ptr
20  id as integer
21  sym as FBSYMBOL ptr
22 end type
23 
24 declare sub ppInclude()
25 declare sub ppIncLib( )
26 declare sub ppLibPath( )
27 declare sub ppLine()
28 declare sub ppLang()
29 
30 '' globals
31  dim shared as PP_CTX pp
32 
33 const SYMB_MAXKEYWORDS = 24
34 
35  dim shared kwdTb( 0 to SYMB_MAXKEYWORDS-1 ) as SYMBKWD => _
36  { _
37  (@"IF" , FB_TK_PP_IF ), _
38  (@"IFDEF" , FB_TK_PP_IFDEF ), _
39  (@"IFNDEF" , FB_TK_PP_IFNDEF ), _
40  (@"ELSE" , FB_TK_PP_ELSE ), _
41  (@"ELSEIF" , FB_TK_PP_ELSEIF ), _
42  (@"ENDIF" , FB_TK_PP_ENDIF ), _
43  (@"DEFINE" , FB_TK_PP_DEFINE ), _
44  (@"UNDEF" , FB_TK_PP_UNDEF ), _
45  (@"MACRO" , FB_TK_PP_MACRO ), _
46  (@"ENDMACRO", FB_TK_PP_ENDMACRO ), _
47  (@"INCLUDE" , FB_TK_PP_INCLUDE ), _
48  (@"LIBPATH" , FB_TK_PP_LIBPATH ), _
49  (@"INCLIB" , FB_TK_PP_INCLIB ), _
50  (@"PRAGMA" , FB_TK_PP_PRAGMA ), _
51  (@"PRINT" , FB_TK_PP_PRINT ), _
52  (@"ERROR" , FB_TK_PP_ERROR ), _
53  (@"LINE" , FB_TK_PP_LINE ), _
54  (@"LANG" , FB_TK_PP_LANG ), _
55  (@"ASSERT" , FB_TK_PP_ASSERT ), _
56  (NULL) _
57  }
58 
59 ''::::
60 sub ppInit( )
61  dim as integer i
62 
63  '' create a fake namespace
64  pp.kwdns.class = FB_SYMBCLASS_NAMESPACE
65  pp.kwdns.scope = FB_MAINSCOPE
66 
67  symbSymbTbInit( pp.kwdns.nspc.ns.symtb, @pp.kwdns )
68  symbHashTbInit( pp.kwdns.nspc.ns.hashtb, @pp.kwdns, SYMB_MAXKEYWORDS )
69  pp.kwdns.nspc.ns.ext = symbCompAllocExt( )
70 
71  ''
72  for i = 0 to SYMB_MAXKEYWORDS-1
73  if( kwdTb(i).name = NULL ) then
74  exit for
75  end if
76 
78  kwdTb(i).id, _
79  FB_TKCLASS_KEYWORD, _
80  @pp.kwdns.nspc.ns.hashtb )
81  if( kwdTb(i).sym = NULL ) then
82  exit sub
83  end if
84  next
85 
86  ''
87  pp.skipping = FALSE
88 
89  ppDefineInit( )
90 
91  ppCondInit( )
92 
93  ppPragmaInit( )
94 
95 end sub
96 
97 ''::::
98 sub ppEnd( )
99  dim as integer i
100 
101  ppPragmaEnd( )
102 
103  ppCondEnd( )
104 
105  ppDefineEnd( )
106 
107  ''
108  for i = 0 to SYMB_MAXKEYWORDS-1
109  if( kwdTb(i).sym = NULL ) then
110  exit for
111  end if
112 
113  symbFreeSymbol( kwdTb(i).sym )
114  kwdTb(i).sym = NULL
115  next
116 
117  symbCompFreeExt( pp.kwdns.nspc.ns.ext )
118  hashEnd( @pp.kwdns.nspc.ns.hashtb.tb )
119 
120 end sub
121 
122 '':::::
123 sub ppCheck( )
124 
125  '' not a '#' char?
126  if( lex.ctx->head->id <> CHAR_SHARP ) then
127  exit sub
128  end if
129 
130  '' already inside the PP? (ie: skipping a false #IF or #ELSE)
131  if( lex.ctx->reclevel <> 0 ) then
132  exit sub
133  end if
134 
135  '' not at the beginning of line?
136  if( lex.ctx->lasttk_id <> FB_TK_EOL ) then
137  '' or top of source-file?
138  if( lex.ctx->lasttk_id <> INVALID ) then
139  exit sub
140  end if
141  end if
142 
143  lex.ctx->reclevel += 1
144 
145  '' !!!FIXME!!! if LEXCHECK_KWDNAMESPC is used in future, it
146  '' must be restored
147  lex.ctx->kwdns = @pp.kwdns
148 
149  lexSkipToken( LEXCHECK_KWDNAMESPC )
150 
151  '' let the parser do the rest..
152  ppParse( )
153  lex.ctx->reclevel -= 1
154 
155 end sub
156 
157 
158 '' PreProcess = '#'DEFINE ID (!WHITESPC '(' ID (',' ID)* ')')? LITERAL*
159 '' | '#'UNDEF ID
160 '' | '#'IFDEF ID
161 '' | '#'IFNDEF ID
162 '' | '#'IF Expression
163 '' | '#'ELSE
164 '' | '#'ELSEIF Expression
165 '' | '#'ENDIF
166 '' | '#'ASSERT Expression
167 '' | '#'PRINT LITERAL*
168 '' | '#'INCLUDE ONCE? LIT_STR
169 '' | '#'INCLIB LIT_STR
170 '' | '#'LIBPATH LIT_STR
171 '' | '#'ERROR LIT_STR .
172 ''
173 sub ppParse( )
174  '' note: when adding any new PP symbol, update ppSkip() too
175  select case as const lexGetToken( LEXCHECK_KWDNAMESPC )
176 
177  '' DEFINE ID (!WHITESPC '(' ID (',' ID)* ')')? LITERAL+
178  case FB_TK_PP_DEFINE
180  ppDefine( FALSE )
181 
182  '' MACRO ID '(' ID (',' ID)* ')' Comment? EOL
183  '' MacroBody*
184  '' ENDMACRO
185  case FB_TK_PP_MACRO
187  ppDefine( TRUE )
188 
189  '' UNDEF ID
190  case FB_TK_PP_UNDEF
191  dim as FBSYMCHAIN ptr chain_ = any
192  dim as FBSYMBOL ptr base_parent = any
193 
195 
196  chain_ = cIdentifier( base_parent, FB_IDOPT_NONE )
197  if( chain_ <> NULL ) then
198  dim as FBSYMBOL ptr sym = chain_->sym
199  '' don't remove if it was defined inside any namespace (any
200  '' USING reference to that ns would break its linked-list)
201  if( symbGetNamespace( sym ) <> @symbGetGlobalNamespc( ) ) then
202  errReport( FB_ERRMSG_CANTREMOVENAMESPCSYMBOLS )
203  else
204  if( symbGetCantUndef( sym ) ) then
205  errReport( FB_ERRMSG_CANTUNDEF )
206  else
207  '' Preserve #undef under -pp, except if #undeffing a macro,
208  '' which won't be preserved (only other symbols will be)
209  if( env.ppfile_num > 0 ) then
210  if( symbIsDefine( sym ) = FALSE ) then
211  lexPPOnlyEmitText( "#undef" )
213  end if
214  end if
215  '' Forget the symbol so it's no longer found by lookups,
216  '' but don't fully delete it, since it might already be used somewhere.
218  end if
219  end if
220  end if
221 
222  lexSkipToken( )
223 
224  '' IFDEF ID
225  '' IFNDEF ID
226  '' IF ID '=' LITERAL
227  case FB_TK_PP_IFDEF, FB_TK_PP_IFNDEF, FB_TK_PP_IF
228  ppCondIf( )
229 
230  '' ELSE
231  case FB_TK_PP_ELSE, FB_TK_PP_ELSEIF
232  ppCondElse( )
233 
234  '' ENDIF
235  case FB_TK_PP_ENDIF
236  ppCondEndIf( )
237 
238  '' ASSERT Expression
239  case FB_TK_PP_ASSERT
240  lexSkipToken( )
241  ppAssert( )
242 
243  '' PRINT LITERAL*
244  case FB_TK_PP_PRINT
245  lexSkipToken( )
246  print *ppReadLiteral( )
247 
248  '' ERROR LITERAL*
249  case FB_TK_PP_ERROR
250  lexSkipToken( )
251  errReportEx( -1, *ppReadLiteral( ) )
252 
253  '' INCLUDE ONCE? LIT_STR
254  case FB_TK_PP_INCLUDE
255  lexSkipToken( )
256  ppInclude( )
257 
258  '' INCLIB LIT_STR
259  case FB_TK_PP_INCLIB
260  lexSkipToken( )
261  ppIncLib( )
262 
263  '' LIBPATH LIT_STR
264  case FB_TK_PP_LIBPATH
265  lexSkipToken( )
266  ppLibPath( )
267 
268  '' PRAGMA ...
269  case FB_TK_PP_PRAGMA
270  lexSkipToken( )
271  ppPragma( )
272 
273  case FB_TK_PP_LINE
274  lexSkipToken( )
275  ppLine()
276 
277  case FB_TK_PP_LANG
278  lexSkipToken( )
279  ppLang( )
280 
281  case else
282  errReport( FB_ERRMSG_SYNTAXERROR )
283  end select
284 
285  '' Comment?
286  cComment( )
287 
288  '' emit the current line in text form
289  hEmitCurrLine( )
290 
291  '' EOL
292  if( lexGetToken( ) <> FB_TK_EOL ) then
293  if( lexGetToken( ) <> FB_TK_EOF ) then
294  errReport( FB_ERRMSG_EXPECTEDEOL )
295  '' error recovery: skip until next line
296  hSkipUntil( FB_TK_EOL )
297  end if
298  end if
299 end sub
300 
301 '':::::
302 '' ppInclude = '#'INCLUDE ONCE? LIT_STR
303 ''
304 sub ppInclude()
305  static as zstring * FB_MAXPATHLEN+1 incfile
306  dim as integer isonce = any
307 
308  '' ONCE?
309  isonce = FALSE
310  if( lexGetClass( ) = FB_TKCLASS_IDENTIFIER ) then
311  if( hMatchText( "ONCE" ) ) then
312  isonce = TRUE
313  end if
314  end if
315 
316  if( lexGetClass( ) <> FB_TKCLASS_STRLITERAL ) then
317  errReport( FB_ERRMSG_SYNTAXERROR )
318  '' error recovery: skip
319  lexSkipToken( )
320  return
321  end if
322 
323  lexEatToken( incfile )
324 
325  fbIncludeFile( incfile, isonce )
326 end sub
327 
328 '':::::
329 '' ppIncLib = '#'INCLIB LIT_STR
330 ''
331 sub ppIncLib( )
332  if( lexGetClass( ) <> FB_TKCLASS_STRLITERAL ) then
333  errReport( FB_ERRMSG_SYNTAXERROR )
334  '' error recovery: skip
335  lexSkipToken( )
336  return
337  end if
338 
339  '' Preserve under -pp
340  if( env.ppfile_num > 0 ) then
341  lexPPOnlyEmitText( "#inclib" )
343  end if
344 
345  fbAddLib( lexGetText( ) )
346  lexSkipToken( )
347 end sub
348 
349 '':::::
350 '' ppLibPath = '#'LIBPATH LIT_STR
351 ''
352 sub ppLibPath( )
353  if( lexGetClass( ) <> FB_TKCLASS_STRLITERAL ) then
354  errReport( FB_ERRMSG_SYNTAXERROR )
355  '' error recovery: skip
356  lexSkipToken( )
357  return
358  end if
359 
360  '' Preserve under -pp
361  if( env.ppfile_num > 0 ) then
362  lexPPOnlyEmitText( "#libpath" )
364  end if
365 
367  lexSkipToken( )
368 end sub
369 
370 '':::::
371 '' ppLine = '#'LINE LIT_NUM LIT_STR?
372 ''
373 sub ppLine()
374  '' LIT_NUM
375  if( lexGetClass( ) <> FB_TKCLASS_NUMLITERAL ) then
376  errReport( FB_ERRMSG_SYNTAXERROR )
377  '' error recovery: skip
378  hSkipUntil( FB_TK_EOL )
379  else
380  lex.ctx->linenum = valint( *lexGetText( ) )
381  lexSkipToken( )
382 
383  '' LIT_STR?
384  if( lexGetClass( ) = FB_TKCLASS_STRLITERAL ) then
385  env.inf.name = *lexGetText( )
386  lexSkipToken( )
387  end if
388  end if
389 end sub
390 
391 '':::::
392 '' ppLang = '#'LANG LIT_STR
393 ''
394 sub ppLang( )
395  dim as FB_LANG id = any
396 
397  if( lexGetClass( ) <> FB_TKCLASS_STRLITERAL ) then
398  errReport( FB_ERRMSG_SYNTAXERROR )
399  '' error recovery: skip
400  lexSkipToken( )
401  exit sub
402  end if
403 
404  id = fbGetLangId( lexGetText( ) )
405  if( id = FB_LANG_INVALID ) then
406  errReport( FB_ERRMSG_INVALIDLANG )
407  lexSkipToken( )
408  exit sub
409  end if
410 
411  '' Preserve under -pp
412  if( env.ppfile_num > 0 ) then
413  lexPPOnlyEmitText( "#lang """ + fbGetLangName( id ) + """" )
414  end if
415 
416  fbChangeOption( FB_COMPOPT_LANG, id )
417  lexSkipToken( )
418 end sub
419 
420 '':::::
421 sub hRtrimMacroText _
422  ( _
423  byval text as DZSTRING ptr _
424  ) static
425 
426  dim as zstring ptr p
427 
428  '' remove the white-spaces (including nl)
429  if( text = NULL ) then
430  exit sub
431  elseif( text->data = NULL ) then
432  exit sub
433  end if
434 
435  p = text->data + (text->len - 1)
436  do while( p > text->data )
437 
438  select case as const (*p)[0]
439  '' space or nl?
440  case CHAR_SPACE, CHAR_TAB, CHAR_LF
441  text->len -= 1
442  *p = 0
443 
444  case else
445  exit do
446  end select
447 
448  p -= 1
449  loop
450 
451 end sub
452 
453 '':::::
454 function ppReadLiteral _
455  ( _
456  byval ismultiline as integer _
457  ) as zstring ptr
458 
459  static as DZSTRING text
460  dim as integer nestedcnt = 0
461 
462  DZstrReset( text )
463 
464  do
465  select case as const lexGetToken( LEX_FLAGS )
466  case FB_TK_EOF
467  if( ismultiline ) then
468  errReport( FB_ERRMSG_EXPECTEDMACRO )
469  end if
470 
471  exit do
472 
473  '' nl?
474  case FB_TK_EOL
475  if( ismultiline = FALSE ) then
476  exit do
477  end if
478 
479  '' multi-line, only add if it's not at the beginning
480  if( text.len > 0 ) then
481  '' just lf
482  DZstrConcatAssign( text, LFCHAR )
483  end if
484 
485  lexSkipToken( LEX_FLAGS )
486 
487  continue do
488 
489  case FB_TK_COMMENT, FB_TK_REM
490  if( ismultiline = FALSE ) then
491  exit do
492  end if
493 
494  do
495  lexSkipToken( LEX_FLAGS )
496 
497  select case lexGetToken( LEX_FLAGS )
498  case FB_TK_EOL, FB_TK_EOF
499  exit do
500  end select
501  loop
502 
503  continue do
504 
505  '' '#'?
506  case CHAR_SHARP
507  select case lexGetLookAhead( 1, (LEX_FLAGS or LEXCHECK_KWDNAMESPC) and _
508  (not LEXCHECK_NOWHITESPC) )
509  '' '#' macro?
510  case FB_TK_PP_MACRO
511  if( ismultiline ) then
512  nestedcnt += 1
513  end if
514 
515  '' '#' endmacro?
516  case FB_TK_PP_ENDMACRO
517  if( ismultiline ) then
518  '' not nested?
519  if( nestedcnt = 0 ) then
520  lexSkipToken( LEX_FLAGS )
521  '' no LEX_FLAGS, white-spaces must be skipped
522  lexSkipToken( )
523 
524  hRtrimMacroText( @text )
525 
526  exit do
527  end if
528 
529  nestedcnt -= 1
530  end if
531 
532  end select
533 
534  '' white space?
535  case CHAR_SPACE, CHAR_TAB
536 
537  '' only add if it's not at the beginning
538  if( text.len > 0 ) then
539  '' condensed to a single white-space
540  DZstrConcatAssign( text, " " )
541  end if
542 
543  lexSkipToken( LEX_FLAGS )
544 
545  continue do
546 
547  case FB_TK_TYPEOF
548  DZstrConcatAssign( text, ppTypeOf( ) )
549  exit do
550 
551  end select
552 
553  '' anything else..
554  if( lexGetType() <> FB_DATATYPE_WCHAR ) then
555  DZstrConcatAssign( text, lexGetText( ) )
556  else
557  DZstrConcatAssignW( text, lexGetTextW( ) )
558  end if
559 
560  lexSkipToken( LEX_FLAGS )
561 
562  loop
563 
564  function = text.data
565 
566 end function
567 
568 '':::::
570  ( _
571  byval text as DWSTRING ptr _
572  ) static
573 
574  dim as wstring ptr p
575 
576  '' remove the white-spaces (including nl)
577 
578  p = text->data + (text->len - 1)
579  do while( p > text->data )
580 
581  select case as const (*p)[0]
582  '' space or nl?
583  case CHAR_SPACE, CHAR_TAB, CHAR_LF
584  text->len -= 1
585  *p = 0
586 
587  case else
588  exit do
589  end select
590 
591  p -= 1
592  loop
593 
594 end sub
595 
596 '':::::
597 function ppReadLiteralW _
598  ( _
599  byval ismultiline as integer _
600  ) as wstring ptr
601 
602  static as DWSTRING text
603  dim as integer nestedcnt = 0
604 
605  DWstrAllocate( text, 0 )
606 
607  do
608  select case as const lexGetToken( LEX_FLAGS )
609  case FB_TK_EOF
610  if( ismultiline ) then
611  errReport( FB_ERRMSG_EXPECTEDMACRO )
612  end if
613 
614  exit do
615 
616  '' nl?
617  case FB_TK_EOL
618  if( ismultiline = FALSE ) then
619  exit do
620  end if
621 
622  '' multi-line, only add if it's not at the beginning
623  if( text.len > 0 ) then
624  '' just lf
625  DWstrConcatAssign( text, wstr( LFCHAR ) )
626  end if
627 
628  lexSkipToken( LEX_FLAGS )
629 
630  continue do
631 
632  case FB_TK_COMMENT, FB_TK_REM
633  if( ismultiline = FALSE ) then
634  exit do
635  end if
636 
637  do
638  lexSkipToken( LEX_FLAGS )
639 
640  select case lexGetToken( LEX_FLAGS )
641  case FB_TK_EOL, FB_TK_EOF
642  exit do
643  end select
644  loop
645 
646  continue do
647 
648  '' '#'?
649  case CHAR_SHARP
650  select case lexGetLookAhead( 1, (LEX_FLAGS or LEXCHECK_KWDNAMESPC) and _
651  (not LEXCHECK_NOWHITESPC) )
652  '' '#' macro?
653  case FB_TK_PP_MACRO
654  if( ismultiline ) then
655  nestedcnt += 1
656  end if
657 
658  '' '#' endmacro?
659  case FB_TK_PP_ENDMACRO
660  if( ismultiline ) then
661  '' not nested?
662  if( nestedcnt = 0 ) then
663  lexSkipToken( LEX_FLAGS )
664  '' no LEX_FLAGS, white-spaces must be skipped
665  lexSkipToken( )
666 
667  hRtrimMacroTextW( @text )
668 
669  exit do
670  end if
671 
672  nestedcnt -= 1
673  end if
674 
675  end select
676 
677  '' white space?
678  case CHAR_SPACE, CHAR_TAB
679 
680  '' only add if it's not at the beginning
681  if( text.len > 0 ) then
682  '' condensed to a single white-space
683  DWstrConcatAssign( text, wstr( " " ) )
684  end if
685 
686  lexSkipToken( LEX_FLAGS )
687 
688  continue do
689 
690  case FB_TK_TYPEOF
691  DWstrConcatAssignA( text, ppTypeOf( ) )
692  exit do
693 
694  end select
695 
696  '' anything else..
697  if( lexGetType( ) = FB_DATATYPE_WCHAR ) then
698  DWstrConcatAssign( text, lexGetTextW( ) )
699  else
700  DWstrConcatAssignA( text, lexGetText( ) )
701  end if
702 
703  lexSkipToken( LEX_FLAGS )
704 
705  loop
706 
707  function = text.data
708 
709 end function
710 
711 function ppTypeOf( ) as string
712  '' get type's name
713  dim as integer dtype = any
714  dim as longint lgt = any
715  dim as FBSYMBOL ptr subtype = any
716 
717  '' TYPEOF
718  lexSkipToken( )
719 
720  '' '('
721  if( lexGetToken( ) <> CHAR_LPRNT ) then
722  errReport( FB_ERRMSG_EXPECTEDLPRNT )
723  else
725  end if
726 
727  cTypeOf( dtype, subtype, lgt )
728 
729  function = ucase( symbTypeToStr( dtype, subtype, lgt ) )
730 
731  '' ')'
732  if( lexGetToken( ) <> CHAR_RPRNT ) then
733  errReport( FB_ERRMSG_EXPECTEDRPRNT )
734  '' error recovery: skip until next ')'
735  hSkipUntil( CHAR_RPRNT )
736  else
738  end if
739 end function
740