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[]
}
data ConInt {
int val
}
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(TargetOption target)
{
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(" -os Specify the target operating system for generated output code")
out.println(" -chip Specify the target chipset for generated output code")
out.println(" -n Only compile source files with a different modified date/time")
out.println(" -v Produce verbose output during compilation")
out.println("")
out.println("Default output target is $(target.os) $(target.chip)")
}
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 getDefaultTarget(char danaHome[])
{
char os[] = getDefaulTargetOS()
char chip[] = getDefaulTargetChip()
if (fileSystem.exists("util/compiler/hwi/Code_$(os)_$(chip).o"))
{
return new TargetOption(os, chip)
}
else if (fileSystem.exists("$(danaHome)/util/compiler/hwi/Code_$(os)_$(chip).o"))
{
return new TargetOption(os, chip)
}
else
{
//check for UBC
ConInt ci = new ConInt()
int aw_bits = dana.serial(ci).arrayLength * 8
chip = aw_bits.makeString()
os = "ubc"
if (fileSystem.exists("util/compiler/hwi/Code_$(os)_$(chip).o"))
{
return new TargetOption(os, chip)
}
else if (fileSystem.exists("$(danaHome)/util/compiler/hwi/Code_$(os)_$(chip).o"))
{
return new TargetOption(os, chip)
}
}
return null
}
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)
}
}
byte getOSTarget(char targetOS[])
{
if (targetOS == "ubc")
{
return EnvelopeWriter.OS_UBC
}
else if (targetOS == "unix")
{
return EnvelopeWriter.OS_LINUX
}
else if (targetOS == "windows")
{
return EnvelopeWriter.OS_WINDOWS
}
return EnvelopeWriter.OS_LINUX
}
IDC loadCodeGen(char os[], char chip[], char danaHome[])
{
IDC codeIDC = null
if (fileSystem.exists("util/compiler/hwi/Code_$(os)_$(chip).o"))
{
codeIDC = rloader.load("util/compiler/hwi/Code_$(os)_$(chip).o").mainComponent
}
else if (fileSystem.exists("$(danaHome)/util/compiler/hwi/Code_$(os)_$(chip).o"))
{
codeIDC = rloader.load("$(danaHome)/util/compiler/hwi/Code_$(os)_$(chip).o").mainComponent
}
return codeIDC
}
int App:main(AppParam params[])
{
byte osTarget = EnvelopeWriter.OS_LINUX
int addressWidth = ADDRESS_WIDTH
ParamParser pparser = new ParamParser(params, new String[](new String("-v"), new String("-gni"), new String("-nuc")))
char danaHome[] = pparser.getValue("-dh")
if (danaHome == null) danaHome = new char[](systemInfo.getVariable("DANA_HOME"), "/components")
if (danaHome == null)
{
out.println("error: Dana home location not set; either use the environment variable DANA_HOME, or pass value in via the -dh parameter")
return 1
}
TargetOption target = getDefaultTarget(danaHome)
if (target == null)
{
out.println("error: no default code generator found; check your installation")
return 1
}
if (params.arrayLength == 0 || pparser.getFreeValues().arrayLength == 0)
{
printHelp(target)
return 0
}
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)
{
target.os = pparser.getValue("-os")
}
if (pparser.getValue("-chip") != null)
{
target.os = pparser.getValue("-chip")
}
IDC codeIDC = loadCodeGen(target.os, target.chip, danaHome)
if (codeIDC == null)
{
out.println("unknown OS/chip combination for code generation $(target.os)/$(target.chip)")
out.println("available options are:")
printTargetOptions(danaHome)
return 1
}
Code codeHW = new Code() from codeIDC
osTarget = getOSTarget(target.os)
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()
}
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
}
}