[bb] Super-simple C-style preprocessor by Yasha [ 1+ years ago ]

Started by BlitzBot, June 29, 2017, 00:28:38

Previous topic - Next topic

BlitzBot

Title : Super-simple C-style preprocessor
Author : Yasha
Posted : 1+ years ago

Description : This is the preprocessor I originally tried to force on top of my <a href="../Community/posts6473.html?topic=84763" target="_blank">failed QuakeC implementation</a>. It's since found its way into my other projects, including <a href="../Community/posts17a0.html?topic=87642" target="_blank">Objective-B3D</a>. It's not actually all that useful (no macros!), and nowadays many people (read: Java users) are opposed to this sort of thing altogether, but here it is if anyone wants it. Bear in mind that this doesn't include any way to preserve line numbering or file names (useful for error messages), which is why I've stopped using it.

(taken from the code below)
;Commands:
;   #define NAME CONSTANT - replace all occurences of NAME with CONSTANT. Cannot refer to other #defines
;   #undef NAME - remove a #defined constant so it isn't replaced any more
;   #if EXPRESSION - either compare a constant to another constant (literal or #defined) or assert that it's nonzero
;   #ifdef NAME - include the following block of code only if NAME has been #defined
;   #ifndef NAME - include the following block of code only if NAME has not been #defined
;   #elif EXPRESSION - short for ElseIf
;   #else - if the last check evaluated false, do this instead
;   #endif - mark the end of a conditional inclusion block. These are allowed to nest
;   #include FILE - copy the entire contents of the specified filename (quotemarks optional)
;   #option OPTION - does nothing: add language-relevant options here
;   #error ERR - issues error ERR via error handler. Best used with a condition check...!

;Notes:
;   - #define does not create macros, only simple substitution constants
;   - commands must be the first text on their line
;   - any text after a command will potentially be considered part of it, or at best ignored


Code :
Code (blitzbasic) Select
;General-purpose C-like directive processor
;==========================================


;Not very useful by itself, but you can plug it in to a code processor or script compiler easily


;Commands:
; #define NAME CONSTANT - replace all occurences of NAME with CONSTANT. Cannot refer to other #defines
; #undef NAME - remove a #defined constant so it isn't replaced any more
; #if EXPRESSION - either compare a constant to another constant (literal or #defined) or assert that it's nonzero
; #ifdef NAME - include the following block of code only if NAME has been #defined
; #ifndef NAME - include the following block of code only if NAME has not been #defined
; #elif EXPRESSION - short for ElseIf
; #else - if the last check evaluated false, do this instead
; #endif - mark the end of a conditional inclusion block. These are allowed to nest
; #include FILE - copy the entire contents of the specified filename (quotemarks optional)
; #option OPTION - does nothing: add language-relevant options here
; #error ERR - issues error ERR via error handler. Best used with a condition check...!

;Notes:
; - #define does not create macros, only simple substitution constants
; - commands must be the first text on their line
; - any text after a command will potentially be considered part of it, or at best ignored


