#!gawk -f
# dsw2mak.awk
#
# An Awk script that generates a unix Makefile from a
# Microsoft Developer Studio workspace file.
#
# Copyright (C) 2001  José Fonseca
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
# for more details.
#
# José Fonseca <j_r_fonseca@yahoo.co.uk>
#
# Features:
#   - generation of GUI applications (including resource files), 
#     DLLs, console applications and static libraries
#   - translations of the most common compiler and linker options
#   - conversion of workspace files (.dsw) and all associated 
#     projects files (.dsp) generating all necessary Makefiles
#   - handling of nested !IF, !ELSEIF and !ENDIF maintaining the 
#     same build configurations as the original project
#   - automatic generation of the dependencies
#
# Example:
#   gawk -f dsw2mak.awk MyApp.dsw
#
# Notes:
#   - Make sure that both this script and the input files are in
#     a line ending convention that gawk version in your system
#     can handle.
#   - If an option is not handled by this script don't edit all 
#     generate Makefiles by hand. Add support for the option in 
#     this script and submit your additions to the author.
#
# Changelog (incomplete):
#   2003-11-25: Amitai Yuval
#     Strip DOS line-endings from DSW's too.
#  
#   2002-11-07: Alain Touret
#     Fix bug in the linker output target determination.
#     Support for C++ source files with .cc and .cxx extensions.
#
#   2001-02-18: José Fonseca
#     Improved linker libraries and options handling.
#     Debug output.
#     Better handling of custom builds
#
#   2001-02-15: José Fonseca
#     Improved C compiler options handling.
#     More verbose warning output.
#
#   2001-02-14: José Fonseca
#     Added comments to the source code.
#


