bunch of new builtin functions, const expression evaluation now also done for array literals

This commit is contained in:
Irmen de Jong 2018-09-04 23:37:21 +02:00
parent 76d07a2de8
commit d9865a4b97
11 changed files with 297 additions and 154 deletions

View File

@ -1,5 +1,5 @@
.wy-nav-content {
max-width: 900px;
max-width: 1000px;
}
/* override table width restrictions */

View File

@ -172,6 +172,10 @@ Note that the various keywords for the data type and variable type (``byte``, ``
cannot be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance.
.. todo::
matrix datatype
Variables that represent CPU hardware registers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -423,14 +427,14 @@ log10(x)
sqrt(x)
Square root.
max(x [, y, ...])
Maximum of the values x, y, ...
min(x [, y, ...])
Minumum of the values x, y, ...
round(x)
Rounds the floating point to an integer.
Rounds the floating point to the closest integer.
floor (x)
Rounds the floating point down to an integer towards minus infinity.
ceil(x)
Rounds the floating point up to an integer towards positive infinity.
rad(x)
Degrees to radians.
@ -438,47 +442,66 @@ rad(x)
deg(x)
Radians to degrees.
_lsl(x)
max(x)
Maximum of the values in the non-scalar (array or matrix) value x
min(x)
Minimum of the values in the non-scalar (array or matrix) value x
avg(x)
Average of the values in the non-scalar (array or matrix) value x
sum(x)
Sum of the values in the non-scalar (array or matrix) value x
len(x)
Number of values in the non-scalar (array or matrix) value x.
(This is different from the number of *bytes* in memory if the datatype isn't byte)
any(x)
1 ('true') if any of the values in the non-scalar (array or matrix) value x is 'true' (not zero), else 0 ('false')
all(x)
1 ('true') if all of the values in the non-scalar (array or matrix) value x are 'true' (not zero), else 0 ('false')
lsl(x)
Shift the bits in x (byte or word) one position to the left.
Bit 0 is set to 0 (and the highest bit is shifted into the status register's Carry flag)
Modifies in-place but also returns the new value.
_lsr(x)
lsr(x)
Shift the bits in x (byte or word) one position to the right.
The highest bit is set to 0 (and bit 0 is shifted into the status register's Carry flag)
Modifies in-place but also returns the new value.
_rol(x)
rol(x)
Rotate the bits in x (byte or word) one position to the left.
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
while the highest bit will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
_rol2(x)
rol2(x)
Like _rol but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
_ror(x)
ror(x)
Rotate the bits in x (byte or word) one position to the right.
This uses the CPU's rotate semantics: the highest bit will be set to the current value of the Carry flag,
while bit 0 will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
_ror2(x)
ror2(x)
Like _ror but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
_P_carry(bit)
P_carry(bit)
Set (or clear) the CPU status register Carry flag. No result value.
(translated into ``SEC`` or ``CLC`` cpu instruction)
_P_irqd(bit)
P_irqd(bit)
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction)
.. todo::
additional builtins such as: avg, sum, abs, round

View File

@ -127,7 +127,7 @@ The following 6502 CPU hardware registers are directly usable in program code (a
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
- ``AX``, ``AY``, ``XY`` surrogate 16-bit registers: LSB-order (lo/hi) combined register pairs
- the status register (P) carry flag and interrupt disable flag can be written via the ``_P_carry`` and ``_P_irqd`` builtin functions.
- the status register (P) carry flag and interrupt disable flag can be written via the ``P_carry`` and ``P_irqd`` builtin functions.
Subroutine Calling Conventions
------------------------------

View File

@ -5,6 +5,25 @@
~ main $c003 {
const word len1 = len([1,2,3,wa1, wa2, ws1, all1])
const word wa1 = abs(-999)
const byte wa2 = abs(-99)
const float wa3 = abs(-1.23456)
const float avg1 = avg([-1.23456, 99999])
const float sum1 = sum([-1.23456, 99999])
const word ws1 = floor(sum([1,2,3,4.9]))
const word ws2 = ceil(avg([1,2,3,4.9]))
const word ws3 = round(sum([1,2,3,4.9]))
const word any1 = any([0,0,0,0,0,22,0,0])
const word any2 = any([2+sin(2), 2])
const word all1 = all([0,0,0,0,0,22,0,0])
const word all2 = all([0.0])
const word all3 = all([wa1, wa2, ws1, all1])
const word max1 = max([-1,-2,3,99+22])
const word min1 = min([1,2,3,99+22])
A = X>2
X = Y>Y
@ -15,12 +34,12 @@
str ascending5 = "z" to "z"
const byte cc = 4 + (2==9)
byte cc2 = 4 - (2==10)
memory byte derp = max($ffdd)
memory byte derp = max([$ffdd])
memory byte derpA = abs(-2.5-0.5)
memory byte derpB = max(1, 2.2, 4.4, 100)
memory byte cderp = min($ffdd)+ (1/1)
memory byte cderpA = min($ffdd, 10, 20, 30)
memory byte cderpB = min(1, 2.2, 4.4, 100)
memory byte derpB = max([1, 2.2, 4.4, 100])
memory byte cderp = min([$ffdd])+ (1/1)
memory byte cderpA = min([$ffdd, 10, 20, 30])
memory byte cderpB = min([1, 2.2, 4.4, 100])
memory byte derp2 = 2+$ffdd+round(10*sin(3))
const byte hopla=55-33
const byte hopla3=100+(-hopla)
@ -47,8 +66,8 @@
byte equalQQ = 4==4
const byte equalQQ2 = (4+hopla)>0
_P_carry(1)
_P_irqd(0)
P_carry(1)
P_irqd(0)
equalQQ = foo(33)
equalQQ = main.foo(33)
@ -73,16 +92,16 @@
if(6==6) {
A=sin(X)
X=max(1,2,Y)
X=min(1,2,Y)
X=_lsl(1)
X=_lsl(Y)
_P_carry(0)
_P_carry(Y) ; TODO error
_P_carry(9.99) ; TODO error
_P_irqd(0)
_P_irqd(Y) ; TODO error
_P_irqd(9.99) ; TODO error
X=max([1,2,Y])
X=min([1,2,Y])
X=lsl(1)
X=lsl(Y)
P_carry(0)
P_carry(Y) ; TODO error
P_carry(9.99) ; TODO error
P_irqd(0)
P_irqd(Y) ; TODO error
P_irqd(9.99) ; TODO error
} else X=33
if(6>36) {

View File

@ -21,11 +21,12 @@ fun main(args: Array<String>) {
exitProcess(1)
}
val startTime = System.currentTimeMillis()
val filepath = Paths.get(args[0]).normalize()
val moduleAst = importModule(filepath)
moduleAst.linkParents()
val globalNamespace = moduleAst.namespace()
//globalNamespace.debugPrint()
val globalNamespace = moduleAst.definingScope()
// globalNamespace.debugPrint()
// perform syntax checks and optimizations
@ -35,7 +36,7 @@ fun main(args: Array<String>) {
moduleAst.checkValid(globalNamespace) // check if tree is valid
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers()
moduleAst.optimizeStatements(globalNamespace, allScopedSymbolDefinitions)
val globalNamespaceAfterOptimize = moduleAst.namespace() // it could have changed in the meantime
val globalNamespaceAfterOptimize = moduleAst.definingScope() // it could have changed in the meantime
moduleAst.checkValid(globalNamespaceAfterOptimize) // check if final tree is valid
moduleAst.checkRecursion() // check if there are recursive subroutine calls
@ -67,7 +68,10 @@ fun main(args: Array<String>) {
// val assembler = intermediate.compileToAssembly()
// assembler.assemble(compilerOptions, "input", "output")
// val monitorfile = assembler.generateBreakpointList()
//
val endTime = System.currentTimeMillis()
println("Compilation time: ${(endTime-startTime)/1000.0} sec.")
// // start the vice emulator
// val program = "foo"
// val cmdline = listOf("x64", "-moncommands", monitorfile,

View File

@ -161,6 +161,10 @@ interface IAstProcessor {
fun process(label: Label): IStatement {
return label
}
fun process(literalValue: LiteralValue): LiteralValue {
return literalValue
}
}
@ -173,7 +177,7 @@ interface Node {
if(scope!=null) {
return scope
}
if(this is Label && this.name.startsWith("pseudo::")) {
if(this is Label && this.name.startsWith("builtin::")) {
return BuiltinFunctionScopePlaceholder
}
throw FatalAstException("scope missing from $this")
@ -248,7 +252,7 @@ interface INameScope {
} else {
// unqualified name, find the scope the statement is in, look in that first
var statementScope = statement
while(true) {
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope()
val result = localScope.labelsAndVariables()[scopedName[0]]
if (result != null)
@ -259,6 +263,7 @@ interface INameScope {
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
}
}
@ -316,6 +321,14 @@ object BuiltinFunctionScopePlaceholder : INameScope {
override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes")
}
object BuiltinFunctionStatementPlaceholder : IStatement {
override var position: Position? = null
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
override fun process(processor: IAstProcessor): IStatement = this
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
}
class Module(override val name: String,
override var statements: MutableList<IStatement>) : Node, INameScope {
override var position: Position? = null
@ -334,47 +347,49 @@ class Module(override val name: String,
processor.process(this)
}
fun namespace(): INameScope {
class GlobalNamespace(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position?) : INameScope {
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main") // main is always used
override fun usedNames(): Set<String> = scopedNamesUsed
override fun lookup(scopedName: List<String>, statement: Node): IStatement? {
if(BuiltinFunctionNames.contains(scopedName.last())) {
// pseudo functions always exist, return a dummy statement for them
val pseudo = Label("pseudo::${scopedName.last()}")
pseudo.position = statement.position
pseudo.parent = ParentSentinel
return pseudo
}
val stmt = super.lookup(scopedName, statement)
if(stmt!=null) {
val targetScopedName = when(stmt) {
is Label -> stmt.scopedname
is VarDecl -> stmt.scopedname
is Block -> stmt.scopedname
is Subroutine -> stmt.scopedname
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
}
registerUsedName(targetScopedName.joinToString("."))
}
return stmt
}
override fun registerUsedName(name: String) {
scopedNamesUsed.add(name)
}
}
return GlobalNamespace("<<<global>>>", statements, position)
private val theGlobalNamespace by lazy {
GlobalNamespace("<<<global>>>", statements, position)
}
override fun definingScope(): INameScope = theGlobalNamespace
override fun usedNames(): Set<String> = throw NotImplementedError("not implemented on sub-scopes")
override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes")
private class GlobalNamespace(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position?) : INameScope {
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main") // main is always used
override fun usedNames(): Set<String> = scopedNamesUsed
override fun lookup(scopedName: List<String>, statement: Node): IStatement? {
if(BuiltinFunctionNames.contains(scopedName.last())) {
// builtin functions always exist, return a dummy statement for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}")
builtinPlaceholder.position = statement.position
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
val stmt = super.lookup(scopedName, statement)
if(stmt!=null) {
val targetScopedName = when(stmt) {
is Label -> stmt.scopedname
is VarDecl -> stmt.scopedname
is Block -> stmt.scopedname
is Subroutine -> stmt.scopedname
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
}
registerUsedName(targetScopedName.joinToString("."))
}
return stmt
}
override fun registerUsedName(name: String) {
scopedNamesUsed.add(name)
}
}
}
@ -575,7 +590,7 @@ class BinaryExpression(var left: IExpression, val operator: String, var right: I
}
override fun constValue(namespace: INameScope): LiteralValue? {
throw ExpressionException("expression should have been optimized away into a single value, before const value was requested (this error is often caused by another)", position)
throw FatalAstException("binary expression should have been optimized away into a single value, before const value was requested (this error is often caused by another) pos=$position")
}
override fun process(processor: IAstProcessor) = processor.process(this)
@ -585,7 +600,7 @@ class BinaryExpression(var left: IExpression, val operator: String, var right: I
class LiteralValue(val intvalue: Int? = null,
val floatvalue: Double? = null,
val strvalue: String? = null,
val arrayvalue: List<IExpression>? = null) : IExpression {
val arrayvalue: MutableList<IExpression>? = null) : IExpression {
override var position: Position? = null
override lateinit var parent: Node
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false
@ -596,7 +611,7 @@ class LiteralValue(val intvalue: Int? = null,
floatvalue!=null -> floatvalue.toInt()
else -> {
if((strvalue!=null || arrayvalue!=null) && errorIfNotNumeric)
throw AstException("attempt to get int value from non-integer $this")
throw AstException("attempt to get int value from non-numeric $this")
else null
}
}
@ -608,7 +623,7 @@ class LiteralValue(val intvalue: Int? = null,
intvalue!=null -> intvalue.toDouble()
else -> {
if((strvalue!=null || arrayvalue!=null) && errorIfNotNumeric)
throw AstException("attempt to get float value from non-integer $this")
throw AstException("attempt to get float value from non-numeric $this")
else null
}
}
@ -626,7 +641,11 @@ class LiteralValue(val intvalue: Int? = null,
}
override fun constValue(namespace: INameScope): LiteralValue? = this
override fun process(processor: IAstProcessor) = this
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "LiteralValue(int=$intvalue, float=$floatvalue, str=$strvalue, array=$arrayvalue pos=$position)"
}
}
@ -751,14 +770,21 @@ class FunctionCall(override var target: IdentifierReference, override var arglis
"round" -> builtinRound(arglist, position, namespace)
"rad" -> builtinRad(arglist, position, namespace)
"deg" -> builtinDeg(arglist, position, namespace)
"_lsl" -> builtinLsl(arglist, position, namespace)
"_lsr" -> builtinLsr(arglist, position, namespace)
"_rol" -> throw ExpressionException("builtin function _rol can't be used in expressions because it doesn't return a value", position)
"_rol2" -> throw ExpressionException("builtin function _rol2 can't be used in expressions because it doesn't return a value", position)
"_ror" -> throw ExpressionException("builtin function _ror can't be used in expressions because it doesn't return a value", position)
"_ror2" -> throw ExpressionException("builtin function _ror2 can't be used in expressions because it doesn't return a value", position)
"_P_carry" -> throw ExpressionException("builtin function _P_carry can't be used in expressions because it doesn't return a value", position)
"_P_irqd" -> throw ExpressionException("builtin function _P_irqd can't be used in expressions because it doesn't return a value", position)
"sum" -> builtinSum(arglist, position, namespace)
"avg" -> builtinAvg(arglist, position, namespace)
"len" -> builtinLen(arglist, position, namespace)
"any" -> builtinAny(arglist, position, namespace)
"all" -> builtinAll(arglist, position, namespace)
"floor" -> builtinFloor(arglist, position, namespace)
"ceil" -> builtinCeil(arglist, position, namespace)
"lsl" -> builtinLsl(arglist, position, namespace)
"lsr" -> builtinLsr(arglist, position, namespace)
"rol" -> throw ExpressionException("builtin function _rol can't be used in expressions because it doesn't return a value", position)
"rol2" -> throw ExpressionException("builtin function _rol2 can't be used in expressions because it doesn't return a value", position)
"ror" -> throw ExpressionException("builtin function _ror can't be used in expressions because it doesn't return a value", position)
"ror2" -> throw ExpressionException("builtin function _ror2 can't be used in expressions because it doesn't return a value", position)
"P_carry" -> throw ExpressionException("builtin function _P_carry can't be used in expressions because it doesn't return a value", position)
"P_irqd" -> throw ExpressionException("builtin function _P_irqd can't be used in expressions because it doesn't return a value", position)
else -> null
}
}
@ -1252,7 +1278,7 @@ private fun il65Parser.BooleanliteralContext.toAst() = when(text) {
private fun il65Parser.ArrayliteralContext.toAst(withPosition: Boolean) =
expression().map { it.toAst(withPosition) }
expression().map { it.toAst(withPosition) }.toMutableList()
private fun il65Parser.If_stmtContext.toAst(withPosition: Boolean): IfStatement {

View File

@ -20,8 +20,8 @@ fun Module.checkValid(globalNamespace: INameScope) {
/**
* todo check subroutine parameters against signature
* todo check subroutine return values against target assignment values
* todo check subroutine call parameters against signature
* todo check subroutine return values against the call's result assignments
*/
class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
@ -125,11 +125,12 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
VarDeclType.VAR, VarDeclType.CONST -> {
when {
decl.value == null ->
err("need a compile-time constant initializer value")
err("var/const declaration needs a compile-time constant initializer value")
decl.value !is LiteralValue ->
err("need a compile-time constant initializer value, found: ${decl.value!!::class.simpleName}")
err("var/const declaration needs a compile-time constant initializer value, found: ${decl.value!!::class.simpleName}")
decl.isScalar -> {
checkConstInitializerValueScalar(decl)
checkValueType(decl, decl.value as LiteralValue, decl.position)
checkValueRange(decl.datatype, decl.value as LiteralValue, decl.position)
}
decl.isArray || decl.isMatrix -> {
@ -298,10 +299,10 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
override fun process(functionCall: FunctionCall): IExpression {
// this function call is (part of) an expression, which should be in a statement somewhere.
val statementNode = findParentNode<IStatement>(functionCall)
val stmtOfExpression = findParentNode<IStatement>(functionCall)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCall.position}")
val targetStatement = checkFunctionOrLabelExists(functionCall.target, statementNode)
val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression)
if(targetStatement!=null)
functionCall.targetStatement = targetStatement // link to the actual target statement
return super.process(functionCall)
@ -315,6 +316,8 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? {
if(target.nameInSource.size==1 && BuiltinFunctionNames.contains(target.nameInSource[0]))
return BuiltinFunctionStatementPlaceholder
val targetStatement = globalNamespace.lookup(target.nameInSource, statement)
if(targetStatement is Label || targetStatement is Subroutine)
return targetStatement
@ -331,17 +334,17 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
DataType.FLOAT -> {
val number = value.asFloat(false)
if (number!=null && (number > 1.7014118345e+38 || number < -1.7014118345e+38))
return err("floating point value out of range for MFLPT format")
return err("floating point value '$number' out of range for MFLPT format")
}
DataType.BYTE -> {
val number = value.asInt(false)
if (number!=null && (number < 0 || number > 255))
return err("value out of range for unsigned byte")
return err("value '$number' out of range for unsigned byte")
}
DataType.WORD -> {
val number = value.asInt(false)
if (number!=null && (number < 0 || number > 65535))
return err("value out of range for unsigned word")
return err("value '$number' out of range for unsigned word")
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val str = value.strvalue
@ -352,6 +355,37 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
return true
}
private fun checkValueType(vardecl: VarDecl, value: LiteralValue, position: Position?) : Boolean {
fun err(msg: String) : Boolean {
checkResult.add(SyntaxError(msg, position))
return false
}
when {
vardecl.isScalar -> when (vardecl.datatype) {
DataType.FLOAT -> {
if (value.floatvalue == null)
return err("floating point value expected")
}
DataType.BYTE -> {
if (value.intvalue == null)
return err("byte integer value expected")
}
DataType.WORD -> {
if (value.intvalue == null)
return err("word integer value expected")
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
if (value.strvalue == null)
return err("string value expected")
}
}
vardecl.isArray -> if(value.arrayvalue==null)
return err("array value expected")
vardecl.isMatrix -> TODO()
}
return true
}
private fun checkConstInitializerValueScalar(decl: VarDecl) {
fun err(msg: String) {
checkResult.add(SyntaxError(msg, decl.position))

View File

@ -21,10 +21,10 @@ fun Module.checkIdentifiers(): MutableMap<String, IStatement> {
val BuiltinFunctionNames = setOf(
"_P_carry", "_P_irqd", "_rol", "_ror", "_rol2", "_ror2",
"_lsl", "_lsr", "sin", "cos", "abs", "acos",
"asin", "tan", "atan", "log", "log10", "sqrt",
"max", "min", "round", "rad", "deg")
"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")
class AstIdentifiersChecker : IAstProcessor {
@ -65,7 +65,7 @@ class AstIdentifiersChecker : IAstProcessor {
override fun process(subroutine: Subroutine): IStatement {
if(BuiltinFunctionNames.contains(subroutine.name)) {
// the special pseudo-functions can't be redefined
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
} else {
val scopedName = subroutine.scopedname.joinToString(".")
@ -81,7 +81,7 @@ class AstIdentifiersChecker : IAstProcessor {
override fun process(label: Label): IStatement {
if(BuiltinFunctionNames.contains(label.name)) {
// the special pseudo-functions can't be redefined
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position))
} else {
val scopedName = label.scopedname.joinToString(".")

View File

@ -3,22 +3,22 @@ package il65.functions
import il65.ast.*
val BuiltIns = listOf("sin", "cos", "abs", "acos", "asin", "tan", "atan", "log", "log10", "sqrt", "max", "min", "round", "rad", "deg")
// @todo additional builtins such as: avg, sum, abs, round
val BuiltIns = listOf(
"sin", "cos", "abs", "acos", "asin", "tan", "atan", "log", "log10",
"sqrt", "max", "min", "round", "rad", "deg", "avg", "sum"
)
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)->Double): LiteralValue {
private fun oneDoubleArg(args: List<IExpression>, position: Position?, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val float = args[0].constValue(namespace)?.asFloat()
if(float!=null) {
val result = LiteralValue(floatvalue = function(float))
val result = intOrFloatLiteral(function(float).toDouble(), args[0].position)
result.position = args[0].position
return result
}
@ -26,13 +26,13 @@ private fun oneDoubleArg(args: List<IExpression>, position: Position?, namespace
throw NotConstArgumentException()
}
private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position?, namespace:INameScope, function: (arg: Double)->Int): LiteralValue {
private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position?, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val float = args[0].constValue(namespace)?.asFloat()
if(float!=null) {
val result = LiteralValue(intvalue = function(float))
val result = LiteralValue(function(float).toInt())
result.position = args[0].position
return result
}
@ -40,13 +40,13 @@ private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position?,
throw NotConstArgumentException()
}
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position?, namespace:INameScope, function: (arg: Int)->Int): LiteralValue {
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position?, namespace:INameScope, function: (arg: Int)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position)
val integer = args[0].constValue(namespace)?.asInt()
if(integer!=null) {
val result = LiteralValue(intvalue = function(integer))
val result = LiteralValue(function(integer).toInt())
result.position = args[0].position
return result
}
@ -54,8 +54,42 @@ private fun oneIntArgOutputInt(args: List<IExpression>, position: Position?, nam
throw NotConstArgumentException()
}
private fun nonScalarArgOutputNumber(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) }
if(constants.contains(null))
throw NotConstArgumentException()
val result = function(constants.map { it?.asFloat()!! }).toDouble()
return intOrFloatLiteral(result, args[0].position)
}
private fun nonScalarArgOutputBoolean(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) }
if(constants.contains(null))
throw NotConstArgumentException()
val result = function(constants.map { it?.asFloat()!! })
return LiteralValue(if(result) 1 else 0)
}
fun builtinRound(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace) { it -> Math.round(it).toInt() }
= 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)
fun builtinSin(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::sin)
@ -90,41 +124,35 @@ fun builtinRad(args: List<IExpression>, position: Position?, namespace:INameScop
fun builtinDeg(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::toDegrees)
fun builtinAbs(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function abs requires one numeric argument", position)
val float = args[0].constValue(namespace)?.asFloat()
if(float!=null)
return intOrFloatLiteral(Math.abs(float), args[0].position)
else
throw SyntaxError("built-in function abs requires floating point value as argument", position)
}
fun builtinAbs(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::abs)
fun builtinMax(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue {
if(args.isEmpty())
throw SyntaxError("max requires at least one argument", position)
val constants = args.map { it.constValue(namespace) }
if(constants.contains(null))
throw NotConstArgumentException()
val result = constants.map { it?.asFloat()!! }.max()
return intOrFloatLiteral(result!!, args[0].position)
}
fun builtinLsl(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 }
fun builtinMin(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue {
if(args.isEmpty())
throw SyntaxError("min requires at least one argument", position)
val constants = args.map { it.constValue(namespace) }
if(constants.contains(null))
throw NotConstArgumentException()
val result = constants.map { it?.asFloat()!! }.min()
return intOrFloatLiteral(result!!, args[0].position)
}
fun builtinLsr(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 1 }
fun builtinLsl(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue =
oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 }
fun builtinMin(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= nonScalarArgOutputNumber(args, position, namespace) { it.min()!! }
fun builtinLsr(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue =
oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 1 }
fun builtinMax(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= nonScalarArgOutputNumber(args, position, namespace) { it.max()!! }
fun builtinSum(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= nonScalarArgOutputNumber(args, position, namespace) { it.sum() }
fun builtinAvg(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= nonScalarArgOutputNumber(args, position, namespace) { it.average() }
fun builtinLen(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= nonScalarArgOutputNumber(args, position, namespace) { it.size }
fun builtinAny(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= nonScalarArgOutputBoolean(args, position, namespace) { it.any { v -> v != 0.0} }
fun builtinAll(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= nonScalarArgOutputBoolean(args, position, namespace) { it.all { v -> v != 0.0} }
private fun intOrFloatLiteral(value: Double, position: Position?): LiteralValue {

View File

@ -48,7 +48,7 @@ fun Module.optimizeExpressions(globalNamespace: INameScope) {
todo expression optimization: reduce expression nesting / flattening of parenthesis
todo expression optimization: simplify logical expression when a term makes it always true or false (1 or 0)
todo expression optimization: optimize some simple multiplications into shifts (A*8 -> A<<3, A/4 -> A>>2)
to
todo expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call)
*/
class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcessor {
@ -187,7 +187,7 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
v.position = range.position
v.parent = range.parent
v
})
}.toMutableList())
}
from.strvalue != null && to.strvalue != null -> {
// char range
@ -208,6 +208,15 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
range
}
}
override fun process(literalValue: LiteralValue): LiteralValue {
if(literalValue.arrayvalue!=null) {
val newArray = literalValue.arrayvalue.map { it.process(this) }
literalValue.arrayvalue.clear()
literalValue.arrayvalue.addAll(newArray)
}
return super.process(literalValue)
}
}

View File

@ -462,7 +462,7 @@ sub float_sub_SW1_from_XY (mflt: XY) -> (?) {
sub clear_screen (char: A, color: Y) -> () {
; ---- clear the character screen with the given fill character and character color.
; (assumes screen is at $0400, could be altered in the future with self-modifying code)
; @todo X = SCREEN ADDR HI BYTE
; @todo some byte var to set the SCREEN ADDR HI BYTE
%asm {{
sta _loop + 1 ; self-modifying