Kotlin: Generate a module from the dbscheme

This commit is contained in:
Ian Lynagh
2021-07-28 11:55:50 +01:00
parent 4721ccd965
commit d28059a1c0
2 changed files with 124 additions and 30 deletions

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python3
import re
import sys
def upperFirst(string):
return string[0].upper() + string[1:]
with open('../ql/src/config/semmlecode.dbscheme', 'r') as f:
dbscheme = f.read()
# Remove comments
dbscheme = re.sub(r'/\*.*?\*/', '', dbscheme, flags=re.DOTALL)
dbscheme = re.sub(r'//[^\r\n]*/', '', dbscheme)
type_hierarchy = {}
with open('src/main/kotlin/KotlinExtractorDbScheme.kt', 'w') as kt:
kt.write('/* Generated by ' + sys.argv[0] + ': Do not edit manually. */\n')
kt.write('package com.github.codeql\n')
kt.write('class Label<T>(val name: Int) {\n')
kt.write(' override fun toString(): String = "#$name"\n')
kt.write('}\n')
# kind enums
for name, kind, body in re.findall(r'case\s+@([^.\s]*)\.([^.\s]*)\s+of\b(.*?);',
dbscheme,
flags=re.DOTALL):
for num, typ in re.findall(r'(\d+)\s*=\s*@(\S+)', body):
s = type_hierarchy.get(typ, set())
s.add(name)
type_hierarchy[typ] = s
# unions
for name, unions in re.findall(r'@(\w+)\s*=\s*(@\w+(?:\s*\|\s*@\w+)*)',
dbscheme,
flags=re.DOTALL):
type_hierarchy[name] = type_hierarchy.get(name, set())
for typ in re.findall(r'@(\w+)', unions):
s = type_hierarchy.get(typ, set())
s.add(name)
type_hierarchy[typ] = s
kt.write('\n')
# tables
for relname, body in re.findall('\n([\w_]+)(\([^)]*\))',
dbscheme,
flags=re.DOTALL):
for db_type in re.findall(':\s*@([^\s,]+)\s*(?:,|$)', body):
type_hierarchy[db_type] = type_hierarchy.get(db_type, set())
kt.write('fun write' + upperFirst(relname) + '(data: TrapWriter, ')
for colname, db_type in re.findall('(\S+)\s*:\s*([^\s,]+)', body):
kt.write(colname + ': ')
if db_type == 'int':
# TODO: Do something better if the column is a 'case'
kt.write('Int')
elif db_type == 'float':
kt.write('Double')
elif db_type == 'string':
kt.write('String')
elif db_type == 'date':
kt.write('String')
elif db_type == 'boolean':
kt.write('Boolean')
elif db_type[0] == '@':
kt.write('Label<out Db' + upperFirst(db_type[1:]) + '>')
else:
raise Exception('Bad db_type: ' + db_type)
kt.write(', ')
kt.write(') {\n')
kt.write(' data.writeTrap("' + relname + '(')
comma = ''
for colname, db_type in re.findall('(\S+)\s*:\s*([^\s,]+)', body):
kt.write(comma)
if db_type == 'string' or db_type == 'date':
kt.write('\\"$' + colname + '\\"') # TODO: Escaping
else:
# TODO: Any reformatting or escaping necessary?
# e.g. float formats?
kt.write('$' + colname)
comma = ', '
kt.write(')\\n")\n')
kt.write('}\n')
for typ in type_hierarchy:
kt.write('sealed interface Db' + upperFirst(typ))
names = type_hierarchy[typ]
if names:
kt.write(': ')
kt.write(', '.join(map(lambda name: 'Db' + upperFirst(name), type_hierarchy[typ])))
kt.write(' {}\n')

View File

