mirror of
https://github.com/irmen/prog8.git
synced 2024-11-25 19:31:36 +00:00
bunch of new builtin functions, const expression evaluation now also done for array literals
This commit is contained in:
parent
76d07a2de8
commit
d9865a4b97
@ -1,5 +1,5 @@
|
||||
.wy-nav-content {
|
||||
max-width: 900px;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
/* override table width restrictions */
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
------------------------------
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -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(".")
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user