diff --git a/codeCore/src/prog8/code/ast/AstPrinter.kt b/codeCore/src/prog8/code/ast/AstPrinter.kt new file mode 100644 index 000000000..0e706dde8 --- /dev/null +++ b/codeCore/src/prog8/code/ast/AstPrinter.kt @@ -0,0 +1,153 @@ +package prog8.code.ast + +import prog8.code.core.* + +/** + * Produces readable text from a [PtNode] (AST node, usually starting with PtProgram as root), + * passing it as a String to the specified receiver function. + */ +fun printAst(root: PtNode, output: (text: String) -> Unit) { + fun type(dt: DataType) = "!${dt.name.lowercase()}!" + fun txt(node: PtNode): String { + return when(node) { + is PtAssignTarget -> "" + is PtAssignment -> "" + is PtBreakpoint -> "%breakpoint" + is PtConditionalBranch -> "if_${node.condition.name.lowercase()}" + is PtAddressOf -> "&" + is PtArray -> "array len=${node.children.size} ${type(node.type)}" + is PtArrayIndexer -> " ${type(node.type)}" + is PtBinaryExpression -> " ${node.operator} ${type(node.type)}" + is PtBuiltinFunctionCall -> { + val str = if(node.void) "void " else "" + str + node.name + "()" + } + is PtContainmentCheck -> "in" + is PtFunctionCall -> { + val str = if(node.void) "void " else "" + str + node.name + "()" + } + is PtIdentifier -> "${node.name} ${type(node.type)}" + is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}" + is PtMemoryByte -> "@()" + is PtNumber -> "${node.number.toHex()} ${type(node.type)}" + is PtPrefix -> node.operator + is PtRange -> "" + is PtString -> "\"${node.value.escape()}\"" + is PtTypeCast -> "as ${node.type.name.lowercase()}" + is PtForLoop -> "for" + is PtIfElse -> "ifelse" + is PtIncludeBinary -> "%incbin '${node.file}', ${node.offset}, ${node.length}" + is PtInlineAssembly -> { + if(node.isIR) + "%ir {{ ...${node.assembly.length} characters... }}" + else + "%asm {{ ...${node.assembly.length} characters... }}" + } + is PtJump -> { + if(node.identifier!=null) + "goto ${node.identifier.name}" + else if(node.address!=null) + "goto ${node.address.toHex()}" + else if(node.generatedLabel!=null) + "goto ${node.generatedLabel}" + else + "???" + } + is PtAsmSub -> { + val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..." + val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}" + val returns = if (node.returnTypes.isEmpty()) "" else (if (node.returnTypes.size == 1) "-> ${node.returnTypes[0].name.lowercase()}" else "-> ${node.returnTypes.map { it.name.lowercase() }}") + val str = if (node.inline) "inline " else "" + if(node.address==null) { + str + "asmsub ${node.name}($params) $clobbers $returns" + } else { + str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns" + } + } + is PtBlock -> { + val addr = if(node.address==null) "" else "@${node.address?.toHex()}" + val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}" + "\nblock '${node.name}' $addr $align" + } + is PtConstant -> { + val value = if(node.type in IntegerDatatypes) node.value.toInt().toString() else node.value.toString() + "const ${node.type.name.lowercase()} ${node.name} = $value" + } + is PtLabel -> "${node.name}:" + is PtMemMapped -> { + if(node.type in ArrayDatatypes) { + val arraysize = if(node.arraySize==null) "" else node.arraySize.toString() + val eltType = ArrayToElementTypes.getValue(node.type) + "&${eltType.name.lowercase()}[$arraysize] ${node.name} = ${node.address.toHex()}" + } else { + "&${node.type.name.lowercase()} ${node.name} = ${node.address.toHex()}" + } + } + is PtSub -> { + val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..." + var str = if(node.inline) "inline " else "" + str += "sub ${node.name}($params) " + if(node.returntype!=null) + str += "-> ${node.returntype.name.lowercase()}" + str + } + is PtVariable -> { + val str = if(node.arraySize!=null) { + val eltType = ArrayToElementTypes.getValue(node.type) + "${eltType.name.lowercase()}[${node.arraySize}] ${node.name}" + } + else if(node.type in ArrayDatatypes) { + val eltType = ArrayToElementTypes.getValue(node.type) + "${eltType.name.lowercase()}[] ${node.name}" + } + else + "${node.type.name.lowercase()} ${node.name}" + if(node.value!=null) + str + " = " + txt(node.value!!) + else + str + } + is PtNodeGroup -> "" + is PtNop -> "nop" + is PtPostIncrDecr -> " ${node.operator}" + is PtProgram -> "PROGRAM ${node.name}" + is PtRepeatLoop -> "repeat" + is PtReturn -> "return" + is PtSubroutineParameter -> "${node.type.name.lowercase()} ${node.name}" + is PtWhen -> "when" + is PtWhenChoice -> { + if(node.isElse) + "else" + else + "->" + } + else -> throw InternalCompilerException("unrecognised ast node $node") + } + } + + if(root is PtProgram) { + output(txt(root)) + root.children.forEach { + walkAst(it) { node, depth -> + val txt = txt(node) + if(txt.isNotEmpty()) + output(" ".repeat(depth) + txt(node)) + } + } + } else { + walkAst(root) { node, depth -> + val txt = txt(node) + if(txt.isNotEmpty()) + output(" ".repeat(depth) + txt(node)) + } + } +} + +fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Unit) { + fun recurse(node: PtNode, depth: Int) { + act(node, depth) + node.children.forEach { recurse(it, depth+1) } + } + recurse(root, 0) +} diff --git a/codeCore/src/prog8/code/ast/AstStatements.kt b/codeCore/src/prog8/code/ast/AstStatements.kt index b57eab7b3..662537d03 100644 --- a/codeCore/src/prog8/code/ast/AstStatements.kt +++ b/codeCore/src/prog8/code/ast/AstStatements.kt @@ -214,8 +214,7 @@ class PtConstant(name: String, override val type: DataType, val value: Double, p } -// TODO what about memory mapped arrays that have a length? missing property! -class PtMemMapped(name: String, override val type: DataType, val address: UInt, position: Position) : PtNamedNode(name, position), IPtVariable { +class PtMemMapped(name: String, val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position) { override fun printProperties() { print("&$type $name = ${address.toHex()}") } diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt index ffb41c680..f4cce7c4e 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt @@ -349,7 +349,7 @@ class IntermediateAstMaker(private val program: Program, private val options: Co PtVariable(srcVar.name, srcVar.datatype, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position) } VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position) - VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.position) + VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position) } } diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 02eb1caa7..86b40731f 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -760,7 +760,8 @@ Math ^^^^ abs(x) - Absolute value of an integer. For floating point numbers, use ``floats.fabs()`` instead. + Absolute value of an integer. Value returned is an unsigned word. + For floating point numbers, use ``floats.fabs()`` instead. sgn(x) Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive). @@ -837,8 +838,7 @@ pokew(address, value) writes the word value at the given address in memory, in usual little-endian lsb/msb byte order. pokemon(address, value) - Attempts to write a byte to a ROM at a location in machine language monitor bank. - Doesn't have anything to do with a certain video game. + Doesn't do anything useful. Also doesn't have anything to do with a certain video game. push(value) pushes a byte value on the CPU hardware stack. Low-level function that should normally not be used.