prog8/compiler/src/prog8/ast/AST.kt
2019-07-02 04:29:51 +02:00

2634 lines
100 KiB
Kotlin

package prog8.ast
import org.antlr.v4.runtime.IntStream
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode
import prog8.compiler.RuntimeValue
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import prog8.parser.CustomLexer
import prog8.parser.prog8Parser
import java.io.CharConversionException
import java.io.File
import java.nio.file.Path
import kotlin.math.abs
import kotlin.math.floor
/**************************** AST Data classes ****************************/
enum class DataType {
UBYTE,
BYTE,
UWORD,
WORD,
FLOAT,
STR,
STR_S,
ARRAY_UB,
ARRAY_B,
ARRAY_UW,
ARRAY_W,
ARRAY_F;
/**
* is the type assignable to the given other type?
*/
infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision?
when(this) {
UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
UWORD -> targetType == UWORD || targetType == FLOAT
WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT
FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S
STR_S -> targetType == STR || targetType==STR_S
in ArrayDatatypes -> targetType === this
else -> false
}
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
infix fun biggerThan(other: DataType) =
when(this) {
in ByteDatatypes -> false
in WordDatatypes -> other in ByteDatatypes
else -> true
}
}
enum class Register {
A,
X,
Y
}
enum class RegisterOrPair {
A,
X,
Y,
AX,
AY,
XY
} // only used in parameter and return value specs in asm subroutines
enum class Statusflag {
Pc,
Pz,
Pv,
Pn
}
enum class BranchCondition {
CS,
CC,
EQ,
Z,
NE,
NZ,
VS,
VC,
MI,
NEG,
PL,
POS
}
val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val ArrayElementTypes = mapOf(
DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE,
DataType.ARRAY_W to DataType.WORD,
DataType.ARRAY_UW to DataType.UWORD,
DataType.ARRAY_F to DataType.FLOAT)
class FatalAstException (override var message: String) : Exception(message)
open class AstException (override var message: String) : Exception(message)
class SyntaxError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Syntax error: $message"
}
class NameError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Name error: $message"
}
open class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Error: $message"
}
class UndefinedSymbolError(symbol: IdentifierReference)
: ExpressionError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
}
interface IAstProcessor {
fun process(program: Program) {
program.modules.forEach { process(it) }
}
fun process(module: Module) {
module.statements = module.statements.asSequence().map { it.process(this) }.toMutableList()
}
fun process(expr: PrefixExpression): IExpression {
expr.expression = expr.expression.process(this)
return expr
}
fun process(expr: BinaryExpression): IExpression {
expr.left = expr.left.process(this)
expr.right = expr.right.process(this)
return expr
}
fun process(directive: Directive): IStatement {
return directive
}
fun process(block: Block): IStatement {
block.statements = block.statements.asSequence().map { it.process(this) }.toMutableList()
return block
}
fun process(decl: VarDecl): IStatement {
decl.value = decl.value?.process(this)
decl.arraysize?.process(this)
return decl
}
fun process(subroutine: Subroutine): IStatement {
subroutine.statements = subroutine.statements.asSequence().map { it.process(this) }.toMutableList()
return subroutine
}
fun process(functionCall: FunctionCall): IExpression {
val newtarget = functionCall.target.process(this)
if(newtarget is IdentifierReference)
functionCall.target = newtarget
functionCall.arglist = functionCall.arglist.map { it.process(this) }.toMutableList()
return functionCall
}
fun process(functionCallStatement: FunctionCallStatement): IStatement {
val newtarget = functionCallStatement.target.process(this)
if(newtarget is IdentifierReference)
functionCallStatement.target = newtarget
functionCallStatement.arglist = functionCallStatement.arglist.map { it.process(this) }.toMutableList()
return functionCallStatement
}
fun process(identifier: IdentifierReference): IExpression {
// note: this is an identifier that is used in an expression.
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
return identifier
}
fun process(jump: Jump): IStatement {
if(jump.identifier!=null) {
val ident = jump.identifier.process(this)
if(ident is IdentifierReference && ident!==jump.identifier) {
return Jump(null, ident, null, jump.position)
}
}
return jump
}
fun process(ifStatement: IfStatement): IStatement {
ifStatement.condition = ifStatement.condition.process(this)
ifStatement.truepart = ifStatement.truepart.process(this) as AnonymousScope
ifStatement.elsepart = ifStatement.elsepart.process(this) as AnonymousScope
return ifStatement
}
fun process(branchStatement: BranchStatement): IStatement {
branchStatement.truepart = branchStatement.truepart.process(this) as AnonymousScope
branchStatement.elsepart = branchStatement.elsepart.process(this) as AnonymousScope
return branchStatement
}
fun process(range: RangeExpr): IExpression {
range.from = range.from.process(this)
range.to = range.to.process(this)
range.step = range.step.process(this)
return range
}
fun process(label: Label): IStatement {
return label
}
fun process(literalValue: LiteralValue): LiteralValue {
if(literalValue.arrayvalue!=null) {
for(av in literalValue.arrayvalue.withIndex()) {
val newvalue = av.value.process(this)
literalValue.arrayvalue[av.index] = newvalue
}
}
return literalValue
}
fun process(assignment: Assignment): IStatement {
assignment.targets = assignment.targets.map { it.process(this) }
assignment.value = assignment.value.process(this)
return assignment
}
fun process(postIncrDecr: PostIncrDecr): IStatement {
postIncrDecr.target = postIncrDecr.target.process(this)
return postIncrDecr
}
fun process(contStmt: Continue): IStatement {
return contStmt
}
fun process(breakStmt: Break): IStatement {
return breakStmt
}
fun process(forLoop: ForLoop): IStatement {
forLoop.loopVar?.process(this)
forLoop.iterable = forLoop.iterable.process(this)
forLoop.body = forLoop.body.process(this) as AnonymousScope
return forLoop
}
fun process(whileLoop: WhileLoop): IStatement {
whileLoop.condition = whileLoop.condition.process(this)
whileLoop.body = whileLoop.body.process(this) as AnonymousScope
return whileLoop
}
fun process(repeatLoop: RepeatLoop): IStatement {
repeatLoop.untilCondition = repeatLoop.untilCondition.process(this)
repeatLoop.body = repeatLoop.body.process(this) as AnonymousScope
return repeatLoop
}
fun process(returnStmt: Return): IStatement {
returnStmt.values = returnStmt.values.map { it.process(this) }
return returnStmt
}
fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
arrayIndexedExpression.identifier.process(this)
arrayIndexedExpression.arrayspec.process(this)
return arrayIndexedExpression
}
fun process(assignTarget: AssignTarget): AssignTarget {
assignTarget.arrayindexed?.process(this)
assignTarget.identifier?.process(this)
assignTarget.memoryAddress?.process(this)
return assignTarget
}
fun process(scope: AnonymousScope): IStatement {
scope.statements = scope.statements.asSequence().map { it.process(this) }.toMutableList()
return scope
}
fun process(typecast: TypecastExpression): IExpression {
typecast.expression = typecast.expression.process(this)
return typecast
}
fun process(memread: DirectMemoryRead): IExpression {
memread.addressExpression = memread.addressExpression.process(this)
return memread
}
fun process(memwrite: DirectMemoryWrite): IExpression {
memwrite.addressExpression = memwrite.addressExpression.process(this)
return memwrite
}
fun process(addressOf: AddressOf): IExpression {
addressOf.identifier.process(this)
return addressOf
}
fun process(inlineAssembly: InlineAssembly): IStatement {
return inlineAssembly
}
}
interface Node {
val position: Position
var parent: Node // will be linked correctly later (late init)
fun linkParents(parent: Node)
fun definingModule(): Module {
if(this is Module)
return this
return findParentNode<Module>(this)!!
}
fun definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
fun definingScope(): INameScope {
val scope = findParentNode<INameScope>(this)
if(scope!=null) {
return scope
}
if(this is Label && this.name.startsWith("builtin::")) {
return BuiltinFunctionScopePlaceholder
}
if(this is GlobalNamespace)
return this
throw FatalAstException("scope missing from $this")
}
}
// find the parent node of a specific type or interface
// (useful to figure out in what namespace/block something is defined, etc)
inline fun <reified T> findParentNode(node: Node): T? {
var candidate = node.parent
while(candidate !is T && candidate !is ParentSentinel)
candidate = candidate.parent
return if(candidate is ParentSentinel)
null
else
candidate as T
}
interface IStatement : Node {
fun process(processor: IAstProcessor) : IStatement
fun makeScopedName(name: String): String {
// easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
// and like this, we can cache the name even,
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
val scope = mutableListOf<String>()
var statementScope = this.parent
while(statementScope !is ParentSentinel && statementScope !is Module) {
if(statementScope is INameScope) {
scope.add(0, statementScope.name)
}
statementScope = statementScope.parent
}
if(name.isNotEmpty())
scope.add(name)
return scope.joinToString(".")
}
val expensiveToInline: Boolean
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
}
interface IFunctionCall {
var target: IdentifierReference
var arglist: MutableList<IExpression>
}
interface INameScope {
val name: String
val position: Position
val statements: MutableList<IStatement>
val parent: Node
fun linkParents(parent: Node)
fun subScopes(): Map<String, INameScope> {
val subscopes = mutableMapOf<String, INameScope>()
for(stmt in statements) {
when(stmt) {
is INameScope -> subscopes[stmt.name] = stmt
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
}
}
return subscopes
}
fun getLabelOrVariable(name: String): IStatement? {
// TODO this is called A LOT and could perhaps be optimized a bit more, but adding a cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
}
return null
}
fun allDefinedSymbols(): List<Pair<String, IStatement>> {
return statements.mapNotNull {
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
}
fun lookup(scopedName: List<String>, localContext: Node) : IStatement? {
if(scopedName.size>1) {
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScopes()?.get(name)
if(scope==null)
break
}
if(scope!=null) {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScopes()[scopedName.last()] as IStatement?
}
}
return null
} else {
// unqualified name, find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope()
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScopes()[scopedName[0]] as IStatement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
}
}
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: IStatement) {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
}
}
object ParentSentinel : Node {
override val position = Position("<<sentinel>>", 0, 0, 0)
override var parent: Node = this
override fun linkParents(parent: Node) {}
}
object BuiltinFunctionScopePlaceholder : INameScope {
override val name = "<<builtin-functions-scope-placeholder>>"
override val position = Position("<<placeholder>>", 0, 0, 0)
override var statements = mutableListOf<IStatement>()
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
}
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement {
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
override fun process(processor: IAstProcessor): IStatement = this
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
override val expensiveToInline = false
}
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>) {
val namespace = GlobalNamespace(modules)
val heap = HeapValues()
val loadAddress: Int
get() = modules.first().loadAddress
fun entrypoint(): Subroutine? {
val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
if(mainBlocks.size > 1)
throw FatalAstException("more than one 'main' block")
return if(mainBlocks.isEmpty()) {
null
} else {
mainBlocks[0].subScopes()["start"] as Subroutine?
}
}
}
class Module(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position,
val isLibraryModule: Boolean,
val source: Path) : Node, INameScope {
override lateinit var parent: Node
lateinit var program: Program
val importedBy = mutableListOf<Module>()
val imports = mutableSetOf<Module>()
var loadAddress: Int = 0 // can be set with the %address directive
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach {it.linkParents(this)}
}
override fun definingScope(): INameScope = program.namespace
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
}
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<IStatement>()
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(this) }
}
override fun lookup(scopedName: List<String>, localContext: Node): IStatement? {
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
// builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
}
}
}
class Block(override val name: String,
val address: Int?,
override var statements: MutableList<IStatement>,
val isInLibrary: Boolean,
override val position: Position) : IStatement, INameScope {
override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach {it.linkParents(this)}
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "Block(name=$name, address=$address, ${statements.size} statements)"
}
fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet()
}
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
args.forEach{it.linkParents(this)}
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
}
data class Label(val name: String, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "Label(name=$name, pos=$position)"
}
val scopedname: String by lazy { makeScopedName(name) }
}
open class Return(var values: List<IExpression>, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = values.any { it !is LiteralValue }
override fun linkParents(parent: Node) {
this.parent = parent
values.forEach {it.linkParents(this)}
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "Return(values: $values, pos=$position)"
}
}
class ReturnFromIrq(override val position: Position) : Return(emptyList(), position) {
override fun process(processor: IAstProcessor) = this
override fun toString(): String {
return "ReturnFromIrq(pos=$position)"
}
}
class Continue(override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent=parent
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
class Break(override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent=parent
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
class ArrayIndex(var index: IExpression, override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
index.linkParents(this)
}
companion object {
fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex {
val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize
return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position)
}
}
fun process(processor: IAstProcessor) {
index = index.process(processor)
}
override fun toString(): String {
return("ArrayIndex($index, pos=$position)")
}
fun size() = (index as? LiteralValue)?.asIntegerValue
}
enum class VarDeclType {
VAR,
CONST,
MEMORY
}
class VarDecl(val type: VarDeclType,
private val declaredDatatype: DataType,
val zeropage: Boolean,
var arraysize: ArrayIndex?,
val name: String,
var value: IExpression?,
val isArray: Boolean,
val autoGenerated: Boolean,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline
get() = value!=null && value !is LiteralValue
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
val datatype =
if (!isArray) declaredDatatype
else when (declaredDatatype) {
DataType.UBYTE -> DataType.ARRAY_UB
DataType.BYTE -> DataType.ARRAY_B
DataType.UWORD -> DataType.ARRAY_UW
DataType.WORD -> DataType.ARRAY_W
DataType.FLOAT -> DataType.ARRAY_F
else -> {
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
DataType.UBYTE
}
}
override fun linkParents(parent: Node) {
this.parent = parent
arraysize?.linkParents(this)
value?.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
val scopedname: String by lazy { makeScopedName(name) }
override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, array=$isArray, value=$value, pos=$position)"
}
fun asDefaultValueDecl(parent: Node?): VarDecl {
val constValue = when(declaredDatatype) {
DataType.UBYTE -> LiteralValue(DataType.UBYTE, 0, position=position)
DataType.BYTE -> LiteralValue(DataType.BYTE, 0, position=position)
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue=0, position=position)
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue=0, position=position)
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue=0.0, position=position)
else -> throw FatalAstException("can only set a default value for a numeric type")
}
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, true, position)
if(parent!=null)
decl.linkParents(parent)
return decl
}
}
open class Assignment(var targets: List<AssignTarget>, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline
get() = value !is LiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
targets.forEach { it.linkParents(this) }
value.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return("Assignment(augop: $aug_op, targets: $targets, value: $value, pos=$position)")
}
val singleTarget: AssignTarget?
get() {
return targets.singleOrNull() // common case
}
}
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
// or just a regular assignment. It may optimize the initialization step from this.
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: IExpression, position: Position)
: Assignment(listOf(target), aug_op, value, position)
data class AssignTarget(val register: Register?,
val identifier: IdentifierReference?,
val arrayindexed: ArrayIndexedExpression?,
var memoryAddress: DirectMemoryWrite?,
override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier?.linkParents(this)
arrayindexed?.linkParents(this)
memoryAddress?.linkParents(this)
}
fun process(processor: IAstProcessor) = processor.process(this)
companion object {
fun fromExpr(expr: IExpression): AssignTarget {
return when (expr) {
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position)
is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
is DirectMemoryWrite -> AssignTarget(null, null, null, expr, expr.position)
else -> throw FatalAstException("invalid expression object $expr")
}
}
}
fun inferType(program: Program, stmt: IStatement): DataType? {
if(register!=null)
return DataType.UBYTE
if(identifier!=null) {
val symbol = program.namespace.lookup(identifier.nameInSource, stmt) ?: return null
if (symbol is VarDecl) return symbol.datatype
}
if(arrayindexed!=null) {
val dt = arrayindexed.inferType(program)
if(dt!=null)
return dt
}
if(memoryAddress!=null)
return DataType.UBYTE
return null
}
fun shortString(withTypePrefix: Boolean=false): String {
if(register!=null)
return (if(withTypePrefix) "0register::" else "") + register.name
if(identifier!=null)
return (if(withTypePrefix) "3identifier::" else "") + identifier.nameInSource.last()
if(arrayindexed!=null)
return (if(withTypePrefix) "2arrayidx::" else "") + arrayindexed.identifier.nameInSource.last()
val address = memoryAddress?.addressExpression
if(address is LiteralValue)
return (if(withTypePrefix) "1address::" else "") +address.asIntegerValue.toString()
return if(withTypePrefix) "???::???" else "???"
}
fun isMemoryMapped(namespace: INameScope): Boolean =
memoryAddress!=null || (identifier?.targetVarDecl(namespace)?.type==VarDeclType.MEMORY)
infix fun isSameAs(value: IExpression): Boolean {
return when {
this.memoryAddress!=null -> false
this.register!=null -> value is RegisterExpr && value.register==register
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier.nameInSource
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
value.identifier.nameInSource==arrayindexed.identifier.nameInSource &&
value.arrayspec.size()!=null &&
arrayindexed.arrayspec.size()!=null &&
value.arrayspec.size()==arrayindexed.arrayspec.size()
else -> false
}
}
fun isSameAs(other: AssignTarget, program: Program): Boolean {
if(this===other)
return true
if(this.register!=null && other.register!=null)
return this.register==other.register
if(this.identifier!=null && other.identifier!=null)
return this.identifier.nameInSource==other.identifier.nameInSource
if(this.memoryAddress!=null && other.memoryAddress!=null) {
val addr1 = this.memoryAddress!!.addressExpression.constValue(program)
val addr2 = other.memoryAddress!!.addressExpression.constValue(program)
return addr1!=null && addr2!=null && addr1==addr2
}
if(this.arrayindexed!=null && other.arrayindexed!=null) {
if(this.arrayindexed.identifier.nameInSource == other.arrayindexed.identifier.nameInSource) {
val x1 = this.arrayindexed.arrayspec.index.constValue(program)
val x2 = other.arrayindexed.arrayspec.index.constValue(program)
return x1!=null && x2!=null && x1==x2
}
}
return false
}
fun isNotMemory(namespace: INameScope): Boolean {
if(this.register!=null)
return true
if(this.memoryAddress!=null)
return false
if(this.arrayindexed!=null) {
val targetStmt = this.arrayindexed.identifier.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!=VarDeclType.MEMORY
}
if(this.identifier!=null) {
val targetStmt = this.identifier.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!=VarDeclType.MEMORY
}
return false
}
}
interface IExpression: Node {
fun constValue(program: Program): LiteralValue?
fun process(processor: IAstProcessor): IExpression
fun referencesIdentifier(name: String): Boolean
fun inferType(program: Program): DataType?
infix fun isSameAs(other: IExpression): Boolean {
if(this===other)
return true
when(this) {
is RegisterExpr ->
return (other is RegisterExpr && other.register==register)
is IdentifierReference ->
return (other is IdentifierReference && other.nameInSource==nameInSource)
is PrefixExpression ->
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
is BinaryExpression ->
return (other is BinaryExpression && other.operator==operator
&& other.left isSameAs left
&& other.right isSameAs right)
is ArrayIndexedExpression -> {
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
}
is LiteralValue -> return (other is LiteralValue && other==this)
}
return false
}
}
// note: some expression elements are mutable, to be able to rewrite/process the expression tree
class PrefixExpression(val operator: String, var expression: IExpression, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
expression.linkParents(this)
}
override fun constValue(program: Program): LiteralValue? = null
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
override fun inferType(program: Program): DataType? = expression.inferType(program)
override fun toString(): String {
return "Prefix($operator $expression)"
}
}
class BinaryExpression(var left: IExpression, var operator: String, var right: IExpression, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
left.linkParents(this)
right.linkParents(this)
}
override fun toString(): String {
return "[$left $operator $right]"
}
// binary expression should actually have been optimized away into a single value, before const value was requested...
override fun constValue(program: Program): LiteralValue? = null
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
override fun inferType(program: Program): DataType? {
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
return when (operator) {
"+", "-", "*", "**", "%" -> if (leftDt == null || rightDt == null) null else {
try {
arithmeticOpDt(leftDt, rightDt)
} catch (x: FatalAstException) {
null
}
}
"/" -> if (leftDt == null || rightDt == null) null else divisionOpDt(leftDt, rightDt)
"&" -> leftDt
"|" -> leftDt
"^" -> leftDt
"and", "or", "xor",
"<", ">",
"<=", ">=",
"==", "!=" -> DataType.UBYTE
"<<", ">>" -> leftDt
else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
}
}
companion object {
fun divisionOpDt(leftDt: DataType, rightDt: DataType): DataType {
return when (leftDt) {
DataType.UBYTE -> when (rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UBYTE
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.BYTE
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.BYTE -> when (rightDt) {
in NumericDatatypes -> DataType.BYTE
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.UWORD -> when (rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.WORD -> when (rightDt) {
in NumericDatatypes -> DataType.WORD
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.FLOAT -> when (rightDt) {
in NumericDatatypes -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
}
fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType {
return when (leftDt) {
DataType.UBYTE -> when (rightDt) {
DataType.UBYTE -> DataType.UBYTE
DataType.BYTE -> DataType.BYTE
DataType.UWORD -> DataType.UWORD
DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.BYTE -> when (rightDt) {
in ByteDatatypes -> DataType.BYTE
in WordDatatypes -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.UWORD -> when (rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.WORD -> when (rightDt) {
in IntegerDatatypes -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.FLOAT -> when (rightDt) {
in NumericDatatypes -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
}
}
fun commonDatatype(leftDt: DataType, rightDt: DataType,
left: IExpression, right: IExpression): Pair<DataType, IExpression?> {
// byte + byte -> byte
// byte + word -> word
// word + byte -> word
// word + word -> word
// a combination with a float will be float (but give a warning about this!)
if(this.operator=="/") {
// division is a bit weird, don't cast the operands
val commondt = divisionOpDt(leftDt, rightDt)
return Pair(commondt, null)
}
return when (leftDt) {
DataType.UBYTE -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.UBYTE, null)
DataType.BYTE -> Pair(DataType.BYTE, left)
DataType.UWORD -> Pair(DataType.UWORD, left)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
}
}
DataType.BYTE -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.BYTE, right)
DataType.BYTE -> Pair(DataType.BYTE, null)
DataType.UWORD -> Pair(DataType.WORD, left)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
}
}
DataType.UWORD -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.UWORD, right)
DataType.BYTE -> Pair(DataType.UWORD, right)
DataType.UWORD -> Pair(DataType.UWORD, null)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
}
}
DataType.WORD -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.WORD, right)
DataType.BYTE -> Pair(DataType.WORD, right)
DataType.UWORD -> Pair(DataType.WORD, right)
DataType.WORD -> Pair(DataType.WORD, null)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
}
}
DataType.FLOAT -> {
Pair(DataType.FLOAT, right)
}
else -> throw FatalAstException("non-numeric datatype $leftDt")
}
}
}
class ArrayIndexedExpression(val identifier: IdentifierReference,
var arrayspec: ArrayIndex,
override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.linkParents(this)
arrayspec.linkParents(this)
}
override fun constValue(program: Program): LiteralValue? = null
override fun process(processor: IAstProcessor): IExpression = processor.process(this)
override fun referencesIdentifier(name: String) = identifier.referencesIdentifier(name)
override fun inferType(program: Program): DataType? {
val target = identifier.targetStatement(program.namespace)
if (target is VarDecl) {
return when (target.datatype) {
in NumericDatatypes -> null
in StringDatatypes -> DataType.UBYTE
DataType.ARRAY_UB -> DataType.UBYTE
DataType.ARRAY_B -> DataType.BYTE
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
else -> throw FatalAstException("invalid dt")
}
}
return null
}
override fun toString(): String {
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
}
}
class TypecastExpression(var expression: IExpression, var type: DataType, val implicit: Boolean, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
expression.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
override fun inferType(program: Program): DataType? = type
override fun constValue(program: Program): LiteralValue? {
val cv = expression.constValue(program) ?: return null
val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
return LiteralValue.fromNumber(value.numericValue(), value.type, position)
}
override fun toString(): String {
return "Typecast($expression as $type)"
}
}
data class AddressOf(val identifier: IdentifierReference, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.parent=this
}
var scopedname: String? = null // will be set in a later state by the compiler
override fun constValue(program: Program): LiteralValue? = null
override fun referencesIdentifier(name: String) = false
override fun inferType(program: Program) = DataType.UWORD
override fun process(processor: IAstProcessor) = processor.process(this)
}
class DirectMemoryRead(var addressExpression: IExpression, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
this.addressExpression.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = false
override fun inferType(program: Program): DataType? = DataType.UBYTE
override fun constValue(program: Program): LiteralValue? = null
override fun toString(): String {
return "DirectMemoryRead($addressExpression)"
}
}
class DirectMemoryWrite(var addressExpression: IExpression, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
this.addressExpression.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = false
override fun inferType(program: Program): DataType? = DataType.UBYTE
override fun constValue(program: Program): LiteralValue? = null
override fun toString(): String {
return "DirectMemoryWrite($addressExpression)"
}
}
private data class NumericLiteral(val number: Number, val datatype: DataType)
open class LiteralValue(val type: DataType,
val bytevalue: Short? = null,
val wordvalue: Int? = null,
val floatvalue: Double? = null,
val strvalue: String? = null,
val arrayvalue: Array<IExpression>? = null,
initHeapId: Int? =null,
override val position: Position) : IExpression {
override lateinit var parent: Node
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false
val isString = type in StringDatatypes
val isNumeric = type in NumericDatatypes
val isArray = type in ArrayDatatypes
var heapId = initHeapId
private set
companion object {
fun fromBoolean(bool: Boolean, position: Position) =
LiteralValue(DataType.UBYTE, bytevalue = if(bool) 1 else 0, position=position)
fun fromNumber(value: Number, type: DataType, position: Position) : LiteralValue {
return when(type) {
in ByteDatatypes -> LiteralValue(type, bytevalue = value.toShort(), position = position)
in WordDatatypes -> LiteralValue(type, wordvalue = value.toInt(), position = position)
DataType.FLOAT -> LiteralValue(type, floatvalue = value.toDouble(), position = position)
else -> throw FatalAstException("non numeric datatype")
}
}
fun optimalNumeric(value: Number, position: Position): LiteralValue {
return if(value is Double) {
LiteralValue(DataType.FLOAT, floatvalue = value, position = position)
} else {
val intval = value.toInt()
when (intval) {
in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue=intval.toShort(), position = position)
in -128..127 -> LiteralValue(DataType.BYTE, bytevalue=intval.toShort(), position = position)
in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = intval, position = position)
in -32768..32767 -> LiteralValue(DataType.WORD, wordvalue = intval, position = position)
else -> LiteralValue(DataType.FLOAT, floatvalue = intval.toDouble(), position = position)
}
}
}
fun optimalInteger(value: Number, position: Position): LiteralValue {
val intval = value.toInt()
if(intval.toDouble() != value.toDouble())
throw FatalAstException("value is not an integer: $value")
return when (intval) {
in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue=value.toShort(), position = position)
in -128..127 -> LiteralValue(DataType.BYTE, bytevalue=value.toShort(), position = position)
in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = value.toInt(), position = position)
else -> throw FatalAstException("integer overflow: $value")
}
}
}
init {
when(type){
in ByteDatatypes -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
in WordDatatypes -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
in StringDatatypes ->
if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
in ArrayDatatypes ->
if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId")
else -> throw FatalAstException("invalid type $type")
}
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null)
throw FatalAstException("literal value without actual value")
}
val asNumericValue: Number? = when {
bytevalue!=null -> bytevalue
wordvalue!=null -> wordvalue
floatvalue!=null -> floatvalue
else -> null
}
val asIntegerValue: Int? = when {
bytevalue!=null -> bytevalue.toInt()
wordvalue!=null -> wordvalue
// don't round a float value, otherwise code will not detect that it's not an integer
else -> null
}
val asBooleanValue: Boolean =
(floatvalue!=null && floatvalue != 0.0) ||
(bytevalue!=null && bytevalue != 0.toShort()) ||
(wordvalue!=null && wordvalue != 0) ||
(strvalue!=null && strvalue.isNotEmpty()) ||
(arrayvalue != null && arrayvalue.isNotEmpty())
override fun linkParents(parent: Node) {
this.parent = parent
arrayvalue?.forEach {it.linkParents(this)}
}
override fun constValue(program: Program): LiteralValue? {
if(arrayvalue!=null) {
for(v in arrayvalue) {
if(v.constValue(program)==null) return null
}
}
return this
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
val vstr = when(type) {
DataType.UBYTE -> "ubyte:$bytevalue"
DataType.BYTE -> "byte:$bytevalue"
DataType.UWORD -> "uword:$wordvalue"
DataType.WORD -> "word:$wordvalue"
DataType.FLOAT -> "float:$floatvalue"
in StringDatatypes -> "str:$strvalue"
in ArrayDatatypes -> "array:$arrayvalue"
else -> throw FatalAstException("weird datatype")
}
return "LiteralValue($vstr)"
}
override fun inferType(program: Program) = type
override fun hashCode(): Int {
val bh = bytevalue?.hashCode() ?: 0x10001234
val wh = wordvalue?.hashCode() ?: 0x01002345
val fh = floatvalue?.hashCode() ?: 0x00103456
val sh = strvalue?.hashCode() ?: 0x00014567
val ah = arrayvalue?.hashCode() ?: 0x11119876
var hash = bh * 31 xor wh
hash = hash*31 xor fh
hash = hash*31 xor sh
hash = hash*31 xor ah
hash = hash*31 xor type.hashCode()
return hash
}
override fun equals(other: Any?): Boolean {
if(other==null || other !is LiteralValue)
return false
if(isNumeric && other.isNumeric)
return asNumericValue?.toDouble()==other.asNumericValue?.toDouble()
if(isArray && other.isArray)
return arrayvalue!!.contentEquals(other.arrayvalue!!) && heapId==other.heapId
if(isString && other.isString)
return strvalue==other.strvalue && heapId==other.heapId
if(type!=other.type)
return false
return compareTo(other) == 0
}
operator fun compareTo(other: LiteralValue): Int {
val numLeft = asNumericValue?.toDouble()
val numRight = other.asNumericValue?.toDouble()
if(numLeft!=null && numRight!=null)
return numLeft.compareTo(numRight)
if(strvalue!=null && other.strvalue!=null)
return strvalue.compareTo(other.strvalue)
throw ExpressionError("cannot order compare type $type with ${other.type}", other.position)
}
fun intoDatatype(targettype: DataType): LiteralValue? {
if(type==targettype)
return this
when(type) {
DataType.UBYTE -> {
if(targettype==DataType.BYTE && bytevalue!! <= 127)
return LiteralValue(targettype, bytevalue=bytevalue, position=position)
if(targettype==DataType.WORD || targettype==DataType.UWORD)
return LiteralValue(targettype, wordvalue=bytevalue!!.toInt(), position=position)
if(targettype==DataType.FLOAT)
return LiteralValue(targettype, floatvalue=bytevalue!!.toDouble(), position=position)
}
DataType.BYTE -> {
if(targettype==DataType.UBYTE && bytevalue!! >= 0)
return LiteralValue(targettype, bytevalue=bytevalue, position=position)
if(targettype==DataType.UWORD && bytevalue!! >= 0)
return LiteralValue(targettype, wordvalue=bytevalue.toInt(), position=position)
if(targettype==DataType.WORD)
return LiteralValue(targettype, wordvalue=bytevalue!!.toInt(), position=position)
if(targettype==DataType.FLOAT)
return LiteralValue(targettype, floatvalue=bytevalue!!.toDouble(), position=position)
}
DataType.UWORD -> {
if(targettype==DataType.BYTE && wordvalue!! <= 127)
return LiteralValue(targettype, bytevalue=wordvalue.toShort(), position=position)
if(targettype==DataType.UBYTE && wordvalue!! <= 255)
return LiteralValue(targettype, bytevalue=wordvalue.toShort(), position=position)
if(targettype==DataType.WORD && wordvalue!! <= 32767)
return LiteralValue(targettype, wordvalue=wordvalue, position=position)
if(targettype==DataType.FLOAT)
return LiteralValue(targettype, floatvalue=wordvalue!!.toDouble(), position=position)
}
DataType.WORD -> {
if(targettype==DataType.BYTE && wordvalue!! in -128..127)
return LiteralValue(targettype, bytevalue=wordvalue.toShort(), position=position)
if(targettype==DataType.UBYTE && wordvalue!! in 0..255)
return LiteralValue(targettype, bytevalue=wordvalue.toShort(), position=position)
if(targettype==DataType.UWORD && wordvalue!! >=0)
return LiteralValue(targettype, wordvalue=wordvalue, position=position)
if(targettype==DataType.FLOAT)
return LiteralValue(targettype, floatvalue=wordvalue!!.toDouble(), position=position)
}
DataType.FLOAT -> {
if(floor(floatvalue!!)==floatvalue) {
val value = floatvalue.toInt()
if (targettype == DataType.BYTE && value in -128..127)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.UBYTE && value in 0..255)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.WORD && value in -32768..32767)
return LiteralValue(targettype, wordvalue = value, position = position)
if (targettype == DataType.UWORD && value in 0..65535)
return LiteralValue(targettype, wordvalue = value, position = position)
}
}
in StringDatatypes -> {
if(targettype in StringDatatypes)
return this
}
else -> {}
}
return null // invalid type conversion from $this to $targettype
}
fun addToHeap(heap: HeapValues) {
if(heapId==null) {
if (strvalue != null) {
heapId = heap.addString(type, strvalue)
}
else if (arrayvalue!=null) {
if(arrayvalue.any {it is AddressOf}) {
val intArrayWithAddressOfs = arrayvalue.map {
when (it) {
is AddressOf -> IntegerOrAddressOf(null, it)
is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null)
else -> throw FatalAstException("invalid datatype in array")
}
}
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
} else {
val valuesInArray = arrayvalue.map { (it as LiteralValue).asNumericValue!! }
if(type==DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray()
heapId = heap.addDoublesArray(doubleArray)
} else {
val integerArray = valuesInArray.map { it.toInt() }
heapId = heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
}
}
}
}
}
}
class RangeExpr(var from: IExpression,
var to: IExpression,
var step: IExpression,
override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
from.linkParents(this)
to.linkParents(this)
step.linkParents(this)
}
override fun constValue(program: Program): LiteralValue? = null
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name)
override fun inferType(program: Program): DataType? {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
return when {
fromDt==null || toDt==null -> null
fromDt==DataType.UBYTE && toDt==DataType.UBYTE -> DataType.UBYTE
fromDt==DataType.UWORD && toDt==DataType.UWORD -> DataType.UWORD
fromDt==DataType.STR && toDt==DataType.STR -> DataType.STR
fromDt==DataType.STR_S && toDt==DataType.STR_S -> DataType.STR_S
fromDt==DataType.WORD || toDt==DataType.WORD -> DataType.WORD
fromDt==DataType.BYTE || toDt==DataType.BYTE -> DataType.BYTE
else -> DataType.UBYTE
}
}
override fun toString(): String {
return "RangeExpr(from $from, to $to, step $step, pos=$position)"
}
fun size(): Int? {
val fromLv = (from as? LiteralValue)
val toLv = (to as? LiteralValue)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange()?.count()
}
fun toConstantIntegerRange(): IntProgression? {
val fromLv = from as? LiteralValue
val toLv = to as? LiteralValue
if(fromLv==null || toLv==null)
return null // non-constant range
val fromVal: Int
val toVal: Int
if(fromLv.isString && toLv.isString) {
// string range -> int range over petscii values
fromVal = Petscii.encodePetscii(fromLv.strvalue!!, true)[0].toInt()
toVal = Petscii.encodePetscii(toLv.strvalue!!, true)[0].toInt()
} else {
// integer range
fromVal = (from as LiteralValue).asIntegerValue!!
toVal = (to as LiteralValue).asIntegerValue!!
}
val stepVal = (step as? LiteralValue)?.asIntegerValue ?: 1
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
}
class RegisterExpr(val register: Register, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun constValue(program: Program): LiteralValue? = null
override fun process(processor: IAstProcessor) = this
override fun referencesIdentifier(name: String): Boolean = false
override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)"
}
override fun inferType(program: Program) = DataType.UBYTE
}
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : IExpression {
override lateinit var parent: Node
fun targetStatement(namespace: INameScope) =
if(nameInSource.size==1 && nameInSource[0] in BuiltinFunctions)
BuiltinFunctionStatementPlaceholder(nameInSource[0], position)
else
namespace.lookup(nameInSource, this)
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun constValue(program: Program): LiteralValue? {
val node = program.namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl
if(vardecl==null) {
return null
} else if(vardecl.type!=VarDeclType.CONST) {
return null
}
return vardecl.value?.constValue(program)
}
override fun toString(): String {
return "IdentifierRef($nameInSource)"
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time?
override fun inferType(program: Program): DataType? {
val targetStmt = targetStatement(program.namespace)
if(targetStmt is VarDecl) {
return targetStmt.datatype
} else {
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
}
}
fun heapId(namespace: INameScope): Int {
val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this)
return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap: $this")
}
}
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
}
}
class Jump(val address: Int?,
val identifier: IdentifierReference?,
val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
identifier?.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
}
}
class FunctionCall(override var target: IdentifierReference,
override var arglist: MutableList<IExpression>,
override val position: Position) : IExpression, IFunctionCall {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
arglist.forEach { it.linkParents(this) }
}
override fun constValue(program: Program) = constValue(program, true)
private fun constValue(program: Program, withDatatypeCheck: Boolean): LiteralValue? {
// if the function is a built-in function and the args are consts, should try to const-evaluate!
// lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1) return null
try {
var resultValue: LiteralValue? = null
val func = BuiltinFunctions[target.nameInSource[0]]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null)
resultValue = exprfunc(arglist, position, program)
else if(func.returntype==null)
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
}
if(withDatatypeCheck) {
val resultDt = this.inferType(program)
if(resultValue==null || resultDt == resultValue.type)
return resultValue
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
} else {
return resultValue
}
}
catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
return null
}
}
override fun toString(): String {
return "FunctionCall(target=$target, pos=$position)"
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
override fun inferType(program: Program): DataType? {
val constVal = constValue(program ,false)
if(constVal!=null)
return constVal.type
val stmt = target.targetStatement(program.namespace) ?: return null
when (stmt) {
is BuiltinFunctionStatementPlaceholder -> {
if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" ||
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return null // these have no return value
}
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program)
}
is Subroutine -> {
if(stmt.returntypes.isEmpty())
return null // no return value
if(stmt.returntypes.size==1)
return stmt.returntypes[0]
return null // has multiple return types... so not a single resulting datatype possible
}
is Label -> return null
}
return null // calling something we don't recognise...
}
}
class FunctionCallStatement(override var target: IdentifierReference,
override var arglist: MutableList<IExpression>,
override val position: Position) : IStatement, IFunctionCall {
override lateinit var parent: Node
override val expensiveToInline
get() = arglist.any { it !is LiteralValue }
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
arglist.forEach { it.linkParents(this) }
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "FunctionCallStatement(target=$target, pos=$position)"
}
}
class InlineAssembly(val assembly: String, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean?)
class AnonymousScope(override var statements: MutableList<IStatement>,
override val position: Position) : INameScope, IStatement {
override val name: String
override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
init {
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++
}
companion object {
private var sequenceNumber = 1
}
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach { it.linkParents(this) }
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
class NopStatement(override val position:Position): IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun process(processor: IAstProcessor) = this
}
// the subroutine class covers both the normal user-defined subroutines,
// and also the predefined/ROM/register-based subroutines.
// (multiple return types can only occur for the latter type)
class Subroutine(override val name: String,
val parameters: List<SubroutineParameter>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<Register>,
val asmAddress: Int?,
val isAsmSubroutine: Boolean,
override var statements: MutableList<IStatement>,
override val position: Position) : IStatement, INameScope {
var keepAlways: Boolean = false
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
override lateinit var parent: Node
val calledBy = mutableListOf<Node>()
val calls = mutableSetOf<Subroutine>()
val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) {
this.parent = parent
parameters.forEach { it.linkParents(this) }
statements.forEach { it.linkParents(this) }
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
}
fun amountOfRtsInAsm(): Int = statements
.asSequence()
.filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
val canBeAsmSubroutine =false // TODO disabled for now, see below about problem with converting to asm subroutine
// !isAsmSubroutine
// && ((parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD))
// || (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE }))
fun intoAsmSubroutine(): Subroutine {
// TODO turn subroutine into asm calling convention. Requires rethinking of how parameters are handled (conflicts with local vardefs now, see AstIdentifierChecker...)
return this // TODO
// println("TO ASM $this") // TODO
// val paramregs = if (parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE))
// listOf(RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else if (parameters.size == 1 && parameters[0].type in setOf(DataType.WORD, DataType.UWORD))
// listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null))
// else if (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE })
// listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else throw FatalAstException("cannot convert subroutine to asm parameters")
//
// val asmsub=Subroutine(
// name,
// parameters,
// returntypes,
// paramregs,
// emptyList(),
// emptySet(),
// null,
// true,
// statements,
// position
// )
// asmsub.linkParents(parent)
// return asmsub
}
}
open class SubroutineParameter(val name: String,
val type: DataType,
override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
}
class IfStatement(var condition: IExpression,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) {
this.parent = parent
condition.linkParents(this)
truepart.linkParents(this)
elsepart.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
}
class BranchStatement(var condition: BranchCondition,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) {
this.parent = parent
truepart.linkParents(this)
elsepart.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
}
class ForLoop(val loopRegister: Register?,
val decltype: DataType?,
val zeropage: Boolean,
val loopVar: IdentifierReference?,
var iterable: IExpression,
var body: AnonymousScope,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent=parent
loopVar?.linkParents(if(decltype==null) this else body)
iterable.linkParents(this)
body.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
}
companion object {
const val iteratorLoopcounterVarname = "prog8forloopcounter"
}
}
class WhileLoop(var condition: IExpression,
var body: AnonymousScope,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
condition.linkParents(this)
body.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
}
class RepeatLoop(var body: AnonymousScope,
var untilCondition: IExpression,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
untilCondition.linkParents(this)
body.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
}
/***************** Antlr Extension methods to create AST ****************/
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source)
}
private fun ParserRuleContext.toPosition() : Position {
val customTokensource = this.start.tokenSource as? CustomLexer
val filename =
when {
customTokensource!=null -> customTokensource.modulePath.fileName.toString()
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
else -> File(start.inputStream.sourceName).name
}
// note: be ware of TAB characters in the source text, they count as 1 column...
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine+stop.text.length)
}
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : IStatement {
val directive = directive()?.toAst()
if(directive!=null) return directive
val block = block()?.toAst(isInLibrary)
if(block!=null) return block
throw FatalAstException(text)
}
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : IStatement =
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<IStatement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun prog8Parser.StatementContext.toAst() : IStatement {
vardecl()?.let {
return VarDecl(VarDeclType.VAR,
it.datatype().toAst(),
it.ZEROPAGE()!=null,
it.arrayindex()?.toAst(),
it.identifier().text,
null,
it.ARRAYSIG()!=null || it.arrayindex()!=null,
false,
it.toPosition())
}
varinitializer()?.let {
val vd = it.vardecl()
return VarDecl(VarDeclType.VAR,
vd.datatype().toAst(),
vd.ZEROPAGE()!=null,
vd.arrayindex()?.toAst(),
vd.identifier().text,
it.expression().toAst(),
vd.ARRAYSIG()!=null || vd.arrayindex()!=null,
false,
it.toPosition())
}
constdecl()?.let {
val cvarinit = it.varinitializer()
val vd = cvarinit.vardecl()
return VarDecl(VarDeclType.CONST,
vd.datatype().toAst(),
vd.ZEROPAGE()!=null,
vd.arrayindex()?.toAst(),
vd.identifier().text,
cvarinit.expression().toAst(),
vd.ARRAYSIG()!=null || vd.arrayindex()!=null,
false,
cvarinit.toPosition())
}
memoryvardecl()?.let {
val mvarinit = it.varinitializer()
val vd = mvarinit.vardecl()
return VarDecl(VarDeclType.MEMORY,
vd.datatype().toAst(),
vd.ZEROPAGE()!=null,
vd.arrayindex()?.toAst(),
vd.identifier().text,
mvarinit.expression().toAst(),
vd.ARRAYSIG()!=null || vd.arrayindex()!=null,
false,
mvarinit.toPosition())
}
assignment()?.let {
return Assignment(it.assign_targets().toAst(), null, it.expression().toAst(), it.toPosition())
}
augassignment()?.let {
return Assignment(listOf(it.assign_target().toAst()),
it.operator.text,
it.expression().toAst(),
it.toPosition())
}
postincrdecr()?.let {
return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition())
}
val directive = directive()?.toAst()
if(directive!=null) return directive
val label = labeldef()?.toAst()
if(label!=null) return label
val jump = unconditionaljump()?.toAst()
if(jump!=null) return jump
val fcall = functioncall_stmt()?.toAst()
if(fcall!=null) return fcall
val ifstmt = if_stmt()?.toAst()
if(ifstmt!=null) return ifstmt
val returnstmt = returnstmt()?.toAst()
if(returnstmt!=null) return returnstmt
val sub = subroutine()?.toAst()
if(sub!=null) return sub
val asm = inlineasm()?.toAst()
if(asm!=null) return asm
val branchstmt = branch_stmt()?.toAst()
if(branchstmt!=null) return branchstmt
val forloop = forloop()?.toAst()
if(forloop!=null) return forloop
val repeatloop = repeatloop()?.toAst()
if(repeatloop!=null) return repeatloop
val whileloop = whileloop()?.toAst()
if(whileloop!=null) return whileloop
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val asmsubstmt = asmsubroutine()?.toAst()
if(asmsubstmt!=null) return asmsubstmt
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun prog8Parser.Assign_targetsContext.toAst(): List<AssignTarget> = assign_target().map { it.toAst() }
private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement {
val name = identifier().text
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
val params = asmsub_params()?.toAst() ?: emptyList()
val returns = asmsub_returns()?.toAst() ?: emptyList()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalReturnvalues = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val clobbers = clobber()?.toAst() ?: emptySet()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(name, normalParameters, normalReturnvalues,
paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition())
}
private class AsmSubroutineParameter(name: String,
type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
val stack: Boolean,
position: Position) : SubroutineParameter(name, type, position)
private class AsmSubroutineReturn(val type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
val stack: Boolean,
val position: Position)
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
= this.register().asSequence().map { it.toAst() }.toSet()
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map { AsmSubroutineParameter(it.vardecl().identifier().text, it.vardecl().datatype().toAst(),
it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
private fun prog8Parser.Functioncall_stmtContext.toAst(): IStatement {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), toPosition())
else
FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCall(location, mutableListOf(), toPosition())
else
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun prog8Parser.InlineasmContext.toAst() =
InlineAssembly(INLINEASMBLOCK().text, toPosition())
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
val values = expression_list()
return Return(values?.toAst() ?: emptyList(), toPosition())
}
private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
val address = integerliteral()?.toAst()?.number?.toInt()
val identifier = scoped_identifier()?.toAst()
return Jump(address, identifier, null, toPosition())
}
private fun prog8Parser.LabeldefContext.toAst(): IStatement =
Label(children[0].text, toPosition())
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
sub_return_part()?.toAst() ?: emptyList(),
emptyList(),
emptyList(),
emptySet(),
null,
false,
statement_block()?.toAst() ?: mutableListOf(),
toPosition())
}
private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
val returns = sub_returns() ?: return emptyList()
return returns.datatype().map { it.toAst() }
}
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
SubroutineParameter(it.identifier().text, it.datatype().toAst(), it.toPosition())
}
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
val register = register()?.toAst()
val identifier = scoped_identifier()
return when {
register!=null -> AssignTarget(register, null, null, null, toPosition())
identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition())
arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition())
}
}
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
private fun prog8Parser.DirectiveContext.toAst() : Directive =
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg =
DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
val integer: Int
var datatype = DataType.UBYTE
when (radix) {
10 -> {
integer = try {
text.toInt()
} catch(x: NumberFormatException) {
throw AstException("${toPosition()} invalid decimal literal ${x.message}")
}
datatype = when(integer) {
in 0..255 -> DataType.UBYTE
in -128..127 -> DataType.BYTE
in 0..65535 -> DataType.UWORD
in -32768..32767 -> DataType.WORD
else -> DataType.FLOAT
}
}
2 -> {
if(text.length>8)
datatype = DataType.UWORD
try {
integer = text.toInt(2)
} catch(x: NumberFormatException) {
throw AstException("${toPosition()} invalid binary literal ${x.message}")
}
}
16 -> {
if(text.length>2)
datatype = DataType.UWORD
try {
integer = text.toInt(16)
} catch(x: NumberFormatException) {
throw AstException("${toPosition()} invalid hexadecimal literal ${x.message}")
}
}
else -> throw FatalAstException("invalid radix")
}
return NumericLiteral(integer, if(forceWord) DataType.UWORD else datatype)
}
val terminal: TerminalNode = children[0] as TerminalNode
val integerPart = this.intpart.text
return when (terminal.symbol.type) {
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null)
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null)
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null)
else -> throw FatalAstException(terminal.text)
}
}
private fun prog8Parser.ExpressionContext.toAst() : IExpression {
val litval = literalvalue()
if(litval!=null) {
val booleanlit = litval.booleanliteral()?.toAst()
return if(booleanlit!=null) {
LiteralValue.fromBoolean(booleanlit, litval.toPosition())
}
else {
val intLit = litval.integerliteral()?.toAst()
when {
intLit!=null -> when(intLit.datatype) {
DataType.UBYTE -> LiteralValue(DataType.UBYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue= intLit.number.toDouble(), position = litval.toPosition())
else -> throw FatalAstException("invalid datatype for numeric literal")
}
litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition())
litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
litval.charliteral()!=null -> {
try {
LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], position = litval.toPosition())
} catch (ce: CharConversionException) {
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
}
}
litval.arrayliteral()!=null -> {
val array = litval.arrayliteral()?.toAst()
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFolder takes care of that and converts the type if needed.
LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition())
}
else -> throw FatalAstException("invalid parsed literal")
}
}
}
if(register()!=null)
return RegisterExpr(register().toAst(), register().toPosition())
if(scoped_identifier()!=null)
return scoped_identifier().toAst()
if(bop!=null)
return BinaryExpression(left.toAst(), bop.text, right.toAst(), toPosition())
if(prefix!=null)
return PrefixExpression(prefix.text, expression(0).toAst(), toPosition())
val funcall = functioncall()?.toAst()
if(funcall!=null) return funcall
if (rangefrom!=null && rangeto!=null) {
val step = rangestep?.toAst() ?: LiteralValue(DataType.UBYTE, 1, position = toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
}
if(childCount==3 && children[0].text=="(" && children[2].text==")")
return expression(0).toAst() // expression within ( )
if(arrayindexed()!=null)
return arrayindexed().toAst()
if(typecast()!=null)
return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition())
if(directmemory()!=null)
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
throw FatalAstException(text)
}
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(),
arrayindex().toAst(),
toPosition())
}
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
IdentifierReference(listOf(text), toPosition())
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
IdentifierReference(NAME().map { it.text }, toPosition())
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
"true" -> true
"false" -> false
else -> throw FatalAstException(text)
}
private fun prog8Parser.ArrayliteralContext.toAst() : Array<IExpression> =
expression().map { it.toAst() }.toTypedArray()
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
val condition = expression().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition() ?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return IfStatement(condition, trueScope, elseScope, toPosition())
}
private fun prog8Parser.Else_partContext.toAst(): MutableList<IStatement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
}
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition() ?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return BranchStatement(branchcondition, trueScope, elseScope, toPosition())
}
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst()
val datatype = datatype()?.toAst()
val zeropage = ZEROPAGE()!=null
val loopvar = identifier()?.toAst()
val iterable = expression()!!.toAst()
val scope =
if(statement()!=null)
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
else
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
return ForLoop(loopregister, datatype, zeropage, loopvar, iterable, scope, toPosition())
}
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition() ?: statement().toPosition())
return WhileLoop(condition, scope, toPosition())
}
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
val untilCondition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition() ?: statement().toPosition())
return RepeatLoop(scope, untilCondition, toPosition())
}
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
internal fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>()
val iter = str.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {
result.add(c)
}
}
return result.joinToString("")
}