Code archives/File Utilities/Super-simple C-style preprocessor

This code has been declared by its author to be Public Domain code.

Download source code

Super-simple C-style preprocessor by Yasha2010
This is the preprocessor I originally tried to force on top of my failed QuakeC implementation. It's since found its way into my other projects, including Objective-B3D. 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
;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\tok=Trim(Left(srcline,sep))
							m\def=Trim(Mid(srcline,sep+1))
						Else
							m\tok=srcline
							m\def=""
						EndIf
						
						For n=Each PP_Macro
							If n<>m And m\tok=n\tok Then PP_Error("Token "+m\tok+" 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\tok=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\tok=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\tok=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\tok),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\tok))=m\tok
											srcline=Left(srcline,ptr-1)+m\def+Right(srcline,Len(srcline)-(ptr-1)-Len(m\tok))
										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\tok=lArg Then lArg=m\def
		If m\tok=rArg Then rArg=m\def
	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.

Code Archives Forum