HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Component app.dnc by barry
expand copy to clipboardexpand
data DNILParam {
	char type[]
}

data DNILFunction {
	char name[]
	char returnType[]
	DNILParam parameters[]
}

data DNILProvides {
	char package[]
	char semantic[]
	char alias[]
	DNILFunction functions[]
}

data DNILRequires {
	char package[]
	char semantic[]
	char alias[]
	bool isNative
	DNILFunction functions[]
}

data DNILRecord {
	DNILProvides providedInterfaces[]
	DNILRequires requiredInterfaces[]
}

data UpdateStatus {
	char lastCheck[]
	char lastReminder[]
	bool disableReminders
	}

data TargetOption {
	char os[]
	char chip[]
}

const bool DEBUG_BASIC = false

const bool UPDATE_CHECK_REMINDER_DAYS = 30

const int LANG_VERSION = 272

component provides App requires io.Output out, data.IntUtil iu, util.compiler.SyntaxParser, util.compiler.OpParser, util.compiler.TypeShaper, util.compiler.EnvelopeWriter, util.compiler.CodeGen, util.compiler.LiteralsTable, util.compiler.TypeTable, util.compiler.GNIGen gniGen, util.compiler.DNCUtil dncUtil, io.File, io.FileSystem fileSystem, os.Run run, composition.RecursiveLoader rloader, data.query.Search search,  data.StringBuilder, data.StringUtil stringUtil, data.adt.List, data.json.JSONEncoder jcoder, util.ParamParser, os.SystemInfo systemInfo, time.DateUtil dateUtil, time.Calendar cal, System sys {

	int ADDRESS_WIDTH = 8

	bool evenAddressLock = true
	
	char[] makeDNILContent(OpParseResult opResult)
		{
		DNILRecord result = new DNILRecord()

		for (int i = 0; i < opResult.providedObjects.arrayLength; i++)
			{
			DNILProvides newProvides = new DNILProvides()
			newProvides.package = opResult.providedObjects[i].mainInterface
			newProvides.semantic = opResult.providedObjects[i].semantic
			newProvides.alias = opResult.providedObjects[i].mainInterface
			if (newProvides.semantic.arrayLength != 0) newProvides.alias = "$(newProvides.alias):$(newProvides.semantic)"
			//newProvides.functions = TODO

			result.providedInterfaces = new DNILProvides[](result.providedInterfaces, newProvides)
			}
		
		for (int i = 0; i < opResult.requiredInterfaces.arrayLength; i++)
			{
			DNILRequires newRequires = new DNILRequires()
			newRequires.package = opResult.requiredInterfaces[i].name
			newRequires.semantic = opResult.requiredInterfaces[i].semantic
			newRequires.alias = opResult.requiredInterfaces[i].name
			if (newRequires.semantic.arrayLength != 0) newRequires.alias = "$(newRequires.alias):$(newRequires.semantic)"
			newRequires.isNative = opResult.requiredInterfaces[i].isNative
			//newRequires.functions = TODO
			
			result.requiredInterfaces = new DNILRequires[](result.requiredInterfaces, newRequires)
			}
		
		return jcoder.jsonFromData(result, new Map("native", "isNative"))
		}
	
	int getCallAddress(ProvidedObject po, ImplementedObject io, char impl[], char name[])
		{
		int res = INT_MAX

		if (impl == null)
			{
			res = dncUtil.findFunctionIndex(po.localFunctions, name)
			if (res != INT_MAX) return io.localFunctions[res].offset
			
			for (int j = 0; j < po.interfaces.arrayLength; j++)
				{
				res = dncUtil.findFunctionIndex(po.interfaces[j].functions, name)
				if (res != INT_MAX) return io.interfaces[j].functions[res].offset
				}
			}
		
		for (int j = 0; j < po.interfaces.arrayLength; j++)
			{
			if (impl == po.interfaces[j].type)
				{
				res = dncUtil.findFunctionIndex(po.interfaces[j].functions, name)
				if (res != INT_MAX) return io.interfaces[j].functions[res].offset
				}
			}
		
		throw new Exception("unknown call offset for $impl:$name()")
		}
	
	char[] deriveOutputPath(char path[])
		{
		char result[] = new char[](path.rsplit(".")[0].string, ".o")

		return result
		}
	
	void printTypes(DanaType types[])
		{
		for (int i = 0; i < types.arrayLength; i ++)
			{
			out.println("type: $(types[i].name) of class $(types[i].class)")
			}
		}
	
	ImplementedObject[] cloneIO(ImplementedObject array[])
		{
		ImplementedObject result[] = clone array
		for (int i = 0; i < array.arrayLength; i++)
			{
			result[i] = clone result[i]
			result[i].interfaces = clone result[i].interfaces

			for (int j = 0; j < result[i].interfaces.arrayLength; j++)
				{
				result[i].interfaces[j] = clone result[i].interfaces[j]
				result[i].interfaces[j].functions = clone result[i].interfaces[j].functions

				for (int k = 0; k < result[i].interfaces[j].functions.arrayLength; k ++)
					{
					result[i].interfaces[j].functions[k] = clone result[i].interfaces[j].functions[k]
					}
				}
			
			result[i].localFunctions = clone result[i].localFunctions

			for (int k = 0; k < result[i].localFunctions.arrayLength; k ++)
				{
				result[i].localFunctions[k] = clone result[i].localFunctions[k]
				}

			result[i].initFunction = clone result[i].initFunction
			}
		
		return result
		}
	
	bool compileFile(char inputFile[], char outputFile[], String searchPaths[], Code codeHW, byte targetPlatform, int targetAddressWidth, int hwidSet[])
		{
		//parse the given file
		SyntaxParser parser = new SyntaxParser(searchPaths, targetAddressWidth)

		File ifd = new File(inputFile, File.READ)
		char sourceCode[] = ifd.read(ifd.getSize())
		ParseResult result = parser.parse(sourceCode, inputFile)
		DanaToken tree = result.tree
		DanaType types[] = parser.getTypes()
		//printTypes(types)

		for (int i = 0; i < result.errors.arrayLength; i++)
			{
			out.println("error in $(result.errors[i].file), line $(result.errors[i].line): $(result.errors[i].message)")
			}
		
		if (result.errors.arrayLength == 0)
			{
			OpParser opp = new OpParser(targetAddressWidth)
			TypeShaper tshape = new TypeShaper()
			DanaType flatTypes[] = tshape.shapeTypes(types, tree)
			//out.println("--flat--")
			//printTypes(flatTypes)
			OpParseResult opResult = opp.parse(inputFile, tree, flatTypes)

			for (int i = 0; i < opResult.errors.arrayLength; i++)
				{
				out.println("error in $(opResult.errors[i].file), line $(opResult.errors[i].line): $(opResult.errors[i].message)")
				}
			
			for (int i = 0; i < opResult.warnings.arrayLength; i++)
				{
				out.println("warning in $(opResult.warnings[i].file), line $(opResult.warnings[i].line): $(opResult.warnings[i].message)")
				}

			if (opResult.errors.arrayLength == 0)
				{
				EnvelopeWriter env = new EnvelopeWriter(outputFile, targetAddressWidth, EnvelopeWriter.EM_LITTLE, targetPlatform)
				LiteralsTable literals = new LiteralsTable()
				TypeTable typeTable = new TypeTable()
				CodeGen codeGen = new CodeGen()
				CGResult cgr = null

				File ofd = new File(outputFile, File.CREATE)

				//generate object file header information, type table, and literals table
				if (DEBUG_BASIC) out.println("preparing envelope")
				ImplementedObject implObjects[] = cloneIO(env.prepareEnvelope(opResult, flatTypes, literals))
				TypeMapping typeMap[] = env.getTypeMappings()
				if (DEBUG_BASIC) out.println("writing envelope")
				env.writeEnvelope(ofd, opResult, implObjects, flatTypes, literals, typeTable, 0, true)

				// now generate the code for each function, and link offsets to implInterfaces
				if (DEBUG_BASIC) out.println("generating code")

				if (DEBUG_BASIC) out.println(" :: code for class-init")
				FunctionInfo initInfo = env.writeFrameHeader(ofd, new ImplementedFunction(opResult.classInitFunction.name, opResult.classInitFunction), types, literals, env.getTextOffset())
				cgr = codeGen.generate(ofd, codeHW, opResult.classInitFunction.instructions, flatTypes, literals, typeTable, typeMap, initInfo, env.getTextOffset(), env.getNNITableOffset(), hwidSet)
				env.addRelocations(cgr.relocations)

				for (int i = 0; i < opResult.providedObjects.arrayLength; i++)
					{
					CallRefAddress callRefs[] = null

					for (int j = 0; j < opResult.providedObjects[i].interfaces.arrayLength; j++)
						{
						for (int k = 0; k < opResult.providedObjects[i].interfaces[j].functions.arrayLength; k++)
							{
							if (DEBUG_BASIC) out.println(" :: code for $(opResult.providedObjects[i].interfaces[j].functions[k].name)")
							FunctionInfo info = env.writeFrameHeader(ofd, implObjects[i].interfaces[j].functions[k], types, literals, env.getTextOffset())
							cgr = codeGen.generate(ofd, codeHW, opResult.providedObjects[i].interfaces[j].functions[k].instructions, flatTypes, literals, typeTable, typeMap, info, env.getTextOffset(), env.getNNITableOffset(), hwidSet)
							implObjects[i].interfaces[j].functions[k].offset = info.textOffset
							env.addRelocations(cgr.relocations)
							callRefs = new CallRefAddress[](callRefs, cgr.callRefs)
							}
						}

					for (int j = 0; j < opResult.providedObjects[i].localFunctions.arrayLength; j++)
						{
						if (DEBUG_BASIC) out.println(" :: code for $(opResult.providedObjects[i].localFunctions[j].name)")
						FunctionInfo info = env.writeFrameHeader(ofd, implObjects[i].localFunctions[j], types, literals, env.getTextOffset())
						cgr = codeGen.generate(ofd, codeHW, opResult.providedObjects[i].localFunctions[j].instructions, flatTypes, literals, typeTable, typeMap, info, env.getTextOffset(), env.getNNITableOffset(), hwidSet)
						implObjects[i].localFunctions[j].offset = info.textOffset
						env.addRelocations(cgr.relocations)
						callRefs = new CallRefAddress[](callRefs, cgr.callRefs)
						}
					
					if (DEBUG_BASIC) out.println(" :: code for object-init")
					FunctionInfo info = env.writeFrameHeader(ofd, implObjects[i].initFunction, types, literals, env.getTextOffset())
					cgr = codeGen.generate(ofd, codeHW, opResult.providedObjects[i].initFunction.instructions, flatTypes, literals, typeTable, typeMap, info, env.getTextOffset(), env.getNNITableOffset(), hwidSet)
					implObjects[i].initFunction.offset = info.textOffset
					env.addRelocations(cgr.relocations)

					//iterate through local-call references, patching their offset addresses now that we know
					for (int j = 0; j < callRefs.arrayLength; j++)
						{
						int addr = getCallAddress(opResult.providedObjects[i], implObjects[i], callRefs[j].intf, callRefs[j].name)
						codeGen.patchCallAddress(ofd, codeHW, callRefs[j].refAddress, addr)
						}
					}
				
				//note text-end, and write relocations
				env.writeTextEnd(ofd)

				env.writeRelocations(ofd)

				//DNIL section
				char json[] = makeDNILContent(opResult)
				env.writeInfoSection(ofd, "DNIL", "json", json)
				if (DEBUG_BASIC) out.println(json)

				//now re-write the envelope, with function implementation offsets known
				ofd.setPos(0)

				env.writeEnvelope(ofd, opResult, implObjects, flatTypes, literals, typeTable, initInfo.textOffset, false)

				if (DEBUG_BASIC) out.println("fd pos finished at $(ofd.getPos()), textOffset is $(env.getTextOffset())")

				ofd.close()
				}
			}
		
		return true
		}
	
	bool compileDirectory(char inputFile[], String searchPaths[], Code codeHW, byte targetPlatform, int targetAddressWidth, bool verbose, bool newOnly, int hwidSet[])
		{
		FileEntry files[] = fileSystem.getDirectoryContents(inputFile)
		
		for (int i = 0; i < files.arrayLength; i++)
			{
			char subPath[] = "$(inputFile)/$(files[i].name)"
			FileInfo info = fileSystem.getInfo(subPath)

			if (info.type == FileInfo.TYPE_DIR && files[i].name != "resources")
				{
				compileDirectory(subPath, searchPaths, codeHW, targetPlatform, targetAddressWidth, verbose, newOnly, hwidSet)
				}
				else if (info.type == FileInfo.TYPE_FILE && subPath.endsWith(".dn"))
				{
				bool doCompile = true
				char output[] = deriveOutputPath(subPath)
				if (newOnly && fileSystem.exists(output) && !dateUtil.after(fileSystem.getInfo(subPath).modified, fileSystem.getInfo(output).modified)) doCompile = false

				if (doCompile)
					{
					if (verbose) out.println(subPath)
					compileFile(subPath, output, searchPaths, codeHW, targetPlatform, targetAddressWidth, hwidSet)
					}
				}
			}
		
		return true
		}
	
	void compileFileGNI(char input[], char typeName[], String searchPaths[], int addressWidth)
		{
		SyntaxParser parser = new SyntaxParser(searchPaths, addressWidth)

		File ifd = new File(input, File.READ)
		char sourceCode[] = ifd.read(ifd.getSize())
		ParseResult result = parser.parse(sourceCode, input)
		DanaToken tree = result.tree
		DanaType types[] = parser.getTypes()
		//printTypes(types)

		for (int i = 0; i < result.errors.arrayLength; i++)
			{
			out.println("error in $(result.errors[i].file), line $(result.errors[i].line): $(result.errors[i].message)")
			}
		
		if (typeName == null)
			{
			int options = 0
			for (int i = 0; i < types.arrayLength; i++)
				{
				if (types[i].fromFile.arrayLength != 0 && fileSystem.getFullPath(types[i].fromFile) == fileSystem.getFullPath(input) && types[i].class == DanaType.INTERFACE)
					{
					typeName = types[i].simpleName
					options ++
					}
				}
			
			if (options != 1)
				{
				if (typeName == null)
					{
					out.println("error: you must supply the interface type name, using -gnType, for which you wish to generate a native interface binding")
					return
					}
				}
			
			out.println(" >> assuming native interface type is $(typeName); use -gnType to change this")
			}
			else
			{
			bool found = false
			for (int i = 0; i < types.arrayLength; i++)
				{
				if (types[i].simpleName == typeName)
					{
					found = true
					}
				}
			
			if (!found)
				{
				out.println("error: no type found by the name '$(typeName)' given via -gnType")
				return
				}
			}
		
		if (result.errors.arrayLength == 0)
			{
			char output[] = "$(typeName)_dni.c"

			TypeShaper tshape = new TypeShaper()
			DanaType flatTypes[] = tshape.shapeTypes(types, tree)

			File fd = new File(output, File.CREATE)
			gniGen.generate(fd, flatTypes.findFirst(DanaType.[name], new DanaType(name = typeName)), flatTypes, addressWidth)
			fd.close()

			out.println(" >> generating C file for interface '$typeName' in '$output'")
			out.println("")
			out.println(" setup helper functions are known as:")
			out.println("  void $(typeName)_setInterfaceFunction(char* name, void* ptr);")
			out.println("  Interface* $(typeName)_getPublicInterface();")
			out.println("  const DanaType* $(typeName)_getTypeDefinition(char* name);")
			}
		}
	
	void printHelp()
		{
		out.println("")
		out.println("Dana compiler [version $LANG_VERSION]")
		out.println("Copyright & intellectual property of Francis Research Ltd.")
		out.println("")
		out.println("Usage: dnc sourcefile")
		out.println("")
		out.println("Options:")
		out.println("  -o      Compile to specified object file")
		out.println("  -sp     Specify additional search paths for resources (semicolon-separated)")
		out.println("  -spo    Set additional search paths (semicolon-separated) and ignore DANA_SP")
		out.println("  -dh     Set (or override) the 'dana home' directory")
		out.println("  -wd     Set the working directory of the compiler")
		out.println("  -gni    Generate a native interface from sourcefile, for a native library")
		out.println("  -gnType Specify the interface type for which to generate a native interface")
		out.println("  -n      Only compile source files with a different modified date/time")
		out.println("  -v      Produce verbose output during compilation")
		}
	
	int getDayInterval(DateTime dateFrom, DateTime dateTo)
		{
		int result = 0

		if (dateFrom.year < dateTo.year)
			{
			result += ((dateTo.year - dateFrom.year) * 365)
			result -= (dateFrom.month * 30)
			result += (dateTo.month * 30)
			result -= dateFrom.day
			result += dateTo.day
			}
			else if (dateFrom.month < dateTo.month)
			{
			result += ((dateTo.month - dateFrom.month) * 30)
			result -= dateFrom.day
			result += dateTo.day
			}
			else if (dateFrom.day < dateTo.day)
			{
			result += (dateTo.day - dateFrom.day)
			}

		return result
		}
	
	void updateCheck()
		{
		char home[] = sys.getDanaHome()

		if (home != null)
			{
			DateTime today = cal.getTime()

			UpdateStatus ustatus = new UpdateStatus()

			if (fileSystem.exists("$home/components/resources-ext/update/status.json"))
				{
				File fd = new File("$home/components/resources-ext/update/status.json", File.READ)
				ustatus = clone jcoder.jsonToData(fd.read(fd.getSize()), typeof(UpdateStatus))
				fd.close()

				if (!ustatus.disableReminders)
					{
					String parts[] = ustatus.lastCheck.explode("/")
					int daysCheck = getDayInterval(new DateTime(parts[0].string.intFromString(), parts[1].string.intFromString(), parts[2].string.intFromString()), today)
					parts = ustatus.lastReminder.explode("/")
					int daysReminder = getDayInterval(new DateTime(parts[0].string.intFromString(), parts[1].string.intFromString(), parts[2].string.intFromString()), today)

					if (daysCheck > UPDATE_CHECK_REMINDER_DAYS && daysReminder > UPDATE_CHECK_REMINDER_DAYS)
						{
						out.println("[note: it's $(daysCheck) days since you last checked for an update]")
						out.println("[to check for and install updates, use 'dana source update']")
						out.println("[to check for newer distributions, use 'dana update']")

						ustatus.lastReminder = "$(today.year)/$(today.month)/$(today.day)"
						}
					}
				}
				else
				{
				ustatus.lastCheck = "$(today.year)/$(today.month)/$(today.day)"
				ustatus.lastReminder = "$(today.year)/$(today.month)/$(today.day)"
				}
			
			if (fileSystem.exists("$home/components/resources-ext/update/"))
				{
				File fd = new File("$home/components/resources-ext/update/status.json", File.CREATE)
				fd.write(jcoder.jsonFromData(ustatus))
				fd.close()
				}
			}
		}
	
	char[] getDefaulTargetOS()
		{
		if (sys.getPlatform().osCode == "deb")
			return "unix"
			else if (sys.getPlatform().osCode == "win")
			return "windows"
			else if (sys.getPlatform().osCode == "osx")
			return "unix"
		
		return "unix"
		}
	
	char[] getDefaulTargetChip()
		{
		if (sys.getPlatform().chipCode == "x64")
			return "x86-64"
			else if (sys.getPlatform().chipCode == "x86")
			return "x86-32"
		
		return "x86-64"
		}
	
	TargetOption[] getTargetOptions(char dir[])
		{
		TargetOption options[]

		FileEntry files[] = fileSystem.getDirectoryContents(dir)

		for (int i = 0; i < files.arrayLength; i++)
			{
			if (files[i].name.startsWith("Code_") && !files[i].name.endsWith(".dn"))
				{
				String parts[] = files[i].name.explode("_.")
				options = new TargetOption[](options, new TargetOption(parts[1].string, parts[2].string))
				}
			}

		return options		
		}
	
	void printSpaces(int n)
		{
		while (n != 0)
			{
			out.print(" ")
			n --
			}
		}
	
	void printTargetOptions(char home[])
		{
		TargetOption options[]

		if (fileSystem.exists("util/compiler/hwi/"))
			{
			options = getTargetOptions("util/compiler/hwi/")
			}
		
		if (fileSystem.exists("$(home)/util/compiler/hwi/"))
			{
			options = new TargetOption[](options, getTargetOptions("$(home)/util/compiler/hwi/"))
			}
		
		int longestOS = 0
		for (int i = 0; i < options.arrayLength; i++)
			{
			if (options[i].os.arrayLength > longestOS)
				longestOS = options[i].os.arrayLength
			}
		
		int padding = 4
		
		out.print(" os") printSpaces(longestOS - 2 + padding) out.println("chip")

		for (int i = 0; i < options.arrayLength; i++)
			{
			out.print(" ") out.print(options[i].os) printSpaces(longestOS - options[i].os.arrayLength + padding) out.println(options[i].chip)
			}
		}

	int App:main(AppParam params[])
		{
		byte osTarget = EnvelopeWriter.OS_LINUX
		int addressWidth = ADDRESS_WIDTH
		char targetOS[] = getDefaulTargetOS()
		char targetChip[] = getDefaulTargetChip()

		if (params.arrayLength == 0)
			{
			printHelp()
			return 0
			}

		ParamParser pparser = new ParamParser(params, new String[](new String("-v"), new String("-gni"), new String("-nuc")))

		if (pparser.getFreeValues().arrayLength == 0)
			{
			printHelp()
			return 0
			}

		char danaHome[] = pparser.getValue("-dh")
		if (danaHome == null) danaHome = new char[](systemInfo.getVariable("DANA_HOME"), "/components")
		char xSearchPaths[] = pparser.getValue("-spo")

		if (xSearchPaths == null)
			{
			xSearchPaths = systemInfo.getVariable("DANA_HOME")

			if (pparser.getValue("-sp") != null)
				{
				if (xSearchPaths == null)
					{
					xSearchPaths = new char[](xSearchPaths, ";", pparser.getValue("-sp"))
					}
					else
					{
					xSearchPaths = pparser.getValue("-sp")
					}
				}
			}
		
		char input[] = pparser.getFreeValues()[0].string

		if (pparser.getValue("-os") != null)
			{
			targetOS = pparser.getValue("-os")
			}
		
		if (pparser.getValue("-chip") != null)
			{
			targetChip = pparser.getValue("-chip")
			}
		
		if (targetOS == "ubc")
			{
			osTarget = EnvelopeWriter.OS_UBC
			}
			else if (targetOS == "unix")
			{
			osTarget = EnvelopeWriter.OS_LINUX
			}
			else if (targetOS == "windows")
			{
			osTarget = EnvelopeWriter.OS_WINDOWS
			}
		
		if (!fileSystem.exists(input))
			{
			out.println("error: no file found at $input")
			return 1
			}
		
		//when did we last check for an update?
		if (!pparser.hasSwitch("-nuc"))
			updateCheck()
		
		IDC codeIDC = null
		Code codeHW = null
		
		if (fileSystem.exists("util/compiler/hwi/Code_$(targetOS)_$(targetChip).o"))
			{
			codeIDC = rloader.load("util/compiler/hwi/Code_$(targetOS)_$(targetChip).o").mainComponent
			codeHW = new Code() from codeIDC
			}
			else if (fileSystem.exists("$(danaHome)/util/compiler/hwi/Code_$(targetOS)_$(targetChip).o"))
			{
			codeIDC = rloader.load("$(danaHome)/util/compiler/hwi/Code_$(targetOS)_$(targetChip).o").mainComponent
			codeHW = new Code() from codeIDC
			}
			else
			{
			out.println("unknown OS/chip combination for code generation $(targetOS)/$(targetChip)")
			out.println("available options are:")
			printTargetOptions(danaHome)

			return 1
			}
		
		addressWidth = codeHW.getAddressWidth()

		int hwidSet[] = codeHW.getHWIDSupport()
		
		String searchPaths[] = new String[](new String(danaHome), xSearchPaths.explode(";"))
		
		if (fileSystem.getInfo(input).type == FileInfo.TYPE_FILE)
			{
			if (pparser.hasSwitch("-gni"))
				{
				char typeName[] = pparser.getValue("-gnType")
				
				compileFileGNI(input, typeName, searchPaths, addressWidth)
				}
				else
				{
				char output[] = pparser.getValue("-o")
				if (output == null) output = deriveOutputPath(input)
				compileFile(input, output, searchPaths, codeHW, osTarget, addressWidth, hwidSet)
				}
			}
			else if (fileSystem.getInfo(input).type == FileInfo.TYPE_DIR)
			{
			if (pparser.hasSwitch("-gni"))
				{
				out.println("error: gni mode is not valid for directory-oriented compile commands")
				return 1
				}
			
			if (input[input.arrayLength-1] == "/")
				input = dana.sub(input, 0, input.arrayLength-2)
			
			compileDirectory(input, searchPaths, codeHW, osTarget, addressWidth, pparser.hasSwitch("-v"), pparser.hasSwitch("-n"), hwidSet)
			}
		
		return 0
		}
	
	}
Revision history
To propose a new revision to this entity, use dana source put -uc your/new/version.dn -n app.dnc -m "reason for update" -u yourUsername
Version 9 by barry
Version 8 by barry
Version 7 by barry
Version 6 by barry
Version 5 by barry
Version 4 (this version) by barry
Notes for this version: Distribution version number bump; adds automated inference of -gnType where possible; adds output of native library helper functions for forward declaration
Version 3 by barry
Version 2 by barry
Version 1 by barry