@@ -36,26 +36,26 @@ fun extractorBug(msg: String) {
interface TrapWriter {
fun writeTrap(trap: String)
fun getLocation(startOffset: Int, endOffset: Int): Int
fun getIdFor(label: String): Int
fun getFreshId(): Int
fun getLocation(startOffset: Int, endOffset: Int): Label<DbLocation_default>
fun <T> getIdFor(label: String): Label<T>
fun <T> getFreshId(): Label<T>
}
class RootTrapWriter: TrapWriter {
override fun writeTrap(trap: String) {
extractorBug("Tried to write TRAP outside a file: $trap")
}
override fun getLocation(startOffset: Int, endOffset: Int): Int {
override fun getLocation(startOffset: Int, endOffset: Int): Label<DbLocation_default> {
extractorBug("Asked for location, but not in a file")
return 0
return Label(0)
}
override fun getIdFor(label: String): Int {
override fun <T> getIdFor(label: String): Label<T> {
extractorBug("Asked for ID for '$label' outside a file")
return 0
return Label(0)
}
override fun getFreshId(): Int {
override fun <T> getFreshId(): Label<T> {
extractorBug("Asked for fresh ID outside a file")
return 0
return Label(0)
}
}
@@ -68,30 +68,30 @@ class FileTrapWriter(
override fun writeTrap(trap: String) {
file.write(trap)
}
override fun getLocation(startOffset: Int, endOffset: Int): Int {
override fun getLocation(startOffset: Int, endOffset: Int): Label<DbLocation_default> {
val startLine = fileEntry.getLineNumber(startOffset) + 1
val startColumn = fileEntry.getColumnNumber(startOffset) + 1
val endLine = fileEntry.getLineNumber(endOffset) + 1
val endColumn = fileEntry.getColumnNumber(endOffset)
val id = getFreshId()
val fileId = getIdFor(fileLabel)
writeTrap("#$id = @\"loc,{#$fileId},$startLine,$startColumn,$endLine,$endColumn\"\n")
writeTrap("locations_default(#$id, #$fileId, $startLine, $startColumn, $endLine, $endColumn)\n")
val id: Label<DbLocation_default> = getFreshId()
val fileId: Label<DbFile> = getIdFor(fileLabel)
writeTrap("$id = @\"loc,{$fileId},$startLine,$startColumn,$endLine,$endColumn\"\n")
writeLocations_default(this, id, fileId, startLine, startColumn, endLine, endColumn)
return id
}
val labelMapping: MutableMap<String, Int> = mutableMapOf<String, Int>()
override fun getIdFor(label: String): Int {
val labelMapping: MutableMap<String, Label<*>> = mutableMapOf<String, Label<*>>()
override fun <T> getIdFor(label: String): Label<T> {
val maybeId = labelMapping.get(label)
if(maybeId == null) {
val id = getFreshId()
val id: Label<T> = getFreshId()
labelMapping.put(label, id)
return id
} else {
return maybeId
return maybeId as Label<T>
}
}
override fun getFreshId(): Int {
return nextId++
override fun <T> getFreshId(): Label<T> {
return Label(nextId++)
}
}
@@ -104,16 +104,16 @@ class KotlinExtractorVisitor(val trapDir: File, val srcDir: File) : IrElementVis
element.acceptChildren(this, data)
}
override fun visitClass(declaration: IrClass, data: TrapWriter) {
val id = data.getFreshId()
val pkgId = data.getFreshId()
val id: Label<DbClass> = data.getFreshId()
val pkgId: Label<DbPackage> = data.getFreshId()
val locId = data.getLocation(declaration.startOffset, declaration.endOffset)
val pkg = declaration.packageFqName?.asString() ?: ""
val cls = declaration.name.asString()
data.writeTrap("#$pkgId = @\"pkg;$pkg\"\n")
data.writeTrap("packages(#$pkgId, \"$pkg\")\n")
data.writeTrap("#$id = @\"class;$pkg.$cls\"\n")
data.writeTrap("classes(#$id, \"$cls\", #$pkgId, #$id)\n")
data.writeTrap("hasLocation(#$id, #$locId)\n")
data.writeTrap("$pkgId = @\"pkg;$pkg\"\n")
writePackages(data, pkgId, pkg)
data.writeTrap("$id = @\"class;$pkg.$cls\"\n")
writeClasses(data, id, cls, pkgId, id)
writeHasLocation(data, id, locId)
declaration.acceptChildren(this, data)
}
override fun visitFile(declaration: IrFile, data: TrapWriter) {
@@ -132,9 +132,9 @@ class KotlinExtractorVisitor(val trapDir: File, val srcDir: File) : IrElementVis
trapFileDir.mkdirs()
trapFile.bufferedWriter().use { trapFileBW ->
val tw = FileTrapWriter(fileLabel, trapFileBW, declaration.fileEntry)
val id = tw.getIdFor(fileLabel)
tw.writeTrap("#$id = $fileLabel\n")
tw.writeTrap("files(#$id, \"$filePath\", \"$basename\", \"$extension\", 0)\n")
val id: Label<DbFile> = tw.getIdFor(fileLabel)
tw.writeTrap("$id = $fileLabel\n")
writeFiles(tw, id, filePath, basename, extension, 0)
declaration.acceptChildren(this, tw)
}
}