prog8/il65/src/il65/functions/BuiltinFunctions.kt

208 lines
10 KiB
Kotlin
Raw Normal View History

2018-08-13 21:28:04 +00:00
package il65.functions
2018-08-16 13:09:24 +00:00
import il65.ast.*
import kotlin.math.abs
import kotlin.math.floor
2018-08-13 21:28:04 +00:00
2018-08-16 14:22:51 +00:00
val BuiltinFunctionNames = setOf(
"P_carry", "P_irqd", "rol", "ror", "rol2", "ror2", "lsl", "lsr",
"sin", "cos", "abs", "acos", "asin", "tan", "atan",
"log", "log10", "sqrt", "rad", "deg", "round", "floor", "ceil",
"max", "min", "avg", "sum", "len", "any", "all", "lsb", "msb")
2018-09-13 23:24:12 +00:00
val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf("P_carry", "P_irqd")
2018-09-02 09:54:42 +00:00
class NotConstArgumentException: AstException("not a const argument to a built-in function")
private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
2018-08-13 21:28:04 +00:00
if(args.size!=1)
2018-08-16 13:09:24 +00:00
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
if(constval.type!=DataType.FLOAT)
throw SyntaxError("built-in function requires one floating point argument", position)
2018-08-13 21:28:04 +00:00
val float = constval.asNumericValue?.toDouble()!!
return numericLiteral(function(float), args[0].position)
2018-08-13 21:28:04 +00:00
}
private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
2018-08-13 21:28:04 +00:00
if(args.size!=1)
2018-08-16 13:09:24 +00:00
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
if(constval.type!=DataType.FLOAT)
throw SyntaxError("built-in function requires one floating point argument", position)
2018-08-13 21:28:04 +00:00
val float = constval.asNumericValue?.toDouble()!!
return numericLiteral(function(float).toInt(), args[0].position)
2018-08-13 21:28:04 +00:00
}
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, function: (arg: Int)->Number): LiteralValue {
2018-09-02 09:54:42 +00:00
if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
if(constval.type!=DataType.BYTE && constval.type!=DataType.WORD)
throw SyntaxError("built-in function requires one integer argument", position)
2018-08-13 21:28:04 +00:00
val integer = constval.asNumericValue?.toInt()!!
return numericLiteral(function(integer).toInt(), args[0].position)
2018-09-02 09:54:42 +00:00
}
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position, namespace:INameScope,
function: (arg: Collection<Double>)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace)
if(iterable?.arrayvalue == null)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
if(constants.contains(null))
throw NotConstArgumentException()
val result = function(constants.map { it!!.toDouble() }).toDouble()
return numericLiteral(result, args[0].position)
}
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position, namespace:INameScope,
function: (arg: Collection<Double>)->Boolean): LiteralValue {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace)
if(iterable?.arrayvalue == null)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
if(constants.contains(null))
throw NotConstArgumentException()
val result = function(constants.map { it?.toDouble()!! })
return LiteralValue.fromBoolean(result, position)
}
fun builtinRound(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace, Math::round)
fun builtinFloor(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace, Math::floor)
fun builtinCeil(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace, Math::ceil)
2018-08-14 10:00:45 +00:00
fun builtinSin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::sin)
2018-08-14 10:00:45 +00:00
fun builtinCos(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::cos)
2018-08-14 10:00:45 +00:00
fun builtinAcos(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::acos)
2018-08-14 10:00:45 +00:00
fun builtinAsin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::asin)
2018-08-14 10:00:45 +00:00
fun builtinTan(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::tan)
2018-08-14 10:00:45 +00:00
fun builtinAtan(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::atan)
2018-08-14 10:00:45 +00:00
fun builtinLog(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::log)
2018-08-14 10:00:45 +00:00
fun builtinLog10(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::log10)
2018-08-14 10:00:45 +00:00
fun builtinSqrt(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::sqrt)
2018-08-14 10:00:45 +00:00
fun builtinRad(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::toRadians)
2018-08-14 10:00:45 +00:00
fun builtinDeg(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
2018-08-16 13:09:24 +00:00
= oneDoubleArg(args, position, namespace, Math::toDegrees)
2018-08-13 23:15:11 +00:00
fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
// 1 arg, type = float or int, result type= same as argument type
if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
val number = constval.asNumericValue
return when (number) {
is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position)
is Double -> numericLiteral(abs(number.toDouble()), args[0].position)
else -> throw SyntaxError("abs requires one numeric argument", position)
}
}
2018-08-13 23:15:11 +00:00
// todo different functions for byte/word params/results?
fun builtinLsb(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x and 255 }
fun builtinMsb(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 8 and 255}
fun builtinLsl(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 }
2018-08-13 23:15:11 +00:00
fun builtinLsr(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 1 }
fun builtinMin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputNumber(args, position, namespace) { it.min()!! }
fun builtinMax(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputNumber(args, position, namespace) { it.max()!! }
fun builtinSum(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputNumber(args, position, namespace) { it.sum() }
fun builtinAvg(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
if(args.size!=1)
throw SyntaxError("avg requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace)
if(iterable?.arrayvalue == null)
throw SyntaxError("avg requires one non-scalar argument", position)
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
if(constants.contains(null))
throw NotConstArgumentException()
val result = (constants.map { it!!.toDouble() }).average()
return numericLiteral(result, args[0].position)
}
fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
val argument = args[0].constValue(namespace) ?: throw NotConstArgumentException()
return when(argument.type) {
DataType.ARRAY, DataType.ARRAY_W -> numericLiteral(argument.arrayvalue!!.size, args[0].position)
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> numericLiteral(argument.strvalue!!.length, args[0].position)
else -> throw FatalAstException("len of weird argument ${args[0]}")
}
}
2018-08-13 23:15:11 +00:00
fun builtinAny(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputBoolean(args, position, namespace) { it.any { v -> v != 0.0} }
2018-09-02 09:54:42 +00:00
fun builtinAll(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputBoolean(args, position, namespace) { it.all { v -> v != 0.0} }
2018-09-02 09:54:42 +00:00
2018-08-13 23:15:11 +00:00
private fun numericLiteral(value: Number, position: Position): LiteralValue {
val floatNum=value.toDouble()
val tweakedValue: Number =
if(floatNum==floor(floatNum) && floatNum in -32768..65535)
floatNum.toInt() // we have an integer disguised as a float.
else
floatNum
return when(tweakedValue) {
is Int -> LiteralValue.optimalNumeric(value.toInt(), position)
is Short -> LiteralValue.optimalNumeric(value.toInt(), position)
is Byte -> LiteralValue(DataType.BYTE, bytevalue = value.toShort(), position = position)
is Double -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
is Float -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
else -> throw FatalAstException("invalid number type ${value::class}")
}
}