diff --git a/docs/source/_static/css/customize.css b/docs/source/_static/css/customize.css index 69a046e35..d4c768a5a 100644 --- a/docs/source/_static/css/customize.css +++ b/docs/source/_static/css/customize.css @@ -1,5 +1,5 @@ .wy-nav-content { - max-width: 900px; + max-width: 1000px; } /* override table width restrictions */ diff --git a/docs/source/programming.rst b/docs/source/programming.rst index c251b7cd7..2029f8ceb 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -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 diff --git a/docs/source/targetsystem.rst b/docs/source/targetsystem.rst index eeccf5bc4..1f790768e 100644 --- a/docs/source/targetsystem.rst +++ b/docs/source/targetsystem.rst @@ -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 ------------------------------ diff --git a/il65/examples/test.ill b/il65/examples/test.ill index bc98574d0..4f40c76ee 100644 --- a/il65/examples/test.ill +++ b/il65/examples/test.ill @@ -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) { diff --git a/il65/src/il65/Main.kt b/il65/src/il65/Main.kt index 62ba007d6..d1ffc9213 100644 --- a/il65/src/il65/Main.kt +++ b/il65/src/il65/Main.kt @@ -21,11 +21,12 @@ fun main(args: Array) { 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) { 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) { // 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, diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index fd977f42d..3a7ebc916 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -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) : 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, - override val position: Position?) : INameScope { - - private val scopedNamesUsed: MutableSet = mutableSetOf("main") // main is always used - - override fun usedNames(): Set = scopedNamesUsed - - override fun lookup(scopedName: List, 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("<<>>", statements, position) + private val theGlobalNamespace by lazy { + GlobalNamespace("<<>>", statements, position) } + override fun definingScope(): INameScope = theGlobalNamespace override fun usedNames(): Set = 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, + override val position: Position?) : INameScope { + + private val scopedNamesUsed: MutableSet = mutableSetOf("main") // main is always used + + override fun usedNames(): Set = scopedNamesUsed + + override fun lookup(scopedName: List, 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? = null) : IExpression { + val arrayvalue: MutableList? = 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 { diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index ed5d2177a..b8c731ae8 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -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(functionCall) + val stmtOfExpression = findParentNode(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)) diff --git a/il65/src/il65/ast/AstIdentifiersChecker.kt b/il65/src/il65/ast/AstIdentifiersChecker.kt index 383e7a87b..9422ecb10 100644 --- a/il65/src/il65/ast/AstIdentifiersChecker.kt +++ b/il65/src/il65/ast/AstIdentifiersChecker.kt @@ -21,10 +21,10 @@ fun Module.checkIdentifiers(): MutableMap { 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(".") diff --git a/il65/src/il65/functions/BuiltinFunctions.kt b/il65/src/il65/functions/BuiltinFunctions.kt index 2deeb8ade..788735ebd 100644 --- a/il65/src/il65/functions/BuiltinFunctions.kt +++ b/il65/src/il65/functions/BuiltinFunctions.kt @@ -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, position: Position?, namespace:INameScope, function: (arg: Double)->Double): LiteralValue { +private fun oneDoubleArg(args: List, 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, position: Position?, namespace throw NotConstArgumentException() } -private fun oneDoubleArgOutputInt(args: List, position: Position?, namespace:INameScope, function: (arg: Double)->Int): LiteralValue { +private fun oneDoubleArgOutputInt(args: List, 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, position: Position?, throw NotConstArgumentException() } -private fun oneIntArgOutputInt(args: List, position: Position?, namespace:INameScope, function: (arg: Int)->Int): LiteralValue { +private fun oneIntArgOutputInt(args: List, 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, position: Position?, nam throw NotConstArgumentException() } +private fun nonScalarArgOutputNumber(args: List, position: Position?, namespace:INameScope, + function: (arg: Collection)->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, position: Position?, namespace:INameScope, + function: (arg: Collection)->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, position: Position?, namespace:INameScope): LiteralValue - = oneDoubleArgOutputInt(args, position, namespace) { it -> Math.round(it).toInt() } + = oneDoubleArgOutputInt(args, position, namespace, Math::round) + +fun builtinFloor(args: List, position: Position?, namespace:INameScope): LiteralValue + = oneDoubleArgOutputInt(args, position, namespace, Math::floor) + +fun builtinCeil(args: List, position: Position?, namespace:INameScope): LiteralValue + = oneDoubleArgOutputInt(args, position, namespace, Math::ceil) fun builtinSin(args: List, position: Position?, namespace:INameScope): LiteralValue = oneDoubleArg(args, position, namespace, Math::sin) @@ -90,41 +124,35 @@ fun builtinRad(args: List, position: Position?, namespace:INameScop fun builtinDeg(args: List, position: Position?, namespace:INameScope): LiteralValue = oneDoubleArg(args, position, namespace, Math::toDegrees) -fun builtinAbs(args: List, 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, position: Position?, namespace:INameScope): LiteralValue + = oneDoubleArg(args, position, namespace, Math::abs) -fun builtinMax(args: List, 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, position: Position?, namespace:INameScope): LiteralValue + = oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 } -fun builtinMin(args: List, 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, position: Position?, namespace:INameScope): LiteralValue + = oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 1 } -fun builtinLsl(args: List, position: Position?, namespace:INameScope): LiteralValue = - oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 } +fun builtinMin(args: List, position: Position?, namespace:INameScope): LiteralValue + = nonScalarArgOutputNumber(args, position, namespace) { it.min()!! } -fun builtinLsr(args: List, position: Position?, namespace:INameScope): LiteralValue = - oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 1 } +fun builtinMax(args: List, position: Position?, namespace:INameScope): LiteralValue + = nonScalarArgOutputNumber(args, position, namespace) { it.max()!! } + +fun builtinSum(args: List, position: Position?, namespace:INameScope): LiteralValue + = nonScalarArgOutputNumber(args, position, namespace) { it.sum() } + +fun builtinAvg(args: List, position: Position?, namespace:INameScope): LiteralValue + = nonScalarArgOutputNumber(args, position, namespace) { it.average() } + +fun builtinLen(args: List, position: Position?, namespace:INameScope): LiteralValue + = nonScalarArgOutputNumber(args, position, namespace) { it.size } + +fun builtinAny(args: List, position: Position?, namespace:INameScope): LiteralValue + = nonScalarArgOutputBoolean(args, position, namespace) { it.any { v -> v != 0.0} } + +fun builtinAll(args: List, position: Position?, namespace:INameScope): LiteralValue + = nonScalarArgOutputBoolean(args, position, namespace) { it.all { v -> v != 0.0} } private fun intOrFloatLiteral(value: Double, position: Position?): LiteralValue { diff --git a/il65/src/il65/optimizing/ExpressionOptimizer.kt b/il65/src/il65/optimizing/ExpressionOptimizer.kt index 2d11fdd1f..4befad41f 100644 --- a/il65/src/il65/optimizing/ExpressionOptimizer.kt +++ b/il65/src/il65/optimizing/ExpressionOptimizer.kt @@ -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) + } } diff --git a/lib65/c64lib.ill b/lib65/c64lib.ill index 0efe32834..59b58529a 100644 --- a/lib65/c64lib.ill +++ b/lib65/c64lib.ill @@ -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