;Replace these with tokens that match your language
Const PP_LINECOMMENT$ = "//" ;Single-line comment
Const PP_BLOCKCOMMENTSTART$ = "/*" ;Start of block-comment
Const PP_BLOCKCOMMENTEND$ = "*/" ;End of block-comment
Const PP_ESCAPECHR$ = "" ;Escape character (to allow " to appear in strings)

Type PP_Macro ;As defined with #define. No regular expressions, just cut and paste
Field tok$ ;Constant token to use in source
Field def$ ;Definition
End Type

Function PP_Preprocess(inputFilename$,outputFilename$) ;Loads the source from file and applies preprocessor commands
Local m.PP_Macro,n.PP_Macro,pos,ptr,i
Local com,sep,skip,debugout,srcline$,remain$
Local inquote,incomment,cfile,filelist=CreateBank(4)
Local sourceBank=CreateBank(4),iStack=CreateBank(0)

cfile=ReadFile(inputFilename)
PokeInt(filelist,0,cfile)
Repeat
srcline=ReadLine(cfile)

If Left(Trim(srcline),1)="#"
srcline=Replace(srcline,Chr(9)," ")
com=Instr(srcline,PP_BLOCKCOMMENTSTART)
If com>0
remain=Right(srcline,Len(srcline)-(com-1)) ;Rescue comments with /* as otherwise there will be parse errors later

ResizeBank sourceBank,BankSize(sourceBank)+Len(remain)+2
For ptr=0 To Len(remain)-1
PokeByte sourceBank,pos+ptr,Asc(Mid(remain,ptr+1,1))
Next
PokeShort sourceBank,pos+ptr,$A0D ;CR+LF - end of line
pos=pos+ptr+2

srcline=Left(srcline,com-1)
EndIf

com=Instr(srcline,PP_LINECOMMENT)
If com>0 Then srcline=Left(srcline,com-1)
srcline=Trim(srcline)+" " ;Force control line to end in a space

Select True
Case Lower(Left(srcline,8))="#define " ;Add a macro
If skip=0
m=New PP_Macro
srcline=Trim(Mid(srcline,8))
sep=Instr(srcline," ")

If sep
m ok=Trim(Left(srcline,sep))
mdef=Trim(Mid(srcline,sep+1))
Else
m ok=srcline
mdef=""
EndIf

For n=Each PP_Macro
If n<>m And m ok=n ok Then PP_Error("Token "+m ok+" is already defined")
Next
EndIf

Case Lower(Left(srcline,7))="#undef " ;Remove a macro
If skip=0
srcline=Trim(Mid(srcline,7))
For m=Each PP_Macro
If m ok=srcline Then Delete m:Exit
Next
If m=Null Then PP_Error("token "+Chr(34)+srcline+Chr(34)+" does not exist")
EndIf

Case Lower(Left(srcline,4))="#if " ;Conditional compilation by value
srcline=Trim(Mid(srcline,4))
If skip=0
ResizeBank iStack,BankSize(iStack)+1
skip=skip+(Not(PP_EvalDirective(srcline)))
PokeByte iStack,BankSize(iStack)-1,Not(skip)
Else
skip=skip+1
EndIf

Case Lower(Left(srcline,7))="#ifdef " ;Conditional compilation by definition
srcline=Trim(Mid(srcline,7))
If skip=0
ResizeBank iStack,BankSize(iStack)+1
skip=skip+1
For m=Each PP_Macro
If m ok=srcline Then skip=skip-1:Exit
Next
PokeByte iStack,BankSize(iStack)-1,Not(skip)
Else
skip=skip+1
EndIf

Case Lower(Left(srcline,8))="#ifndef " ;Conditional lack of compilation by definition
srcline=Trim(Mid(srcline,8))
If skip=0
ResizeBank iStack,BankSize(iStack)+1
For m=Each PP_Macro
If m ok=srcline Then skip=skip+1:Exit
Next
PokeByte iStack,BankSize(iStack)-1,Not(skip)
Else
skip=skip+1
EndIf

Case Lower(Left(srcline,6))="#elif " ;Alternate condition check
If skip=1
srcline=Trim(Mid(srcline,6))
If PeekByte(iStack,BankSize(iStack)-1)=0 ;Only try if this #if level hasn't done anything yet
If PP_EvalDirective(srcline)
PokeByte iStack,BankSize(iStack)-1,1
skip=0
EndIf
Else
skip=1
EndIf
EndIf

Case Lower(Left(srcline,6))="#else " ;Default condition
If skip=1
If PeekByte(iStack,BankSize(iStack)-1)=0 Then skip=0
ElseIf skip=0
skip=1
EndIf

Case Lower(Left(srcline,7))="#endif " ;End of conditional compilation block
If skip>0 Then skip=skip-1
If skip=0
If BankSize(iStack) Then ResizeBank iStack,BankSize(iStack)-1
EndIf

Case Lower(Left(srcline,9))="#include " ;Include files
If skip=0
srcline=Trim(Mid(srcline,9))
If Left(srcline,1)=Chr(34) And Right(srcline,1)=Chr(34) Then srcline=Mid(srcline,2,Len(srcline)-2) ;Cut off quote marks
cfile=ReadFile(srcline)
If cfile=0 Then PP_Error "file "+Chr(34)+srcline+Chr(34)+" does not exist"
ResizeBank(filelist,BankSize(filelist)+4)
PokeInt filelist,BankSize(filelist)-4,cfile
EndIf

Case Lower(Left(srcline,8))="#option " ;Set compilation options
If skip=0
srcline=Trim(Mid(srcline,8))
Select srcline
;Left empty for future expansion
Default:PP_Error("unrecognised compiler option")
End Select
EndIf

Case Lower(Left(srcline,7))="#error "
If skip=0
srcline=Trim(Mid(srcline,7))
PP_Error(srcline)
EndIf

Default
PP_Error("unrecognised preprocessor command: "+Chr(34)+srcline+Chr(34))
End Select
Else
If skip=0
If First PP_Macro<>Null ;Replace #defined tokens
inquote=0
For ptr=1 To Len(srcline)
If Mid(srcline,ptr,2)=PP_LINECOMMENT Then Exit
If Mid(srcline,ptr,1)=Chr(34)
If inquote=0
inquote=True
Else
If Mid(srcline,ptr-1,1)<>PP_ESCAPECHR Then inquote=False
EndIf
EndIf
If Mid(srcline,ptr,2)=PP_BLOCKCOMMENTSTART Then incomment=True:ElseIf Mid(srcline,ptr,2)=PP_BLOCKCOMMENTEND Then incomment=False
If inquote=False And incomment=False
For m=Each PP_Macro
If ptr>1 Then i=Asc(Mid(srcline,ptr-1,1)):Else i=0
If Not((i>47 And i<58) Or (i>64 And i<91) Or i=95 Or (i>96 And i<123))
i=Asc(Mid(srcline,ptr+Len(m ok),1))
If Not((i>47 And i<58) Or (i>64 And i<91) Or i=95 Or (i>96 And i<123))
If Mid(srcline,ptr,Len(m ok))=m ok
srcline=Left(srcline,ptr-1)+mdef+Right(srcline,Len(srcline)-(ptr-1)-Len(m ok))
EndIf
EndIf
EndIf
Next
EndIf
Next
EndIf

ResizeBank sourceBank,BankSize(sourceBank)+Len(srcline)+2
For ptr=0 To Len(srcline)-1
PokeByte sourceBank,pos+ptr,Asc(Mid(srcline,ptr+1,1))
Next
PokeShort sourceBank,pos+ptr,$A0D ;CR+LF - end of line
pos=pos+ptr+2
EndIf
EndIf

If Eof(cfile)
CloseFile cfile
ResizeBank filelist,BankSize(filelist)-4
If BankSize(filelist)=0 Then Exit:Else cfile=PeekInt(filelist,BankSize(filelist)-4)
EndIf
Forever

;Dump out a copy of the source to be read by the tokeniser
debugout=WriteFile(outputFilename)
WriteBytes(sourceBank,debugout,0,BankSize(sourceBank))
CloseFile debugout

FreeBank filelist ;Tidy up
FreeBank sourceBank
FreeBank iStack
Delete Each PP_Macro
End Function

Function PP_EvalDirective(directive$) ;Evaluate an #if directive
Local lArg$,op$,rArg$,i,m.PP_Macro

i=Instr(directive," ")
lArg=Trim(Left(directive,i))
directive=Trim(Mid(directive,i))

i=Instr(directive," ")
op=Trim(Left(directive,i))
rArg=Trim(Mid(directive,i))

For m=Each PP_Macro
If m ok=lArg Then lArg=mdef
If m ok=rArg Then rArg=mdef
Next

Select op
Case "=","==" ;On tests for equality, strings will be compared directly so "6.0" != "6" - must be converted if floats are involved
If Instr(lArg,".") Or Instr(rArg,".")
Return (Float(lArg) = Float(rArg))
Else
Return lArg=rArg
EndIf

Case "<>","!="
If Instr(lArg,".") Or Instr(rArg,".")
Return (Float(lArg) <> Float(rArg))
Else
Return lArg <> rArg
EndIf

Case "<" ;On tests that imply numerical value, strings will be automagically converted!
Return lArg < rArg

Case ">"
Return lArg > rArg

Case "<="
Return lArg <= rArg

Case ">="
Return lArg >= rArg

Case ""
Return lArg<>0

End Select
End Function

Function PP_Error(errorMessage$) ;Replace calls to this with your compiler's main error handler function
Print "ERROR: "+errorMessage
WaitKey:End
End Function


Comments : none...