# check and remove unnecessary quotes from a string
function fixquotes(str) {
	if(str ~ /^"[^[:blank:]]+"$/) {
		sub(/^"/, "", str);
		sub(/"$/, "", str);
	}
	
	return str
}

# fixes a path string
function fixpath(path) {
	# remove leading and trainling whitespaces
	sub(/^[[:blank:]]+/, "", path);
	sub(/[[:blank:]]+$/, "", path);
	
	# check and remove unnecessary quotes
	path = fixquotes(path)
	
	# change the forward slashes to backslashes
	gsub(/\\/, "/", path)
	
	# remove reduntant ./ directories
	gsub(/^(\.\/)+/, "", path)
	gsub(/^\/(\.\/)+/, "", path)
	
	return path
}

# get the base directory from a path
function basedir(path) {
	# remove leading and trainling whitespaces
	sub(/^[[:blank:]]+/, "", path);
	sub(/[[:blank:]]+$/, "", path);
	
	# remove the quotes
	if(path ~ /^".+"$/) {
		sub(/^"/, "", path);
		sub(/"$/, "", path);
	}
	
	# remove the leading path
	sub(/(^|[\/\\:])[^\/\\:]*$/, "", path)
	
	# add quotes if needed
	if(path ~ /[[:blank:]]/)
		path = "\"" path "\""
	
	return path
}

# get the filename from a path
function basefile(path) {
	# remove leading and trainling whitespaces
	sub(/^[[:blank:]]+/, "", path);
	sub(/[[:blank:]]+$/, "", path);
	
	# remove the quotes
	if(path ~ /^".+"$/) {
		sub(/^"/, "", path);
		sub(/"$/, "", path);
	}
	
	# remove the trailing path
	sub(/^.*[\/\\:]/, "", path)
	
	# add quotes if needed
	if(path ~ /[[:blank:]]/)
		path = "\"" path "\""
		
	return path
}

# skip lines until matching a given regular expression
# NOTE: not used but it could be eventually handy
function skip(regexp, infile, ret) {
	while((ret = getline < infile) == 1 && $0 !~ regexp) {}
	
	return ret
}

# parses a project file (.dsp) specified by 'infile' and generates a makefile to 'outfile'
function parse_dsp(infile, outfile, i) {
	print infile
	
	# this specifies verbose debug output
	debug = 0

	# this specifies a prefix to the binutils and gcc binaries
	#prefix = "mingw32-"
	prefix = ""

	# this specifies the name of the 'rm -f' or equivalent command
	rm = "rm -f"

	# check for a bad file
	if((getline < infile) == -1) { 
		print infile ": " ERRNO
		return
	}
	
	# Strip DOS line-endings
	gsub(/\r$/, "")
	
	# count the number of lines
	inline = 1
	
	# print the Makefile header
	print "# Makefile - " basefile(infile) > outfile
	print "" > outfile

	# this specifies the default name for the dependencies file
	dependencies = ".dependencies"

	# attemp to get the project name
	if(/^# Microsoft Developer Studio Project File/) {
		name = gensub(/^# Microsoft Developer Studio Project File - Name="(.*)".*$/, "\\1", "1")
		dependencies = name ".dep"
	}

	# main loop
	while((getline < infile) == 1) {
		# Strip DOS line-endings
		gsub(/\r$/, "")
		
		# increment the number of lines
		inline = inline + 1
		
		# catch the target type definition
		if(/^# TARGTYPE/) {
			if (/[[:space:]]0x0101$/) {
				# Win32 (x86) Application
				exeflag = 1
				dllflag = 0
				libflag = 0
			}
			if (/[[:space:]]0x0102$/) {
				# Win32 (x86) Dynamic-Link Library
				exeflag = 0
				dllflag = 1
				libflag = 0
			}
			if (/[[:space:]]0x0103$/) {
				# Win32 (x86) Console Application
				exeflag = 1
				dllflag = 0
				libflag = 0
			}
			if (/[[:space:]]0x0104$/) {
				# Win32 (x86) Static Library
				exeflag = 0
				dllflag = 0
				libflag = 1
			}
			continue
		}
		
		# catch the default configuration definition
		if(/^CFG=/) {
			print "ifndef CFG" > outfile
			print > outfile
			print "endif" > outfile
		}
		
		# deal with the preprocessor commands
		if(/^!/) {
			# as GNU make doesn't have the '!ELSEIF' equivalent we have to use nested 'if ... else .. endif' to obtain the same effect
			# a stack is used to keep track of the current nested level
		
			if(/^!IF/) {
				$0 = gensub(/^!IF[[:space:]]+(.+)[[:space:]]*==[[:space:]]*(.+)$/, "ifeq \\1 \\2", "1")
				$0 = gensub(/^!IF[[:space:]]+(.+)[[:space:]]*!=[[:space:]]*(.+)$/, "ifneq \\1 \\2", "1")
				print > outfile
				stacktop += 1
				stack[stacktop] = 1
				continue
			}
			if(/^!ELSE$/) {
				print "else"
			}
			if(/^!ELSEIF/) {
				$0 = gensub(/^!ELSEIF[[:space:]]+(.+)[[:space:]]*==[[:space:]]*(.+)$/, "else\nifeq \\1 \\2", "1")
				$0 = gensub(/^!ELSEIF[[:space:]]+(.+)[[:space:]]*!=[[:space:]]*(.+)$/, "else\nifneq \\1 \\2", "1")
				print > outfile
				stack[stacktop] += 1
				continue
			}
			if(/^!ENDIF[[:space:]]*$/) {
				for (i = 0; i < stack[stacktop]; i++)
					print "endif" > outfile
				stacktop -= 1
				continue
			}
		}
		
		# catch the C++ compiler definition
		if(/^CPP=/) {
			print "CC=" prefix "gcc" > outfile
			print "CFLAGS=" > outfile
			print "CXX=" prefix "g++" > outfile
			print "CXXFLAGS=$(CFLAGS)" > outfile
			
			continue
		}
		
		# catch the C++ compiler flags
		if(/^# ADD CPP /) {
			if (debug)
				print infile ":" inline ": " $0
		
			# extract the flags from the line
			cflags = $0
			sub(/^# ADD CPP /, "", cflags)
			
			split(" " cflags, options, /[[:space:]]+\//)
			
			cflags = ""
			for(i in options) {
				option = options[i]
			
				# translate the options
				# some of the translations effectively remove the option (and its arguments) since there is no translation equivalent
				
				if (option == "") {
				} else if(option ~ /^nologo$/) {
					# Suppress Startup Banner and Information Messages
					option = "" 
				} else if (option ~ /^W0$/) {
					# Turns off all warning messages
					option = "-w"
				} else if (option ~ /^W[123]$/) {
					# Warning Level
					option = "-W"
				} else if (option ~ /^W4$/) {
					# Warning Level
					option = "-Wall"
				} else if (option ~ /^WX$/) {
					# Warnings As Errors
					option = "-Werror"
				} else if (option ~ /^Gm$/) {
					# Enable Minimal Rebuild
					option = "" 
				} else if (option ~ /^GX$/) {
					# Enable Exception Handling
					option = "-fexceptions" 
				} else if (option ~ /^Z[d7iI]$/) {
					# Debug Info
					option = "-g" 
				} else if (option ~ /^Od$/) {
					# Disable Optimizations
					option = "-O0" 
				} else if (option ~ /^O1$/) {
					# Minimize Size
					option = "-Os" 
				} else if (option ~ /^O2$/) {
					# Maximize Speed
					option = "-O2" 
				} else if (option ~ /^Ob0$/) {
					# Disables inline Expansion 
					option = "-fno-inline"
				} else if (option ~ /^Ob1$/) {
					# In-line Function Expansion
					option = "" 
				} else if (option ~ /^Ob2$/) {
					# auto In-line Function Expansion
					option = "-finline-functions" 
				} else if (option ~ /^Oy$/) {
					# Frame-Pointer Omission
					option = "-fomit-frame-pointer" 
				} else if (option ~ /^GZ$/) {
					# Catch Release-Build Errors in Debug Build
					option = "" 
				} else if (option ~ /^M[DLT]d?$/) {
					# Use Multithreaded Run-Time Library
					option = "" 
				} else if (option ~ /^D/) {
					# Preprocessor Definitions
					gsub(/^D[[:space:]]*/, "", option)
					option = "-D" fixquotes(option)
				} else if (option ~ /^I/) {
					# Additional Include Directories
					gsub(/^I[[:space:]]*/, "", option)
					option = "-I" fixpath(option)
				} else if (option ~ /^U/) {
					# Undefines a previously defined symbol
					gsub(/^U[[:space:]]*/, "", option)
					option = "-U" fixquotes(option)
				} else if (option ~ /^Fp/) {
					# Name .PCH File
					option = "" 
				} else if (option ~ /^F[Rr]/) {
					# Create .SBR File
					option = "" 
				} else if (option ~ /^YX$/) {
					# Automatic Use of Precompiled Headers
					option = "" 
				} else if (option ~ /^FD$/) {
					# Generate File Dependencies
					option = "" 
				} else if (option ~ /^c$/) {
					# Compile Without Linking
					# this option is always present and is already specified in the suffix rules
					option = "" 
				} else if (option ~ /^GB$/) {
					# Blend Optimization
					option = "-mcpu=pentiumpro -D_M_IX86=500"
				} else if (option ~ /^G6$/) {
					# Pentium Pro Optimization
					option = "-march=pentiumpro -D_M_IX86=600"
				} else if (option ~ /^G5$/) {
					# Pentium Optimization
					option = "-mcpu=pentium -D_M_IX86=500"
				} else if (option ~ /^G3$/) {
					# 80386 Optimization
					option = "-mcpu=i386 -D_M_IX86=300"
				} else if (option ~ /^G4$/) {
					# 80486 Optimization
					option = "-mcpu=i486 -D_M_IX86=400"
				} else if (option ~ /^Yc/) {
					# Create Precompiled Header
					option = ""
				} else if (option ~ /^Yu/) {
					# Use Precompiled Header
					option = ""
				} else if (option ~ /^Za$/) {
					# Disable Language Extensions
					option = "-ansi"
				} else if (option ~ /^Ze$/) {
					# Enable Microsoft Extensions
					print infile ":" inline ": /" option ": Enable Microsoft Extensions option ignored" > "/dev/stderr"
					option = "" 
				} else if (option ~ /^Zm[[:digit:]]+$/) {
					# Specify Memory Allocation Limit
					option = "" 
				} else if (option ~ /^Zp1$/) {
					# Packs structures on 1-byte boundaries
					option = "-fpack-struct" 
				} else if (option ~ /^Zp(2|4|8|16)?$/) {
					# Struct Member Alignment
					option = "" 
					print infile ":" inline ": /" option ": Struct Member Alignment option ignored" > "/dev/stderr"
				} else {
					print infile ":" inline ": /" option ": C compiler option not implemented" > "/dev/stderr"
					option = ""
				}
				
				if (option != "") {
					if(cflags == "")
						cflags = option
					else
						cflags = cflags " " option
				}
					
			}
			
			# change the slashes
			gsub(/\\/, "/", cflags)
			
			print "CFLAGS+=" cflags > outfile
			
			if (debug)
				print outfile ": " "CFLAGS+=" cflags
			
			continue
		}
		
		# catch the linker definition
		if(/^LINK32=/) {
			if (exeflag)
				print "LD=$(CXX) $(CXXFLAGS)" > outfile
			if (dllflag)
				print "LD=" prefix "dllwrap" > outfile
			
			print "LDFLAGS=" > outfile
			
			continue
		}
		
		# catch the linker flags
		if(/^# ADD LINK32 /) {
			if (debug)
				print infile ":" inline ": " $0
				
			# extract the flags from the line
			ldflags = $0
			sub(/^# ADD LINK32 /, "", ldflags)
			
			split(ldflags, options, /[[:space:]]+\//)
			
			# attempts to get the used libraries to a seperate variable
			libs = options[1]
			libs = gensub(/([[:alnum:]/\\_-]+)\.lib/, "-l\\1", "g", libs)
			delete options[1]
			
			ldflags = ""
			for(i in options) {
				option = options[i]
			
				# translate the options
				# some of the translations effectively remove the option (and its arguments) since there is no translation equivalent
				if (option == "") {
				} else if (option ~ /^base:/) {
					# Base Address
					gsub(/^base:/, "--image-base ", option)
				} else if (option ~ /^debug$/) {
					# Generate Debug Info
					option = ""
				} else if (option ~ /^dll$/) {
					# Build a DLL
					dllflag = 1
				
					# remove this option since the DLL output option is handled by the suffix rules
					option = ""
				} else if (option ~ /^incremental:[[:alpha:]]+$/) {
					# Link Incrmentally
					option = ""
				} else if (option ~ /^implib:/) {
					# Name import library
					gsub(/^implib:/, "", option)
					option = "--implib " fixpath(gensub(/([[:alnum:]_-]+)\.lib/, "lib\\1.a", "g", option))
				} else if (option ~ /^libpath:/) {
					# Additional Libpath
					gsub(/^libpath:/, "", option)
					option = "-L" fixpath(option)
				} else if (option ~ /^machine:[[:alnum:]]+$/) {
					# Specify Target Platform
					option = ""
				} else if (option ~ /^map/) {
					# Generate Mapfile
					if (option ~ /^map:/)
						gsub(/^map:/, "-Map ", option)
					else
						option = "-Map " name ".map"
				} else if(option ~ /^nologo$/) {
					# Suppress Startup Banner and Information Messages
					option = "" 
				} else if (option ~ /^out:/) {
					# Output File Name
					target = fixpath(gensub(/out:("[^"]+"|[^[:space:]]+).*$/, "\\1", "1", option))
					
					print "TARGET=" target > outfile
					
					# remove this option since the output option is handled by the suffix rules
					option = ""
				} else if (option ~ /^pdbtype:/) {
					# Program Database Storage
					option = ""
				} else if (option ~ /^subsystem:/) {
					# Specify Subsystem
					gsub(/^subsystem:/, "-Wl,--subsystem,", option)	
				} else if (option ~ /^version:[[:digit:].]+$/) {
					# Version Information
					option = ""
				} else {
					print infile ":" inline ": /" option ": linker option not implemented" > "/dev/stderr"
					option = ""
				}
				
				if (option != "") {
					if(ldflags == "")
						ldflags = option
					else
						ldflags = ldflags " " option
				}
					
			}
			
			# attempt to get the name of the target from the '/out:' option
			if (ldflags ~ /\/out:/) {	# Output File Name
			}
			
			# change the slashes
			gsub(/\\/, "/", ldflags)
						
			print "LDFLAGS+=" ldflags > outfile
			print "LIBS+=" libs > outfile
			
			if (debug) {
				print outfile ": " "LDFLAGS+=" ldflags
				print outfile ": " "LIBS+=" libs
			}
			
			continue
		}
		
		# catch the library archiver definition
		if(/^LIB32=/) {
			libflag = 1
			
			print "AR=" prefix "ar" > outfile
			
			continue
		}

		# catch the library archiver flags
		if(/^# ADD LIB32 /) {
			# extract the flags from the line
			arflags = $0
			sub(/^# ADD LIB32 /, "", arflags)
			
			# translate the options
			gsub(/\/nologo[[:space:]]*/, "", arflags)		# Suppress Startup Banner and Information Messages
			gsub(/\/machine:[[:alnum:]]+[[:space:]]*/, "", arflags)	# Specify Target Platform
			
			# attempt to get the name of the target from the '/out:' option
			if (arflags ~ /\/out:/) {
				target = fixpath(gensub(/^.*\/out:(".*"|[^[:space:]]+).*$/, "\\1", "1", arflags))
				target = basedir(target) "/lib" basefile(gensub(/(\.[^.]*)?$/, ".a", 1, target))
				
				print "TARGET=" target > outfile
				
				# remove this option since the output option is handled differentely
				sub(/\/out:(".*"|[^[:space:]]+)/, "", arflags)
			}
			
			# change the slashes
			gsub(/\\/, "/", arflags)
			
			print "ARFLAGS=rus" > outfile
			
			continue
		}
		
		# catch the resource compiler definition
		if(/^RSC=/) {
			print "RC=" prefix "windres -O COFF" > outfile
			continue
		}
		
		# handle the begin of the target definition
		if(/^# Begin Target$/) {
			print "" > outfile
		
			# print the default target name definition
			print "ifndef TARGET" > outfile
			if(exeflag)
				print "TARGET=" name ".exe" > outfile
			if(dllflag)
				print "TARGET=" name ".dll" > outfile
			if(libflag)
				print "TARGET=lib" name ".a" > outfile
			print "endif" > outfile
			print "" > outfile
		
			# print the default target and the suffix rules
			print ".PHONY: all" > outfile
			print "all: $(TARGET)" > outfile
			print "" > outfile
			print "%.o: %.c" > outfile
			print "\t$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $<" > outfile
			print "" > outfile
			print "%.o: %.cc" > outfile
			print "\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ -c $<" > outfile
			print "" > outfile
			print "%.o: %.cpp" > outfile
			print "\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ -c $<" > outfile
			print "" > outfile
			print "%.o: %.cxx" > outfile
			print "\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ -c $<" > outfile
			print "" > outfile
			print "%.res: %.rc" > outfile
			print "\t$(RC) $(CPPFLAGS) -o $@ -i $<" > outfile
			print "" > outfile
		
			# initialize some bookeeping variables
			ngroups = 0	# number of groups in the target
			nsources = 0	# number of isolated sources in the target
			groupflag = 0	# state variable that indicates if we are inside or outside of a group definition
			
			continue
		}
		
		# handle the end of a target definition
		if(/^# End Target$/) {
			# print the sources files definition that includes...
			printf "SRCS=" > outfile
			
			# ... the sources groups variables...
			for (i = 0; i < ngroups; i++)
				printf "$(%s) ", groups[i] > outfile
			
			# ... and isolated sources not included in any group
			if (nsources) {
				print " \\" > outfile
				for (i = 0; i < nsources - 1; i++)
					print "\t" sources[i] " \\" > outfile
				print "\t" sources[i] > outfile
			}
			else
				print "" > outfile
			print "" > outfile
		
			# define the objects automatically from the sources in the Makefile
			print "OBJS=$(patsubst %.rc,%.res,$(patsubst %.cxx,%.o,$(patsubst %.cpp,%.o,$(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(filter %.c %.cc %.cpp %.cxx %.rc,$(SRCS)))))))" > outfile
			print "" > outfile
		
			# print the target rule, according with the type of target
			print "$(TARGET): $(OBJS)" > outfile
			if (exeflag)
				print "\t$(LD) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)" > outfile
			if (dllflag)
				print "\t$(LD) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)" > outfile
			if (libflag)
				print "\t$(AR) $(ARFLAGS) $@ $(OBJS)" > outfile
			print "" > outfile
			
			continue
		}
		
		# gather groups of source files to put them in diferent variables in the Makefile
		if(/^# Begin Group/) {
			# get the group name
			groupname = gensub(/^# Begin Group "(.*)"$/, "\\1", "1")
			
			# take the variable name as the upper case of the group name and changing the spaces to underscores
			groupvarname = toupper(groupname)
			gsub(/[[:space:]]/, "_", groupvarname)
			
			# add this information to the groups array
			groups[ngroups] = groupvarname
			ngroups += 1
		
			# initialize some bookeeping variables
			ngsources = 0	# number of sources in this group
			
			# signal that we are inside a group
			groupflag = 1
			
			continue
		}
		if(/^# End Group$/) {
			# print the group source variable definition
			printf "%s=", groupvarname > outfile
			if (ngsources) {
				for (i = 0; i < ngsources; i++)
					printf " \\\n\t%s", gsources[i] > outfile
			}
			print "" > outfile
			print "" > outfile
			
			# signal that we are outside a group
			groupflag = 0
			
			continue
		}
		
		if (/^SOURCE=/) {
			# get the source file name
			source = fixpath(gensub(/^SOURCE=(.*)$/, "\\1", "1"))
			
			# add to the group sources or isolated sources according we are in a group or not
			if (groupflag)
			{
				gsources[ngsources] = source
				ngsources += 1
			}
			else
			{
				sources[nsources] = source
				nsources += 1
			}
			
			continue
		}
		
		# attempts to handle custom builds definition
		if(/^# Begin Custom Build/) {
			print infile ":" inline ": " source ": Custom Build" > "/dev/stderr"
			
			# signal we are inside a custom build definition
			customflag = 1
			ncustomvars = 0
			
			continue
		}
		if(/^# End Custom Build/) {
			# signal we are leaving a custom build definition
			customflag = 0
			
			continue
		}
		if(customflag) {
			if (debug)
				print infile ": " $0
				
			# MSDS handles customs builds defining a series of variables for the user convenience
			# handle their definition ...
			if($0 ~ /^IntDir=/) {
				gsub(/^IntDir=/, "", $0)
				Intdir = fixpath($0)
				continue
			}
			if($0 ~ /^IntPath=/) {
				gsub(/^IntPath=/, "", $0)
				IntPath = fixpath($0)
				continue
			}
			if($0 ~ /^OutDir=/) {
				gsub(/^OutDir=/, "", $0)
				OutDir_ = fixpath($0)
				OutDir = "."
				continue
			}
			if($0 ~ /^InputDir=/) {
				gsub(/^InputDir=/, "", $0)
				InputDir = fixpath($0)
				continue
			}
			if($0 ~ /^InputName=/) {
				gsub(/^InputName=/, "", $0)
				InputName = fixquotes($0)
				continue
			}
			if($0 ~ /^InputPath=/) {
				gsub(/^InputPath=/, "", $0)
				InputPath = fixpath($0)
				continue
			}
			if($0 ~ /^TargetDir=/) {
				gsub(/^TargetDir=/, "", $0)
				TargetDir_ = fixpath($0)
				TargetDir = "."
				continue
			}
			if($0 ~ /^TargetPath=/) {
				gsub(/^TargetPath=/, "", $0)
				gsub(TargetDir_, ".", $0)
				TargetPath = fixpath($0)
				continue
			}
			
			# ... and substitute them in the rules
			gsub(/\$\(IntDir\)/, IntDir)
			gsub(/\$\(IntPath\)/, IntPath)
			gsub(/\$\(OutDir\)/, OutDir)
			gsub(/\$\(InputDir\)/, InputDir)
			gsub(/\$\(InputName\)/, InputName)
			gsub(/\$\(InputPath\)/, InputPath)
			gsub(/\$\(TargetDir\)/, TargetDir)
			gsub(/\$\(TargetPath\)/, TargetPath)
			
			gsub(/\$\(SOURCE\)/, source)
			gsub(/"\$\(INTDIR\)"[[:space:]]*/, "")
			gsub(/"\$\(OUTDIR\)"[[:space:]]*/, "")
			
			# do a serie of generic actions to convert the rule
			gsub(/^   /, "\t")
			gsub(/\\/, "/")
			gsub(/\/$/, "\\")
			gsub(/\.obj/, ".o")
			
			print > outfile
			
			if (debug)
				print outfile ": " $0
		}
	}

	# print the 'clean' target rule
	print ".PHONY: clean" > outfile
	print "clean:" > outfile
	print "\t-" rm " $(OBJS) $(TARGET) " dependencies > outfile
	print "" > outfile

	# print the 'depends' target rule for automatic dependencies generation
	print ".PHONY: depends" > outfile
	print "depends:" > outfile
	print "\t-$(CXX) $(CXXFLAGS) $(CPPFLAGS) -MM $(filter %.c %.cc %.cpp %.cxx,$(SRCS)) > " dependencies> outfile
	print "" > outfile
	print "-include " dependencies > outfile
	print "" > outfile
	
	# close the files
	close(outfile)
	close(infile)
}

# parses a workpace file (.dsw) specified by 'infile' and generates a makefile to 'outfile'
function parse_dsw(infile, outfile, i)
{
	print infile
	
	# print the Makefile header
	print "# Makefile - " basefile(infile) > outfile
	print "" > outfile

	# initialize the number of projects counter
	nprojects = 0
	
	# main loop
	while((getline < infile) == 1) {
		# Strip DOS line-endings
		gsub(/\r$/, "")
	
		# catch a project definition
		if(/^Project:/) {
			# increment the project counter
			project = nprojects
			nprojects++

			# extract the project name and filename
			project_name[project] = fixpath(gensub(/^Project:[[:blank:]]+(.*)=(.*)[[:blank:]]+-[[:blank:]]+.*$/, "\\1", 1))
			project_file[project] = fixpath(gensub(/^Project:[[:blank:]]+(.*)=(.*)[[:blank:]]+-[[:blank:]]+.*$/, "\\2", 1))
			
			# check for a .dsp file extension
			if(project_file[project] ~ /\.[Dd][Ss][Pp]$/) {
				# create the output filename by renaming the file extension from .dsp to .mak
				project_makefile[project] = project_file[project]
				sub(/(\.[^.]*)?$/, ".mak", project_makefile[project])
			}
			else
				project_makefile[project] = ""
		
			# initialize the project dependencies
			project_dependencies[project] = ""

			continue
		}
		
		# catch a project dependency marker
		if(project && /^{{{$/) {
			# read dependencies until the end marker
			while((getline < infile) == 1 && !/^}}}$/)
				if(/^[[:blank:]]*Project_Dep_Name[[:blank:]]+/)
					project_dependencies[project] = project_dependencies[project] " " fixpath(gensub(/^[[:blank:]]*Project_Dep_Name[[:blank:]]+(.*)$/, "\\1", 1))
					
			continue
		}
		
		# catch other (perhaps important) section definitions and produce a warning
		if(/^[[:alpha:]]+:/)
		{
			project = 0
			print infile ": " gensub(/^([[:alpha:]]+):/, "\\1", 1) ": unknown section" > "/dev/stderr"
		}
	}

	# print the default target rule
	print ".PHONY: all" > outfile
	printf "all:" > outfile
	for(i = 0; i < nprojects; i++)
		printf " \\\n\t%s", project_name[i] > outfile
	print "" > outfile
	print "" > outfile		

	# print the rules for each project target
	for(i = 0; i < nprojects; i++) {
		print ".PHONY: " project_name[i] > outfile
		print project_name[i] ":" project_dependencies[i] > outfile
		if(project_makefile[i] != "") {
			if(basedir(project_makefile[i]) == "")
				print "\t$(MAKE) -f " project_makefile[i] > outfile
			else
				print "\t$(MAKE) -C " basedir(project_makefile[i]) " -f " basefile(project_makefile[i]) > outfile
		}
		print "" > outfile
	}

	# print the 'clean' target rule
	print ".PHONY: clean" > outfile
	print "clean:" > outfile
	for(i = 0; i < nprojects; i++)
		if(project_makefile[i] != "") {
			if(basedir(project_makefile[i]) == "")
				print "\t$(MAKE) -f " project_makefile[i] " clean" > outfile
			else
				print "\t$(MAKE) -C " basedir(project_makefile[i]) " -f " basefile(project_makefile[i]) " clean" > outfile
		}
	print "" > outfile

	# print the 'depends' target rule for automatic dependencies generation
	print ".PHONY: depends" > outfile
	print "depends:" > outfile
	for(i = 0; i < nprojects; i++)
		if(project_makefile[i] != "") {
			if(basedir(project_makefile[i]) == "")
				print "\t$(MAKE) -f " project_makefile[i] " depends" > outfile
			else
				print "\t$(MAKE) -C " basedir(project_makefile[i]) " -f " basefile(project_makefile[i]) " depends" > outfile
		}
	print "" > outfile

	close(outfile)
	close(infile)

	# parse every project file
	for(i = 0; i < nprojects; i++)
		if(project_makefile[i] != "") {
			if(basedir(infile) == "")
				parse_dsp(project_file[i], project_makefile[i])
			else
				parse_dsp(basedir(infile) "\\" project_file[i], basedir(infile) "\\" project_makefile[i])
		}
}

# main program
BEGIN {
	print "dsw2mak.awk   Generates a Makefile from a .DSW/.DSP file   Jose Fonseca"
	print ""
	
	# for each argument ...
	for (i = 1; i < ARGC; i++) {
		infile = ARGV[i]
		
		# determine whether is a workspace or a project file and parse it
		if(infile ~ /\.[Dd][Ss][Ww]$/) {
			# create the output filename by renaming the filename to Makefile
			outfile = infile
			sub(/[^\/\\:]+$/, "Makefile", outfile)
			
			parse_dsw(infile, outfile)
		} else if(infile ~ /\.[Dd][Ss][Pp]$/) {
			# create the output filename by renaming the file extension from .dsp to .mak
			outfile = infile
			sub(/(\.[^.]*)?$/, ".mak", outfile)
			
			parse_dsp(infile, outfile)
		} else {
			print infile ": unknown file format" > "/dev/stderr"
		}
	}
}
