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 { .wy-nav-content {
max-width: 900px; max-width: 1000px;
} }
/* override table width restrictions */ /* 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`` cannot be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance. for instance.
.. todo::
matrix datatype
Variables that represent CPU hardware registers Variables that represent CPU hardware registers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -423,14 +427,14 @@ log10(x)
sqrt(x) sqrt(x)
Square root. Square root.
max(x [, y, ...])
Maximum of the values x, y, ...
min(x [, y, ...])
Minumum of the values x, y, ...
round(x) 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) rad(x)
Degrees to radians. Degrees to radians.
@ -438,47 +442,66 @@ rad(x)
deg(x) deg(x)
Radians to degrees. 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. 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) 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. 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. 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) 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. 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. 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, 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. while the highest bit will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation) (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). 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. 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. 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). 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. 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, 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. while bit 0 will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation) (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). 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. 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. 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). 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. Set (or clear) the CPU status register Carry flag. No result value.
(translated into ``SEC`` or ``CLC`` cpu instruction) (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. Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction) (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) - ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
- ``AX``, ``AY``, ``XY`` surrogate 16-bit registers: LSB-order (lo/hi) combined register pairs - ``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 Subroutine Calling Conventions
------------------------------ ------------------------------

View File

@ -5,6 +5,25 @@
~ main $c003 { ~ 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 A = X>2
X = Y>Y X = Y>Y
@ -15,12 +34,12 @@
str ascending5 = "z" to "z" str ascending5 = "z" to "z"
const byte cc = 4 + (2==9) const byte cc = 4 + (2==9)
byte cc2 = 4 - (2==10) 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 derpA = abs(-2.5-0.5)
memory byte derpB = max(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 cderp = min([$ffdd])+ (1/1)
memory byte cderpA = min($ffdd, 10, 20, 30) memory byte cderpA = min([$ffdd, 10, 20, 30])
memory byte cderpB = min(1, 2.2, 4.4, 100) memory byte cderpB = min([1, 2.2, 4.4, 100])
memory byte derp2 = 2+$ffdd+round(10*sin(3)) memory byte derp2 = 2+$ffdd+round(10*sin(3))
const byte hopla=55-33 const byte hopla=55-33
const byte hopla3=100+(-hopla) const byte hopla3=100+(-hopla)
@ -47,8 +66,8 @@
byte equalQQ = 4==4 byte equalQQ = 4==4
const byte equalQQ2 = (4+hopla)>0 const byte equalQQ2 = (4+hopla)>0
_P_carry(1) P_carry(1)
_P_irqd(0) P_irqd(0)
equalQQ = foo(33) equalQQ = foo(33)
equalQQ = main.foo(33) equalQQ = main.foo(33)
@ -73,16 +92,16 @@
if(6==6) { if(6==6) {
A=sin(X) A=sin(X)
X=max(1,2,Y) X=max([1,2,Y])
X=min(1,2,Y) X=min([1,2,Y])
X=_lsl(1) X=lsl(1)
X=_lsl(Y) X=lsl(Y)
_P_carry(0) P_carry(0)
_P_carry(Y) ; TODO error P_carry(Y) ; TODO error
_P_carry(9.99) ; TODO error P_carry(9.99) ; TODO error
_P_irqd(0) P_irqd(0)
_P_irqd(Y) ; TODO error P_irqd(Y) ; TODO error
_P_irqd(9.99) ; TODO error P_irqd(9.99) ; TODO error
} else X=33 } else X=33
if(6>36) { if(6>36) {

View File

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

View File

@ -161,6 +161,10 @@ interface IAstProcessor {
fun process(label: Label): IStatement { fun process(label: Label): IStatement {
return label return label
} }
fun process(literalValue: LiteralValue): LiteralValue {
return literalValue
}
} }
@ -173,7 +177,7 @@ interface Node {
if(scope!=null) { if(scope!=null) {
return scope return scope
} }
if(this is Label && this.name.startsWith("pseudo::")) { if(this is Label && this.name.startsWith("builtin::")) {
return BuiltinFunctionScopePlaceholder return BuiltinFunctionScopePlaceholder
} }
throw FatalAstException("scope missing from $this") throw FatalAstException("scope missing from $this")
@ -248,7 +252,7 @@ interface INameScope {
} else { } else {
// unqualified name, find the scope the statement is in, look in that first // unqualified name, find the scope the statement is in, look in that first
var statementScope = statement var statementScope = statement
while(true) { while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope() val localScope = statementScope.definingScope()
val result = localScope.labelsAndVariables()[scopedName[0]] val result = localScope.labelsAndVariables()[scopedName[0]]
if (result != null) if (result != null)
@ -259,6 +263,7 @@ interface INameScope {
// not found in this scope, look one higher up // not found in this scope, look one higher up
statementScope = statementScope.parent statementScope = statementScope.parent
} }
return null
} }
} }
@ -316,6 +321,14 @@ object BuiltinFunctionScopePlaceholder : INameScope {
override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes") 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, class Module(override val name: String,
override var statements: MutableList<IStatement>) : Node, INameScope { override var statements: MutableList<IStatement>) : Node, INameScope {
override var position: Position? = null override var position: Position? = null
@ -334,47 +347,49 @@ class Module(override val name: String,
processor.process(this) processor.process(this)
} }
fun namespace(): INameScope { private val theGlobalNamespace by lazy {
class GlobalNamespace(override val name: String, GlobalNamespace("<<<global>>>", statements, position)
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)
} }
override fun definingScope(): INameScope = theGlobalNamespace
override fun usedNames(): Set<String> = throw NotImplementedError("not implemented on sub-scopes") override fun usedNames(): Set<String> = throw NotImplementedError("not implemented on sub-scopes")
override fun registerUsedName(name: 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? { 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) 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, class LiteralValue(val intvalue: Int? = null,
val floatvalue: Double? = null, val floatvalue: Double? = null,
val strvalue: String? = null, val strvalue: String? = null,
val arrayvalue: List<IExpression>? = null) : IExpression { val arrayvalue: MutableList<IExpression>? = null) : IExpression {
override var position: Position? = null override var position: Position? = null
override lateinit var parent: Node override lateinit var parent: Node
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false 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() floatvalue!=null -> floatvalue.toInt()
else -> { else -> {
if((strvalue!=null || arrayvalue!=null) && errorIfNotNumeric) 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 else null
} }
} }
@ -608,7 +623,7 @@ class LiteralValue(val intvalue: Int? = null,
intvalue!=null -> intvalue.toDouble() intvalue!=null -> intvalue.toDouble()
else -> { else -> {
if((strvalue!=null || arrayvalue!=null) && errorIfNotNumeric) 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 else null
} }
} }
@ -626,7 +641,11 @@ class LiteralValue(val intvalue: Int? = null,
} }
override fun constValue(namespace: INameScope): LiteralValue? = this 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) "round" -> builtinRound(arglist, position, namespace)
"rad" -> builtinRad(arglist, position, namespace) "rad" -> builtinRad(arglist, position, namespace)
"deg" -> builtinDeg(arglist, position, namespace) "deg" -> builtinDeg(arglist, position, namespace)
"_lsl" -> builtinLsl(arglist, position, namespace) "sum" -> builtinSum(arglist, position, namespace)
"_lsr" -> builtinLsr(arglist, position, namespace) "avg" -> builtinAvg(arglist, position, namespace)
"_rol" -> throw ExpressionException("builtin function _rol can't be used in expressions because it doesn't return a value", position) "len" -> builtinLen(arglist, position, namespace)
"_rol2" -> throw ExpressionException("builtin function _rol2 can't be used in expressions because it doesn't return a value", position) "any" -> builtinAny(arglist, position, namespace)
"_ror" -> throw ExpressionException("builtin function _ror can't be used in expressions because it doesn't return a value", position) "all" -> builtinAll(arglist, position, namespace)
"_ror2" -> throw ExpressionException("builtin function _ror2 can't be used in expressions because it doesn't return a value", position) "floor" -> builtinFloor(arglist, position, namespace)
"_P_carry" -> throw ExpressionException("builtin function _P_carry can't be used in expressions because it doesn't return a value", position) "ceil" -> builtinCeil(arglist, position, namespace)
"_P_irqd" -> throw ExpressionException("builtin function _P_irqd can't be used in expressions because it doesn't return a value", position) "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 else -> null
} }
} }
@ -1252,7 +1278,7 @@ private fun il65Parser.BooleanliteralContext.toAst() = when(text) {
private fun il65Parser.ArrayliteralContext.toAst(withPosition: Boolean) = 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 { 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 call parameters against signature
* todo check subroutine return values against target assignment values * todo check subroutine return values against the call's result assignments
*/ */
class AstChecker(private val globalNamespace: INameScope) : IAstProcessor { class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
@ -125,11 +125,12 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
VarDeclType.VAR, VarDeclType.CONST -> { VarDeclType.VAR, VarDeclType.CONST -> {
when { when {
decl.value == null -> 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 -> 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 -> { decl.isScalar -> {
checkConstInitializerValueScalar(decl) checkConstInitializerValueScalar(decl)
checkValueType(decl, decl.value as LiteralValue, decl.position)
checkValueRange(decl.datatype, decl.value as LiteralValue, decl.position) checkValueRange(decl.datatype, decl.value as LiteralValue, decl.position)
} }
decl.isArray || decl.isMatrix -> { decl.isArray || decl.isMatrix -> {
@ -298,10 +299,10 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
override fun process(functionCall: FunctionCall): IExpression { override fun process(functionCall: FunctionCall): IExpression {
// this function call is (part of) an expression, which should be in a statement somewhere. // 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}") ?: 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) if(targetStatement!=null)
functionCall.targetStatement = targetStatement // link to the actual target statement functionCall.targetStatement = targetStatement // link to the actual target statement
return super.process(functionCall) return super.process(functionCall)
@ -315,6 +316,8 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
} }
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? { 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) val targetStatement = globalNamespace.lookup(target.nameInSource, statement)
if(targetStatement is Label || targetStatement is Subroutine) if(targetStatement is Label || targetStatement is Subroutine)
return targetStatement return targetStatement
@ -331,17 +334,17 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
DataType.FLOAT -> { DataType.FLOAT -> {
val number = value.asFloat(false) val number = value.asFloat(false)
if (number!=null && (number > 1.7014118345e+38 || number < -1.7014118345e+38)) 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 -> { DataType.BYTE -> {
val number = value.asInt(false) val number = value.asInt(false)
if (number!=null && (number < 0 || number > 255)) 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 -> { DataType.WORD -> {
val number = value.asInt(false) val number = value.asInt(false)
if (number!=null && (number < 0 || number > 65535)) 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 -> { DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val str = value.strvalue val str = value.strvalue
@ -352,6 +355,37 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
return true 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) { private fun checkConstInitializerValueScalar(decl: VarDecl) {
fun err(msg: String) { fun err(msg: String) {
checkResult.add(SyntaxError(msg, decl.position)) checkResult.add(SyntaxError(msg, decl.position))

View File

@ -21,10 +21,10 @@ fun Module.checkIdentifiers(): MutableMap<String, IStatement> {
val BuiltinFunctionNames = setOf( val BuiltinFunctionNames = setOf(
"_P_carry", "_P_irqd", "_rol", "_ror", "_rol2", "_ror2", "P_carry", "P_irqd", "rol", "ror", "rol2", "ror2", "lsl", "lsr",
"_lsl", "_lsr", "sin", "cos", "abs", "acos", "sin", "cos", "abs", "acos", "asin", "tan", "atan",
"asin", "tan", "atan", "log", "log10", "sqrt", "log", "log10", "sqrt", "rad", "deg", "round", "floor", "ceil",
"max", "min", "round", "rad", "deg") "max", "min", "avg", "sum", "len", "any", "all")
class AstIdentifiersChecker : IAstProcessor { class AstIdentifiersChecker : IAstProcessor {
@ -65,7 +65,7 @@ class AstIdentifiersChecker : IAstProcessor {
override fun process(subroutine: Subroutine): IStatement { override fun process(subroutine: Subroutine): IStatement {
if(BuiltinFunctionNames.contains(subroutine.name)) { 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)) checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
} else { } else {
val scopedName = subroutine.scopedname.joinToString(".") val scopedName = subroutine.scopedname.joinToString(".")
@ -81,7 +81,7 @@ class AstIdentifiersChecker : IAstProcessor {
override fun process(label: Label): IStatement { override fun process(label: Label): IStatement {
if(BuiltinFunctionNames.contains(label.name)) { 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)) checkResult.add(NameError("builtin function cannot be redefined", label.position))
} else { } else {
val scopedName = label.scopedname.joinToString(".") val scopedName = label.scopedname.joinToString(".")

View File

@ -3,22 +3,22 @@ package il65.functions
import il65.ast.* import il65.ast.*
val BuiltIns = listOf("sin", "cos", "abs", "acos", "asin", "tan", "atan", "log", "log10", "sqrt", "max", "min", "round", "rad", "deg") val BuiltIns = listOf(
"sin", "cos", "abs", "acos", "asin", "tan", "atan", "log", "log10",
"sqrt", "max", "min", "round", "rad", "deg", "avg", "sum"
// @todo additional builtins such as: avg, sum, abs, round )
class NotConstArgumentException: AstException("not a const argument to a built-in function") 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) if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position) throw SyntaxError("built-in function requires one floating point argument", position)
val float = args[0].constValue(namespace)?.asFloat() val float = args[0].constValue(namespace)?.asFloat()
if(float!=null) { if(float!=null) {
val result = LiteralValue(floatvalue = function(float)) val result = intOrFloatLiteral(function(float).toDouble(), args[0].position)
result.position = args[0].position result.position = args[0].position
return result return result
} }
@ -26,13 +26,13 @@ private fun oneDoubleArg(args: List<IExpression>, position: Position?, namespace
throw NotConstArgumentException() 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) if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position) throw SyntaxError("built-in function requires one floating point argument", position)
val float = args[0].constValue(namespace)?.asFloat() val float = args[0].constValue(namespace)?.asFloat()
if(float!=null) { if(float!=null) {
val result = LiteralValue(intvalue = function(float)) val result = LiteralValue(function(float).toInt())
result.position = args[0].position result.position = args[0].position
return result return result
} }
@ -40,13 +40,13 @@ private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position?,
throw NotConstArgumentException() 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) if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position) throw SyntaxError("built-in function requires one integer argument", position)
val integer = args[0].constValue(namespace)?.asInt() val integer = args[0].constValue(namespace)?.asInt()
if(integer!=null) { if(integer!=null) {
val result = LiteralValue(intvalue = function(integer)) val result = LiteralValue(function(integer).toInt())
result.position = args[0].position result.position = args[0].position
return result return result
} }
@ -54,8 +54,42 @@ private fun oneIntArgOutputInt(args: List<IExpression>, position: Position?, nam
throw NotConstArgumentException() 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 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 fun builtinSin(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::sin) = 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 fun builtinDeg(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::toDegrees) = oneDoubleArg(args, position, namespace, Math::toDegrees)
fun builtinAbs(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue { fun builtinAbs(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
if(args.size!=1) = oneDoubleArg(args, position, namespace, Math::abs)
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 builtinMax(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue { fun builtinLsl(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
if(args.isEmpty()) = oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 }
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 builtinMin(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue { fun builtinLsr(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
if(args.isEmpty()) = oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 1 }
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 builtinLsl(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue = fun builtinMin(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 } = nonScalarArgOutputNumber(args, position, namespace) { it.min()!! }
fun builtinLsr(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue = fun builtinMax(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 1 } = 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 { 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: 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: 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) 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 { class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcessor {
@ -187,7 +187,7 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
v.position = range.position v.position = range.position
v.parent = range.parent v.parent = range.parent
v v
}) }.toMutableList())
} }
from.strvalue != null && to.strvalue != null -> { from.strvalue != null && to.strvalue != null -> {
// char range // char range
@ -208,6 +208,15 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
range 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) -> () { sub clear_screen (char: A, color: Y) -> () {
; ---- clear the character screen with the given fill character and character color. ; ---- 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) ; (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 {{ %asm {{
sta _loop + 1 ; self-modifying sta _loop + 1 ; self-modifying