started a c++ language compiler code target

(meant to be an intermediate step before direct Wasm/binaryen, via clang compilation to wasm)
This commit is contained in:
Irmen de Jong 2019-10-28 00:09:21 +01:00
parent 8c2e602cc7
commit 165eec4054
3 changed files with 362 additions and 1 deletions

View File

@ -7,6 +7,8 @@ import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.clang.ClangGen
import prog8.compiler.target.clang.ClangMachineDefinition
import prog8.parser.ParsingFailedError
import prog8.vm.astvm.AstVm
import java.nio.file.FileSystems
@ -60,7 +62,12 @@ private fun compileMain(args: Array<String>) {
}
}
"clang" -> {
TODO("clang target")
with(CompilationTarget) {
name = "clang"
machine = ClangMachineDefinition
encodeString = { str -> str.toByteArray().map { it.toShort()} }
asmGenerator = ::ClangGen
}
}
else -> {
System.err.println("invalid compilation target")

View File

@ -0,0 +1,331 @@
package prog8.compiler.target.clang
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.compiler.target.IAssemblyGenerator
import prog8.compiler.target.IAssemblyProgram
import java.io.PrintWriter
import java.nio.file.Path
internal class ClangGen(private val program: Program,
private val zeropage: Zeropage,
private val options: CompilationOptions,
private val outputDir: Path) : IAssemblyGenerator {
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
println("Generating C++ language code... [UNFINISHED, EXPERIMENTAL]")
// TODO manually flatten all scopes. Needed because in C++ even with forward declarations it's not possible to freely access members across all classes
val outputFile = outputDir.resolve("${program.name}.cpp").toFile()
outputFile.printWriter().use { out ->
header(out)
program.allBlocks().forEach { block(out, it) }
footer(out)
}
return ClangAssemblyProgram(program.name, outputDir)
}
private fun header(out: PrintWriter) {
out.println("// c++ transpiled version of Prog8 program ${program.name}\n")
out.println("#include <cstdint>")
out.println("""
template <typename T,T iBegin,T iEnd,T iStep=1>
class _prog8_range {
public:
struct iterator {
T value;
iterator (T v) : value(v) {}
operator T () const { return value; }
operator T& () { return value; }
T operator* () const { return value; }
iterator& operator++ () { value += iStep; return *this; }
};
iterator begin() { return iBegin; }
iterator end() { return iEnd; }
};
void _prog8_inlineasm(const char* assembly) { /* nothing */ }
void _prog8_goto_addr(uint16_t addr) { /* nothing */ }
void _prog8_directive_asminclude(const char* module, const char* scopename) { /* nothing */ }
uint8_t _prog8_register_A = 0;
uint8_t _prog8_register_X = 0;
uint8_t _prog8_register_Y = 0;
uint8_t _prog8_memory[65536];
""")
}
private fun footer(out: PrintWriter) {
}
private fun block(out: PrintWriter, block: Block) {
out.println("// block: $block")
out.println("class ${cname(block.name)} {")
out.println("\tconst char* _prog8_linepos = \"${block.position}\";")
out.print("\tvoid* _prog8_block_address = ")
if (block.address == null)
out.println("nullptr;")
else {
out.println("0x${block.address.toString(16)};")
}
out.println("public:")
block.statements.forEach { statement(out, it) }
out.println("};\n")
}
private fun statement(out: PrintWriter, stmt: Statement) {
when (stmt) {
is BuiltinFunctionStatementPlaceholder -> TODO("$stmt")
is Directive -> directive(out, stmt)
is Label -> out.println("${cname(stmt.name)}:")
is Return -> {
if (stmt.parent is Subroutine) {
if (stmt.value == null)
out.println("return;")
else {
out.println("return ${expression(stmt.value!!)};")
}
}
}
is Continue -> out.println("continue;")
is Break -> out.println("break;")
is VarDecl -> {
val sub = stmt.definingSubroutine()
if (sub == null || sub.parameters.all { it.name != stmt.name })
vardecl(out, stmt)
}
is Assignment -> assignment(out, stmt)
is PostIncrDecr -> out.println("${assigntarget(stmt.target)}${stmt.operator};")
is Jump -> jump(out, stmt)
is FunctionCallStatement -> out.println("${stmt.target.nameInSource.joinToString("::")}(${arguments(stmt.arglist)});")
is InlineAssembly -> {
val asmlines = stmt.assembly.lines().map { "\"$it\"" }.joinToString("\n")
out.println("_prog8_inlineasm($asmlines);")
}
is AnonymousScope -> {
out.println("{")
stmt.statements.forEach { statement(out, it) }
out.println("}")
}
is NopStatement -> out.println("// NOP")
is Subroutine -> {
if (stmt.isAsmSubroutine) {
out.println("// TODO ASM-SUBROUTINE: ${stmt.name} @ ${stmt.position}") // TODO
} else {
val returnType = datatype(stmt.returntypes.singleOrNull(), null)
val params = stmt.parameters.map {
val dt = datatype(it.type, null)
"${dt.first} ${cname(it.name)}${dt.second}"
}
val paramstr = params.joinToString(", ")
out.println("\nstatic ${returnType.first} ${cname(stmt.name)}${returnType.second}($paramstr) {")
out.println("\tconst char* _prog8_linepos = \"${stmt.position}\";")
stmt.statements.forEach { statement(out, it) }
out.println("}\n")
}
}
is IfStatement -> ifstatement(out, stmt)
is BranchStatement -> branchstatement(out, stmt)
is ForLoop -> forloop(out, stmt)
is WhileLoop -> whileloop(out, stmt)
is RepeatLoop -> repeatloop(out, stmt)
is WhenStatement -> out.println("// stmt: $stmt") // TODO
is StructDecl -> out.println("// stmt: $stmt") // TODO
else -> throw AssemblyError("c++ translation error: unexpected statement: $stmt")
}
}
private fun assignment(out: PrintWriter, assign: Assignment) {
val target = assigntarget(assign.target)
val operator = assign.aug_op ?: "="
out.println("$target $operator ${expression(assign.value)};")
}
private fun assigntarget(target: AssignTarget): String {
return when {
target.register!=null -> "_prog8_register_${target.register}"
target.identifier!=null -> expression(target.identifier!!)
target.memoryAddress!=null -> "(*(uint8_t*)${expression(target.memoryAddress.addressExpression)}) "
target.arrayindexed!=null -> expression(target.arrayindexed!!)
else -> "?????"
}
}
private fun jump(out: PrintWriter, jmp: Jump) {
when {
jmp.generatedLabel!=null -> out.println("goto ${jmp.generatedLabel};")
jmp.address!=null -> out.println("_prog8_goto_addr(${jmp.address.toString(16)});")
jmp.identifier!=null -> out.println("goto ${expression(jmp.identifier)};")
}
}
private fun forloop(out: PrintWriter, forlp: ForLoop) {
out.println("// forloop $forlp") // TODO
}
private fun repeatloop(out: PrintWriter, repeatLoop: RepeatLoop) {
out.println("do {")
statement(out, repeatLoop.body)
out.println("} while(!(${expression(repeatLoop.untilCondition)}));")
}
private fun whileloop(out: PrintWriter, whileLoop: WhileLoop) {
out.println("while(${expression(whileLoop.condition)}) {")
statement(out, whileLoop.body)
out.println("}")
}
private fun ifstatement(out: PrintWriter, ifs: IfStatement) {
out.println("if(${expression(ifs.condition)}) {")
statement(out, ifs.truepart)
if(ifs.elsepart.containsCodeOrVars()) {
out.println("} else {")
statement(out, ifs.elsepart)
}
out.println("}")
}
private fun branchstatement(out: PrintWriter, branch: BranchStatement) {
out.println("if(BranchFlag.${branch.condition}) {")
statement(out, branch.truepart)
if(branch.elsepart.containsCodeOrVars()) {
out.println("} else {")
statement(out, branch.elsepart)
}
out.println("}")
}
private fun directive(out: PrintWriter, dr: Directive) {
val args = dr.args.map {
when {
it.int!=null -> it.int.toString()
it.name!=null -> "\"${it.name}\""
it.str!=null -> "\"${it.str}\""
else -> ""
}
}
out.println("_prog8_directive_${dr.directive.substring(1)}(${args.joinToString(", ")});")
}
private fun arguments(args: List<Expression>): String = args.map{expression(it)}.joinToString(", ")
private fun vardecl(out: PrintWriter, decl: VarDecl) {
fun cdecl(memory: Boolean) {
val dtype = datatype(decl.datatype, decl.arraysize)
out.print(dtype.first)
if (memory) {
out.print("* ${cname(decl.name)}")
} else {
out.print(" ${cname(decl.name)}${dtype.second}")
}
if (decl.value != null) {
// TODO c++ requires the value initialization to be done outside the class in a static assignment...
if (memory)
out.print(" = static_cast<${dtype.first}*>(${expression(decl.value!!)})")
else
out.print(" = ${expression(decl.value!!)}")
}
// TODO zeropage flag
out.println(";")
}
out.print("static ")
when (decl.type) {
VarDeclType.VAR -> {
cdecl(false)
}
VarDeclType.CONST -> {
out.print("constexpr const ")
cdecl(false)
}
VarDeclType.MEMORY -> {
out.print("constexpr const ")
cdecl(true)
}
}
}
private fun expression(expr: Expression): String {
return when (expr) {
is PrefixExpression -> "${expr.operator}${expression(expr.expression)}"
is BinaryExpression -> "${expression(expr.left)} ${expr.operator} ${expression(expr.right)}"
is ArrayIndexedExpression -> "${expression(expr.identifier)}[${expression(expr.arrayspec.index)}]"
is TypecastExpression -> "static_cast<${datatype(expr.type, null)}>(${expression(expr.expression)}) "
is AddressOf -> "&${expr.identifier.nameInSource.joinToString("::")}"
is DirectMemoryRead -> "_prog8_memory[${expression(expr.addressExpression)}]"
is NumericLiteralValue -> "${expr.number}"
is StructLiteralValue -> TODO("struct literal")
is StringLiteralValue -> "\"${expr.value}\""
is ArrayLiteralValue -> arrayliteral(expr)
is RangeExpr -> rangeexpr(expr)
is RegisterExpr -> "_prog8_register_${expr.register}"
is IdentifierReference -> expr.nameInSource.joinToString("::")
is FunctionCall -> functioncall(expr)
}
}
private fun rangeexpr(range: RangeExpr): String {
val constrange = range.toConstantIntegerRange()
return if(constrange!=null) {
if(constrange.first>=0 && constrange.last>=0)
"_prog8_range<uint16_t, ${constrange.first}, ${constrange.last}, ${constrange.step}>()"
else
"_prog8_range<int16_t, ${constrange.first}, ${constrange.last}, ${constrange.step}>()"
} else {
TODO("rangeexpr $range")
}
}
private fun functioncall(fc: FunctionCall): String {
val args = fc.arglist.map { expression(it) }.joinToString(", ")
return "${expression(fc.target)}($args)"
}
private fun arrayliteral(arraylv: ArrayLiteralValue): String {
val values = arraylv.value.map { expression(it) }.joinToString(", ")
return "{ $values }"
}
private fun cname(name: String): String {
return if (name in listOf("char", "int", "short", "void")) name + "__c_" else name
}
private fun datatype(dt: DataType?, arraysize: ArrayIndex?): Pair<String, String> {
return when (dt) {
DataType.UBYTE -> Pair("uint8_t", "")
DataType.BYTE -> Pair("int8_t", "")
DataType.UWORD -> Pair("uint16_t", "")
DataType.WORD -> Pair("int16_t", "")
DataType.FLOAT -> Pair("float", "")
DataType.STR -> Pair("const char*", "")
DataType.STR_S -> Pair("const char*", "")
DataType.ARRAY_UB -> if (arraysize == null) Pair("uint8_t", "[]") else Pair("uint8_t", "[${expression(arraysize.index)}]")
DataType.ARRAY_B -> if (arraysize == null) Pair("int8_t", "[]") else Pair("int8_t", "[${expression(arraysize.index)}]")
DataType.ARRAY_UW -> if (arraysize == null) Pair("uint16_t", "[]") else Pair("uint16_t", "[${expression(arraysize.index)}]")
DataType.ARRAY_W -> if (arraysize == null) Pair("int16_t", "[]") else Pair("int16_t", "[${expression(arraysize.index)}]")
DataType.ARRAY_F -> if (arraysize == null) Pair("float", "[]") else Pair("float", "[${expression(arraysize.index)}]")
DataType.STRUCT -> TODO("struct")
null -> Pair("void", "")
}
}
}
class ClangAssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
override fun assemble(options: CompilationOptions) {
println("TODO C++ COMPILE?")
TODO("not implemented - use g++ or clang to compile the code")
}
}

View File

@ -0,0 +1,23 @@
package prog8.compiler.target.clang
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition
object ClangMachineDefinition: IMachineDefinition {
override val FLOAT_MAX_NEGATIVE: Double = C64MachineDefinition.FLOAT_MAX_NEGATIVE
override val FLOAT_MAX_POSITIVE: Double = C64MachineDefinition.FLOAT_MAX_POSITIVE
override val FLOAT_MEM_SIZE: Int = C64MachineDefinition.FLOAT_MEM_SIZE
override val opcodeNames: Set<String> = C64MachineDefinition.opcodeNames
override fun getZeropage(compilerOptions: CompilationOptions): Zeropage = ClangZeropage(compilerOptions)
class ClangZeropage(options: CompilationOptions) : Zeropage(options) {
override val exitProgramStrategy: ExitProgramStrategy = ExitProgramStrategy.SYSTEM_RESET
init {
for (reserved in options.zpReserved)
reserve(reserved)
}
}
}