mirror of
https://github.com/irmen/prog8.git
synced 2024-06-10 20:29:33 +00:00
195 lines
11 KiB
Kotlin
195 lines
11 KiB
Kotlin
package prog8.compiler
|
|
|
|
import prog8.ast.Program
|
|
import prog8.ast.base.AstException
|
|
import prog8.ast.base.FatalAstException
|
|
import prog8.ast.base.SyntaxError
|
|
import prog8.ast.expressions.*
|
|
import prog8.ast.statements.VarDecl
|
|
import prog8.code.core.*
|
|
import kotlin.math.abs
|
|
import kotlin.math.sign
|
|
import kotlin.math.sqrt
|
|
|
|
|
|
private typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteral
|
|
|
|
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
|
|
|
|
class FSignature(val name: String,
|
|
val pure: Boolean, // does it have side effects?
|
|
val parameters: List<FParam>,
|
|
val returnType: DataType?,
|
|
val constExpressionFunc: ConstExpressionCaller? = null)
|
|
|
|
|
|
private val functionSignatures: List<FSignature> = listOf(
|
|
// this set of function have no return value and operate in-place:
|
|
FSignature("rol" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
|
FSignature("ror" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
|
FSignature("rol2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
|
FSignature("ror2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
|
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
|
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
|
// cmp returns a status in the carry flag, but not a proper return value
|
|
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
|
|
FSignature("abs" , true, listOf(FParam("value", IntegerDatatypesNoBool)), DataType.UWORD, ::builtinAbs),
|
|
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD, ::builtinLen),
|
|
// normal functions follow:
|
|
FSignature("sizeof" , true, listOf(FParam("object", DataType.values())), DataType.UBYTE, ::builtinSizeof),
|
|
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE, ::builtinSgn ),
|
|
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()) } },
|
|
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
|
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
|
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x and 255).toDouble() } },
|
|
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x ushr 8 and 255).toDouble()} },
|
|
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
|
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
|
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
|
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
|
FSignature("pokemon" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
|
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
|
FSignature("pop" , false, listOf(FParam("target", ByteDatatypes)), null),
|
|
FSignature("popw" , false, listOf(FParam("target", WordDatatypes)), null),
|
|
FSignature("push" , false, listOf(FParam("value", ByteDatatypes)), null),
|
|
FSignature("pushw" , false, listOf(FParam("value", WordDatatypes)), null),
|
|
FSignature("rsave" , false, emptyList(), null),
|
|
FSignature("rsavex" , false, emptyList(), null),
|
|
FSignature("rrestore" , false, emptyList(), null),
|
|
FSignature("rrestorex" , false, emptyList(), null),
|
|
FSignature("memory" , true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
|
|
FSignature("callfar" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
|
FSignature("callrom" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
|
)
|
|
|
|
val BuiltinFunctions = functionSignatures.associateBy { it.name }
|
|
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")
|
|
|
|
private fun builtinAny(array: List<Double>): Double = if(array.any { it!=0.0 }) 1.0 else 0.0
|
|
|
|
private fun builtinAll(array: List<Double>): Double = if(array.all { it!=0.0 }) 1.0 else 0.0
|
|
|
|
fun builtinFunctionReturnType(function: String): InferredTypes.InferredType {
|
|
if(function in arrayOf("set_carry", "set_irqd", "clear_carry", "clear_irqd"))
|
|
return InferredTypes.InferredType.void()
|
|
|
|
val func = BuiltinFunctions.getValue(function)
|
|
if(func.returnType==null)
|
|
return InferredTypes.InferredType.void()
|
|
return InferredTypes.knownFor(func.returnType)
|
|
}
|
|
|
|
|
|
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
|
class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
|
|
|
|
|
|
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Double): NumericLiteral {
|
|
if(args.size!=1)
|
|
throw SyntaxError("built-in function requires one integer argument", position)
|
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD)
|
|
throw SyntaxError("built-in function requires one integer argument", position)
|
|
|
|
val integer = constval.number.toInt()
|
|
return NumericLiteral.optimalInteger(function(integer).toInt(), args[0].position)
|
|
}
|
|
|
|
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Double>)->Double): NumericLiteral {
|
|
if(args.size!=1)
|
|
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
|
|
|
val array= args[0] as? ArrayLiteral ?: throw NotConstArgumentException()
|
|
val constElements = array.value.map{it.constValue(program)?.number}
|
|
if(constElements.contains(null))
|
|
throw NotConstArgumentException()
|
|
|
|
return NumericLiteral.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
|
|
}
|
|
|
|
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
|
// 1 arg, type = int, result type= uword
|
|
if(args.size!=1)
|
|
throw SyntaxError("abs requires one integer argument", position)
|
|
|
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
return when (constval.type) {
|
|
in IntegerDatatypesNoBool -> NumericLiteral.optimalInteger(abs(constval.number.toInt()), args[0].position)
|
|
else -> throw SyntaxError("abs requires one integer argument", position)
|
|
}
|
|
}
|
|
|
|
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
|
// 1 arg, type = anything, result type = ubyte
|
|
if(args.size!=1)
|
|
throw SyntaxError("sizeof requires one argument", position)
|
|
if(args[0] !is IdentifierReference)
|
|
throw SyntaxError("sizeof argument should be an identifier", position)
|
|
|
|
val dt = args[0].inferType(program)
|
|
if(dt.isKnown) {
|
|
val target = (args[0] as IdentifierReference).targetStatement(program)
|
|
?: throw CannotEvaluateException("sizeof", "no target")
|
|
|
|
return when {
|
|
dt.isArray -> {
|
|
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
|
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
|
|
NumericLiteral.optimalInteger(program.memsizer.memorySize(elementDt) * length, position)
|
|
}
|
|
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
|
else -> NumericLiteral(DataType.UBYTE, program.memsizer.memorySize(dt.getOr(DataType.UNDEFINED)).toDouble(), position)
|
|
}
|
|
} else {
|
|
throw SyntaxError("sizeof invalid argument type", position)
|
|
}
|
|
}
|
|
|
|
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
|
// note: in some cases the length is > 255, and then we have to return a UWORD type instead of a UBYTE.
|
|
if(args.size!=1)
|
|
throw SyntaxError("len requires one argument", position)
|
|
|
|
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
|
|
var arraySize = directMemVar?.arraysize?.constIndex()
|
|
if(arraySize != null)
|
|
return NumericLiteral.optimalInteger(arraySize, position)
|
|
if(args[0] is ArrayLiteral)
|
|
return NumericLiteral.optimalInteger((args[0] as ArrayLiteral).value.size, position)
|
|
if(args[0] !is IdentifierReference)
|
|
throw SyntaxError("len argument should be an identifier", position)
|
|
val target = (args[0] as IdentifierReference).targetVarDecl(program)
|
|
?: throw CannotEvaluateException("len", "no target vardecl")
|
|
|
|
return when(target.datatype) {
|
|
in ArrayDatatypes -> {
|
|
arraySize = target.arraysize?.constIndex()
|
|
if(arraySize==null)
|
|
throw CannotEvaluateException("len", "arraysize unknown")
|
|
NumericLiteral.optimalInteger(arraySize, args[0].position)
|
|
}
|
|
DataType.STR -> {
|
|
val refLv = target.value as? StringLiteral ?: throw CannotEvaluateException("len", "stringsize unknown")
|
|
NumericLiteral.optimalInteger(refLv.value.length, args[0].position)
|
|
}
|
|
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
|
|
else -> throw InternalCompilerException("weird datatype")
|
|
}
|
|
}
|
|
|
|
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
|
if (args.size != 2)
|
|
throw SyntaxError("mkword requires msb and lsb arguments", position)
|
|
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
|
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
|
|
return NumericLiteral(DataType.UWORD, result.toDouble(), position)
|
|
}
|
|
|
|
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
|
if (args.size != 1)
|
|
throw SyntaxError("sgn requires one argument", position)
|
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
return NumericLiteral(DataType.BYTE, constval.number.sign, position)
|
|
}
|