mirror of
https://github.com/irmen/prog8.git
synced 2024-12-24 01:29:28 +00:00
recursion checking and bugfix in subroutine usage determination
This commit is contained in:
parent
6ed6a3a552
commit
76d07a2de8
@ -203,7 +203,9 @@ Integers
|
||||
Integers are 8 or 16 bit numbers and can be written in normal decimal notation,
|
||||
in hexadecimal and in binary notation.
|
||||
|
||||
@todo right now only unsinged integers are supported (0-255 for byte types, 0-65535 for word types)
|
||||
.. todo::
|
||||
Right now only unsinged integers are supported (0-255 for byte types, 0-65535 for word types)
|
||||
@todo maybe signed integers (-128..127 and -32768..32767) will be added later
|
||||
|
||||
|
||||
Strings
|
||||
@ -254,14 +256,9 @@ The resulting value is simply a 16 bit word. Example::
|
||||
AX = #somevar
|
||||
|
||||
|
||||
**Indirect addressing:**
|
||||
@todo ???
|
||||
|
||||
**Indirect addressing in jumps:**
|
||||
@todo ???
|
||||
For an indirect ``goto`` statement, the compiler will issue the 6502 CPU's special instruction
|
||||
(``jmp`` indirect). A subroutine call (``jsr`` indirect) is emitted
|
||||
using a couple of instructions.
|
||||
.. todo::
|
||||
This is not yet implemented.
|
||||
Indirect addressing, Indirect addressing in jumps (jmp/jsr indirect)
|
||||
|
||||
|
||||
Loops
|
||||
@ -321,11 +318,13 @@ for normal assignments (``A = A + X``).
|
||||
Expressions
|
||||
-----------
|
||||
|
||||
In most places where a number or other value is expected, you can use just the number, or a full constant expression.
|
||||
In most places where a number or other value is expected, you can use just the number, or a constant expression.
|
||||
The expression is parsed and evaluated by the compiler itself at compile time, and the (constant) resulting value is used in its place.
|
||||
Expressions can contain function calls to the math library (sin, cos, etc) and you can also use
|
||||
all builtin functions (max, avg, min, sum etc). They can also reference idendifiers defined elsewhere in your code,
|
||||
if this makes sense.
|
||||
Expressions can contain procedure and function calls.
|
||||
There are various built-in functions such as sin(), cos(), min(), max() that can be used in expressions (see :ref:`builtinfunctions`).
|
||||
You can also reference idendifiers defined elsewhere in your code.
|
||||
The compiler will evaluate the expression if it is a constant, and just use the resulting value from then on.
|
||||
Expressions that cannot be compile-time evaluated will result in code that calculates them at runtime.
|
||||
|
||||
|
||||
Arithmetic and Logical expressions
|
||||
@ -378,6 +377,13 @@ value of the given registers after the subroutine call. Otherwise, the subrouti
|
||||
as well clobber all three registers. Preserving the original values does result in some
|
||||
stack manipulation code to be inserted for every call like this, which can be quite slow.
|
||||
|
||||
.. caution::
|
||||
Note that *recursive* subroutine calls are not supported at this time.
|
||||
If you do need a recursive algorithm, you'll have to hand code it in embedded assembly for now,
|
||||
or rewrite it into an iterative algorithm.
|
||||
|
||||
|
||||
.. _builtinfunctions:
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
@ -473,3 +479,6 @@ _P_carry(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
|
||||
|
@ -33,6 +33,7 @@
|
||||
const byte equal = 4==4
|
||||
const byte equal2 = (4+hopla)>0
|
||||
|
||||
goto mega
|
||||
if_eq goto mega
|
||||
|
||||
if_eq {
|
||||
@ -57,7 +58,7 @@
|
||||
byte equalWW = 4==4
|
||||
const byte equalWW2 = (4+hopla)>0
|
||||
|
||||
if (1==1) goto hopla
|
||||
if (1==1) goto cool
|
||||
|
||||
if (1==2) return 44
|
||||
|
||||
@ -101,9 +102,14 @@ cool:
|
||||
byte blerp = 3
|
||||
A=99
|
||||
return 33
|
||||
ultrafoo()
|
||||
X =33
|
||||
mega:
|
||||
cool:
|
||||
sub ultrafoo() -> () {
|
||||
return 33
|
||||
goto main.mega
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -30,12 +30,14 @@ fun main(args: Array<String>) {
|
||||
|
||||
// perform syntax checks and optimizations
|
||||
|
||||
moduleAst.checkIdentifiers(globalNamespace)
|
||||
moduleAst.checkIdentifiers()
|
||||
moduleAst.optimizeExpressions(globalNamespace)
|
||||
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers(globalNamespace)
|
||||
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
|
||||
moduleAst.checkValid(globalNamespaceAfterOptimize) // check if final tree is valid
|
||||
val globalNamespaceAfterOptimize = moduleAst.namespace() // 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
|
||||
|
||||
|
||||
// determine special compiler options
|
||||
|
@ -168,12 +168,36 @@ interface Node {
|
||||
var position: Position? // optional for the sake of easy unit testing
|
||||
var parent: Node // will be linked correctly later (late init)
|
||||
fun linkParents(parent: Node)
|
||||
fun definingScope(): INameScope {
|
||||
val scope = findParentNode<INameScope>(this)
|
||||
if(scope!=null) {
|
||||
return scope
|
||||
}
|
||||
if(this is Label && this.name.startsWith("pseudo::")) {
|
||||
return BuiltinFunctionScopePlaceholder
|
||||
}
|
||||
throw FatalAstException("scope missing from $this")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// find the parent node of a specific type or interface
|
||||
// (useful to figure out in what namespace/block something is defined, etc)
|
||||
inline fun <reified T> findParentNode(node: Node): T? {
|
||||
var candidate = node.parent
|
||||
while(candidate !is T && candidate !is ParentSentinel)
|
||||
candidate = candidate.parent
|
||||
return if(candidate is ParentSentinel)
|
||||
null
|
||||
else
|
||||
candidate as T
|
||||
}
|
||||
|
||||
|
||||
interface IStatement : Node {
|
||||
fun process(processor: IAstProcessor) : IStatement
|
||||
fun makeScopedName(name: String): List<String> {
|
||||
// this is usually cached in a lazy property on the statement object itself
|
||||
val scope = mutableListOf<String>()
|
||||
var statementScope = this.parent
|
||||
while(statementScope !is ParentSentinel && statementScope !is Module) {
|
||||
@ -191,6 +215,7 @@ interface IStatement : Node {
|
||||
interface IFunctionCall {
|
||||
var target: IdentifierReference
|
||||
var arglist: List<IExpression>
|
||||
var targetStatement: IStatement
|
||||
}
|
||||
|
||||
interface INameScope {
|
||||
@ -204,7 +229,7 @@ interface INameScope {
|
||||
|
||||
fun subScopes() = statements.filter { it is INameScope } .map { it as INameScope }.associate { it.name to it }
|
||||
|
||||
fun definedNames() = statements.filter { it is Label || it is VarDecl }
|
||||
fun labelsAndVariables() = statements.filter { it is Label || it is VarDecl }
|
||||
.associate {((it as? Label)?.name ?: (it as? VarDecl)?.name) to it }
|
||||
|
||||
fun lookup(scopedName: List<String>, statement: Node) : IStatement? {
|
||||
@ -217,19 +242,15 @@ interface INameScope {
|
||||
return null
|
||||
}
|
||||
val foundScope : INameScope = scope!!
|
||||
return foundScope.definedNames()[scopedName.last()]
|
||||
return foundScope.labelsAndVariables()[scopedName.last()]
|
||||
?:
|
||||
foundScope.subScopes()[scopedName.last()] as IStatement?
|
||||
} else {
|
||||
// unqualified name, find the scope the statement is in, look in that first
|
||||
var statementScope: Node = statement
|
||||
var statementScope = statement
|
||||
while(true) {
|
||||
while (statementScope !is INameScope && statementScope !is ParentSentinel)
|
||||
statementScope = statementScope.parent
|
||||
if (statementScope is ParentSentinel)
|
||||
return null
|
||||
val localScope = statementScope as INameScope
|
||||
val result = localScope.definedNames()[scopedName[0]]
|
||||
val localScope = statementScope.definingScope()
|
||||
val result = localScope.labelsAndVariables()[scopedName[0]]
|
||||
if (result != null)
|
||||
return result
|
||||
val subscope = localScope.subScopes()[scopedName[0]] as IStatement?
|
||||
@ -244,7 +265,7 @@ interface INameScope {
|
||||
fun debugPrint() {
|
||||
fun printNames(indent: Int, namespace: INameScope) {
|
||||
println(" ".repeat(4*indent) + "${namespace.name} -> ${namespace::class.simpleName} at ${namespace.position}")
|
||||
namespace.definedNames().forEach {
|
||||
namespace.labelsAndVariables().forEach {
|
||||
println(" ".repeat(4 * (1 + indent)) + "${it.key} -> ${it.value::class.simpleName} at ${it.value.position}")
|
||||
}
|
||||
namespace.subScopes().forEach {
|
||||
@ -266,7 +287,7 @@ interface INameScope {
|
||||
* Inserted into the Ast in place of modified nodes (not inserted directly as a parser result)
|
||||
* It can hold zero or more replacement statements that have to be inserted at that point.
|
||||
*/
|
||||
data class AnonymousStatementList(override var parent: Node, var statements: List<IStatement>) : IStatement {
|
||||
class AnonymousStatementList(override var parent: Node, var statements: List<IStatement>) : IStatement {
|
||||
override var position: Position? = null
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -281,15 +302,22 @@ data class AnonymousStatementList(override var parent: Node, var statements: Lis
|
||||
}
|
||||
|
||||
|
||||
object ParentSentinel : Node {
|
||||
private object ParentSentinel : Node {
|
||||
override var position: Position? = null
|
||||
override var parent: Node = this
|
||||
override fun linkParents(parent: Node) {}
|
||||
}
|
||||
|
||||
object BuiltinFunctionScopePlaceholder : INameScope {
|
||||
override val name = "<<builtin-functions-scope-placeholder>>"
|
||||
override val position: Position? = null
|
||||
override var statements = mutableListOf<IStatement>()
|
||||
override fun usedNames(): Set<String> = throw NotImplementedError("not implemented on sub-scopes")
|
||||
override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes")
|
||||
}
|
||||
|
||||
data class Module(override val name: String,
|
||||
override var statements: MutableList<IStatement>) : Node, INameScope {
|
||||
class Module(override val name: String,
|
||||
override var statements: MutableList<IStatement>) : Node, INameScope {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -326,10 +354,10 @@ data class Module(override val name: String,
|
||||
val stmt = super.lookup(scopedName, statement)
|
||||
if(stmt!=null) {
|
||||
val targetScopedName = when(stmt) {
|
||||
is Label -> stmt.makeScopedName(stmt.name)
|
||||
is VarDecl -> stmt.makeScopedName(stmt.name)
|
||||
is Block -> stmt.makeScopedName(stmt.name)
|
||||
is Subroutine -> stmt.makeScopedName(stmt.name)
|
||||
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("."))
|
||||
@ -350,11 +378,12 @@ data class Module(override val name: String,
|
||||
}
|
||||
|
||||
|
||||
data class Block(override val name: String,
|
||||
class Block(override val name: String,
|
||||
val address: Int?,
|
||||
override var statements: MutableList<IStatement>) : IStatement, INameScope {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
val scopedname: List<String> by lazy { makeScopedName(name) }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -372,7 +401,7 @@ data class Block(override val name: String,
|
||||
}
|
||||
|
||||
|
||||
data class Directive(val directive: String, val args: List<DirectiveArg>) : IStatement {
|
||||
class Directive(val directive: String, val args: List<DirectiveArg>) : IStatement {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -385,7 +414,7 @@ data class Directive(val directive: String, val args: List<DirectiveArg>) : ISta
|
||||
}
|
||||
|
||||
|
||||
data class DirectiveArg(val str: String?, val name: String?, val int: Int?) : Node {
|
||||
class DirectiveArg(val str: String?, val name: String?, val int: Int?) : Node {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -395,19 +424,24 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?) : No
|
||||
}
|
||||
|
||||
|
||||
data class Label(val name: String) : IStatement {
|
||||
class Label(val name: String) : IStatement {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
val scopedname: List<String> by lazy { makeScopedName(name) }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Label(name=$name, pos=$position)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class Return(var values: List<IExpression>) : IStatement {
|
||||
class Return(var values: List<IExpression>) : IStatement {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -423,7 +457,7 @@ data class Return(var values: List<IExpression>) : IStatement {
|
||||
}
|
||||
|
||||
|
||||
data class ArraySpec(var x: IExpression, var y: IExpression?) : Node {
|
||||
class ArraySpec(var x: IExpression, var y: IExpression?) : Node {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -446,7 +480,7 @@ enum class VarDeclType {
|
||||
MEMORY
|
||||
}
|
||||
|
||||
data class VarDecl(val type: VarDeclType,
|
||||
class VarDecl(val type: VarDeclType,
|
||||
val datatype: DataType,
|
||||
val arrayspec: ArraySpec?,
|
||||
val name: String,
|
||||
@ -465,6 +499,8 @@ data class VarDecl(val type: VarDeclType,
|
||||
val isScalar = arrayspec==null
|
||||
val isArray = arrayspec!=null && arrayspec.y==null
|
||||
val isMatrix = arrayspec?.y != null
|
||||
val scopedname: List<String> by lazy { makeScopedName(name) }
|
||||
|
||||
fun arraySizeX(namespace: INameScope) : Int? {
|
||||
return arrayspec?.x?.constValue(namespace)?.intvalue
|
||||
}
|
||||
@ -474,7 +510,7 @@ data class VarDecl(val type: VarDeclType,
|
||||
}
|
||||
|
||||
|
||||
data class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExpression) : IStatement {
|
||||
class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExpression) : IStatement {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -491,7 +527,7 @@ data class Assignment(var target: AssignTarget, val aug_op : String?, var value:
|
||||
}
|
||||
}
|
||||
|
||||
data class AssignTarget(val register: Register?, val identifier: IdentifierReference?) : Node {
|
||||
class AssignTarget(val register: Register?, val identifier: IdentifierReference?) : Node {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -513,7 +549,7 @@ interface IExpression: Node {
|
||||
|
||||
// note: some expression elements are mutable, to be able to rewrite/process the expression tree
|
||||
|
||||
data class PrefixExpression(val operator: String, var expression: IExpression) : IExpression {
|
||||
class PrefixExpression(val operator: String, var expression: IExpression) : IExpression {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -528,7 +564,7 @@ data class PrefixExpression(val operator: String, var expression: IExpression) :
|
||||
}
|
||||
|
||||
|
||||
data class BinaryExpression(var left: IExpression, val operator: String, var right: IExpression) : IExpression {
|
||||
class BinaryExpression(var left: IExpression, val operator: String, var right: IExpression) : IExpression {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -546,7 +582,7 @@ data class BinaryExpression(var left: IExpression, val operator: String, var rig
|
||||
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
|
||||
}
|
||||
|
||||
data class LiteralValue(val intvalue: Int? = null,
|
||||
class LiteralValue(val intvalue: Int? = null,
|
||||
val floatvalue: Double? = null,
|
||||
val strvalue: String? = null,
|
||||
val arrayvalue: List<IExpression>? = null) : IExpression {
|
||||
@ -594,7 +630,7 @@ data class LiteralValue(val intvalue: Int? = null,
|
||||
}
|
||||
|
||||
|
||||
data class RangeExpr(var from: IExpression, var to: IExpression) : IExpression {
|
||||
class RangeExpr(var from: IExpression, var to: IExpression) : IExpression {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -610,7 +646,7 @@ data class RangeExpr(var from: IExpression, var to: IExpression) : IExpression {
|
||||
}
|
||||
|
||||
|
||||
data class RegisterExpr(val register: Register) : IExpression {
|
||||
class RegisterExpr(val register: Register) : IExpression {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -624,7 +660,7 @@ data class RegisterExpr(val register: Register) : IExpression {
|
||||
}
|
||||
|
||||
|
||||
data class IdentifierReference(val nameInSource: List<String>) : IExpression {
|
||||
class IdentifierReference(val nameInSource: List<String>) : IExpression {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -645,12 +681,16 @@ data class IdentifierReference(val nameInSource: List<String>) : IExpression {
|
||||
return vardecl.value?.constValue(namespace)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "IdentifierRef($nameInSource)"
|
||||
}
|
||||
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time?
|
||||
}
|
||||
|
||||
|
||||
data class PostIncrDecr(var target: AssignTarget, val operator: String) : IStatement {
|
||||
class PostIncrDecr(var target: AssignTarget, val operator: String) : IStatement {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -666,9 +706,10 @@ data class PostIncrDecr(var target: AssignTarget, val operator: String) : IState
|
||||
}
|
||||
|
||||
|
||||
data class Jump(val address: Int?, val identifier: IdentifierReference?) : IStatement {
|
||||
class Jump(val address: Int?, val identifier: IdentifierReference?) : IStatement {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
var targetStatement: IStatement? = null
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -679,9 +720,10 @@ data class Jump(val address: Int?, val identifier: IdentifierReference?) : IStat
|
||||
}
|
||||
|
||||
|
||||
data class FunctionCall(override var target: IdentifierReference, override var arglist: List<IExpression>) : IExpression, IFunctionCall {
|
||||
class FunctionCall(override var target: IdentifierReference, override var arglist: List<IExpression>) : IExpression, IFunctionCall {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
override lateinit var targetStatement: IStatement
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -726,14 +768,19 @@ data class FunctionCall(override var target: IdentifierReference, override var a
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "FunctionCall(target=$target, targetStmt=$targetStatement, pos=$position)"
|
||||
}
|
||||
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
|
||||
}
|
||||
|
||||
|
||||
data class FunctionCallStatement(override var target: IdentifierReference, override var arglist: List<IExpression>) : IStatement, IFunctionCall {
|
||||
class FunctionCallStatement(override var target: IdentifierReference, override var arglist: List<IExpression>) : IStatement, IFunctionCall {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
override lateinit var targetStatement: IStatement
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -742,10 +789,14 @@ data class FunctionCallStatement(override var target: IdentifierReference, overr
|
||||
}
|
||||
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
|
||||
override fun toString(): String {
|
||||
return "FunctionCall(target=$target, targetStmt=$targetStatement, pos=$position)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class InlineAssembly(val assembly: String) : IStatement {
|
||||
class InlineAssembly(val assembly: String) : IStatement {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -757,13 +808,14 @@ data class InlineAssembly(val assembly: String) : IStatement {
|
||||
}
|
||||
|
||||
|
||||
data class Subroutine(override val name: String,
|
||||
class Subroutine(override val name: String,
|
||||
val parameters: List<SubroutineParameter>,
|
||||
val returnvalues: List<SubroutineReturnvalue>,
|
||||
val address: Int?,
|
||||
override var statements: MutableList<IStatement>) : IStatement, INameScope {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
val scopedname: List<String> by lazy { makeScopedName(name) }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -783,7 +835,7 @@ data class Subroutine(override val name: String,
|
||||
}
|
||||
|
||||
|
||||
data class SubroutineParameter(val name: String, val register: Register?, val statusflag: Statusflag?) : Node {
|
||||
class SubroutineParameter(val name: String, val register: Register?, val statusflag: Statusflag?) : Node {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -793,7 +845,7 @@ data class SubroutineParameter(val name: String, val register: Register?, val st
|
||||
}
|
||||
|
||||
|
||||
data class SubroutineReturnvalue(val register: Register?, val statusflag: Statusflag?, val clobbered: Boolean) : Node {
|
||||
class SubroutineReturnvalue(val register: Register?, val statusflag: Statusflag?, val clobbered: Boolean) : Node {
|
||||
override var position: Position? = null
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -803,7 +855,7 @@ data class SubroutineReturnvalue(val register: Register?, val statusflag: Status
|
||||
}
|
||||
|
||||
|
||||
data class IfStatement(var condition: IExpression,
|
||||
class IfStatement(var condition: IExpression,
|
||||
var statements: List<IStatement>, var
|
||||
elsepart: List<IStatement>) : IStatement {
|
||||
override var position: Position? = null
|
||||
@ -820,7 +872,7 @@ data class IfStatement(var condition: IExpression,
|
||||
}
|
||||
|
||||
|
||||
data class BranchStatement(var condition: BranchCondition,
|
||||
class BranchStatement(var condition: BranchCondition,
|
||||
var statements: List<IStatement>, var
|
||||
elsepart: List<IStatement>) : IStatement {
|
||||
override var position: Position? = null
|
||||
|
@ -22,7 +22,6 @@ fun Module.checkValid(globalNamespace: INameScope) {
|
||||
/**
|
||||
* todo check subroutine parameters against signature
|
||||
* todo check subroutine return values against target assignment values
|
||||
*
|
||||
*/
|
||||
|
||||
class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
@ -44,18 +43,22 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
}
|
||||
|
||||
override fun process(jump: Jump): IStatement {
|
||||
super.process(jump)
|
||||
if(jump.identifier!=null) {
|
||||
val targetStatement = checkFunctionOrLabelExists(jump.identifier, jump)
|
||||
if(targetStatement!=null)
|
||||
jump.targetStatement = targetStatement // link to actual jump target
|
||||
}
|
||||
|
||||
if(jump.address!=null && (jump.address < 0 || jump.address > 65535))
|
||||
checkResult.add(SyntaxError("jump address must be valid integer 0..\$ffff", jump.position))
|
||||
return jump
|
||||
return super.process(jump)
|
||||
}
|
||||
|
||||
override fun process(block: Block): IStatement {
|
||||
if(block.address!=null && (block.address<0 || block.address>65535)) {
|
||||
checkResult.add(SyntaxError("block memory address must be valid integer 0..\$ffff", block.position))
|
||||
}
|
||||
super.process(block)
|
||||
return block
|
||||
return super.process(block)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,9 +69,9 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
checkResult.add(SyntaxError(msg, subroutine.position))
|
||||
}
|
||||
|
||||
// subroutines may only be defined directly inside a block
|
||||
if(subroutine.parent !is Block)
|
||||
err("subroutines can only be defined in a block (not in other scopes)")
|
||||
// // subroutines may only be defined directly inside a block @todo NAH, why should we restrict that?
|
||||
// if(subroutine.parent !is Block)
|
||||
// err("subroutines can only be defined in a block (not in other scopes)")
|
||||
|
||||
if(BuiltIns.contains(subroutine.name))
|
||||
err("cannot override a built-in function")
|
||||
@ -79,8 +82,9 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
val uniqueParamRegs = subroutine.parameters.map {it.register}.toSet()
|
||||
if(uniqueParamRegs.size!=subroutine.parameters.size)
|
||||
err("parameter registers should be unique")
|
||||
val uniqueResults = subroutine.returnvalues.map {it.register}.toSet()
|
||||
if(uniqueResults.size!=subroutine.returnvalues.size)
|
||||
val uniqueResultRegisters = subroutine.returnvalues.filter{it.register!=null}.map {it.register.toString()}.toMutableSet()
|
||||
uniqueResultRegisters.addAll(subroutine.returnvalues.filter{it.statusflag!=null}.map{it.statusflag.toString()})
|
||||
if(uniqueResultRegisters.size!=subroutine.returnvalues.size)
|
||||
err("return registers should be unique")
|
||||
|
||||
super.process(subroutine)
|
||||
@ -294,24 +298,28 @@ 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.
|
||||
var statementNode: Node = functionCall
|
||||
while(statementNode !is IStatement && statementNode !is ParentSentinel)
|
||||
statementNode = statementNode.parent
|
||||
if(statementNode is ParentSentinel)
|
||||
throw FatalAstException("cannot determine statement scope of function call expression at ${functionCall.position}")
|
||||
val statementNode = findParentNode<IStatement>(functionCall)
|
||||
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCall.position}")
|
||||
|
||||
checkFunctionExists(functionCall.target, statementNode as IStatement)
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCall.target, statementNode)
|
||||
if(targetStatement!=null)
|
||||
functionCall.targetStatement = targetStatement // link to the actual target statement
|
||||
return super.process(functionCall)
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCallStatement): IStatement {
|
||||
checkFunctionExists(functionCall.target, functionCall)
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCall.target, functionCall)
|
||||
if(targetStatement!=null)
|
||||
functionCall.targetStatement = targetStatement // link to the actual target statement
|
||||
return super.process(functionCall)
|
||||
}
|
||||
|
||||
private fun checkFunctionExists(target: IdentifierReference, statement: IStatement) {
|
||||
if(globalNamespace.lookup(target.nameInSource, statement)==null)
|
||||
checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? {
|
||||
val targetStatement = globalNamespace.lookup(target.nameInSource, statement)
|
||||
if(targetStatement is Label || targetStatement is Subroutine)
|
||||
return targetStatement
|
||||
checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
|
||||
return null
|
||||
}
|
||||
|
||||
private fun checkValueRange(datatype: DataType, value: LiteralValue, position: Position?) : Boolean {
|
||||
|
@ -7,8 +7,8 @@ import il65.parser.ParsingFailedError
|
||||
* Also builds a list of all (scoped) symbol definitions
|
||||
*/
|
||||
|
||||
fun Module.checkIdentifiers(globalNamespace: INameScope): MutableMap<String, IStatement> {
|
||||
val checker = AstIdentifiersChecker(globalNamespace)
|
||||
fun Module.checkIdentifiers(): MutableMap<String, IStatement> {
|
||||
val checker = AstIdentifiersChecker()
|
||||
this.process(checker)
|
||||
val checkResult = checker.result()
|
||||
checkResult.forEach {
|
||||
@ -27,7 +27,7 @@ val BuiltinFunctionNames = setOf(
|
||||
"max", "min", "round", "rad", "deg")
|
||||
|
||||
|
||||
class AstIdentifiersChecker(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
class AstIdentifiersChecker : IAstProcessor {
|
||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||
|
||||
var symbols: MutableMap<String, IStatement> = mutableMapOf()
|
||||
@ -42,7 +42,7 @@ class AstIdentifiersChecker(private val globalNamespace: INameScope) : IAstProce
|
||||
}
|
||||
|
||||
override fun process(block: Block): IStatement {
|
||||
val scopedName = block.makeScopedName(block.name).joinToString(".")
|
||||
val scopedName = block.scopedname.joinToString(".")
|
||||
val existing = symbols[scopedName]
|
||||
if(existing!=null) {
|
||||
nameError(block.name, block.position, existing)
|
||||
@ -53,7 +53,7 @@ class AstIdentifiersChecker(private val globalNamespace: INameScope) : IAstProce
|
||||
}
|
||||
|
||||
override fun process(decl: VarDecl): IStatement {
|
||||
val scopedName = decl.makeScopedName(decl.name).joinToString(".")
|
||||
val scopedName = decl.scopedname.joinToString(".")
|
||||
val existing = symbols[scopedName]
|
||||
if(existing!=null) {
|
||||
nameError(decl.name, decl.position, existing)
|
||||
@ -68,7 +68,7 @@ class AstIdentifiersChecker(private val globalNamespace: INameScope) : IAstProce
|
||||
// the special pseudo-functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
|
||||
} else {
|
||||
val scopedName = subroutine.makeScopedName(subroutine.name).joinToString(".")
|
||||
val scopedName = subroutine.scopedname.joinToString(".")
|
||||
val existing = symbols[scopedName]
|
||||
if (existing != null) {
|
||||
nameError(subroutine.name, subroutine.position, existing)
|
||||
@ -84,7 +84,7 @@ class AstIdentifiersChecker(private val globalNamespace: INameScope) : IAstProce
|
||||
// the special pseudo-functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
||||
} else {
|
||||
val scopedName = label.makeScopedName(label.name).joinToString(".")
|
||||
val scopedName = label.scopedname.joinToString(".")
|
||||
val existing = symbols[scopedName]
|
||||
if (existing != null) {
|
||||
nameError(label.name, label.position, existing)
|
||||
|
121
il65/src/il65/ast/AstRecursionChecker.kt
Normal file
121
il65/src/il65/ast/AstRecursionChecker.kt
Normal file
@ -0,0 +1,121 @@
|
||||
package il65.ast
|
||||
|
||||
import il65.parser.ParsingFailedError
|
||||
|
||||
/**
|
||||
* Checks for the occurrence of recursive subroutine calls
|
||||
*/
|
||||
|
||||
fun Module.checkRecursion() {
|
||||
val checker = AstRecursionChecker()
|
||||
this.process(checker)
|
||||
val checkResult = checker.result()
|
||||
checkResult.forEach {
|
||||
System.err.println(it)
|
||||
}
|
||||
if(checkResult.isNotEmpty())
|
||||
throw ParsingFailedError("There are ${checkResult.size} errors in module '$name'.")
|
||||
}
|
||||
|
||||
|
||||
class DirectedGraph<VT> {
|
||||
private val graph = mutableMapOf<VT, MutableSet<VT>>()
|
||||
private var uniqueVertices = mutableSetOf<VT>()
|
||||
val numVertices : Int
|
||||
get() = uniqueVertices.size
|
||||
|
||||
fun add(from: VT, to: VT) {
|
||||
var targets = graph[from]
|
||||
if(targets==null) {
|
||||
targets = mutableSetOf()
|
||||
graph[from] = targets
|
||||
}
|
||||
targets.add(to)
|
||||
uniqueVertices.add(from)
|
||||
uniqueVertices.add(to)
|
||||
}
|
||||
|
||||
fun print() {
|
||||
println("#vertices: $numVertices")
|
||||
graph.forEach { from, to ->
|
||||
println("$from CALLS:")
|
||||
to.forEach { it -> println(" $it") }
|
||||
}
|
||||
val cycle = checkForCycle()
|
||||
if(cycle.isNotEmpty()) {
|
||||
println("CYCLIC! $cycle")
|
||||
}
|
||||
}
|
||||
|
||||
fun checkForCycle(): MutableList<VT> {
|
||||
val visited = uniqueVertices.associate { it to false }.toMutableMap()
|
||||
val recStack = uniqueVertices.associate { it to false }.toMutableMap()
|
||||
val cycle = mutableListOf<VT>()
|
||||
for(node in uniqueVertices) {
|
||||
if(isCyclicUntil(node, visited, recStack, cycle))
|
||||
return cycle
|
||||
}
|
||||
return mutableListOf()
|
||||
}
|
||||
|
||||
private fun isCyclicUntil(node: VT,
|
||||
visited: MutableMap<VT, Boolean>,
|
||||
recStack: MutableMap<VT, Boolean>,
|
||||
cycleNodes: MutableList<VT>): Boolean {
|
||||
|
||||
if(recStack[node]==true) return true
|
||||
if(visited[node]==true) return false
|
||||
|
||||
// mark current node as visited and add to recursion stack
|
||||
visited[node] = true
|
||||
recStack[node] = true
|
||||
|
||||
// recurse for all neighbours
|
||||
val neighbors = graph[node]
|
||||
if(neighbors!=null) {
|
||||
for (neighbour in neighbors) {
|
||||
if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) {
|
||||
cycleNodes.add(node)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pop node from recursion stack
|
||||
recStack[node] = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AstRecursionChecker : IAstProcessor {
|
||||
private val callGraph = DirectedGraph<INameScope>()
|
||||
|
||||
fun result(): List<AstException> {
|
||||
val cycle = callGraph.checkForCycle()
|
||||
if(cycle.isEmpty())
|
||||
return emptyList()
|
||||
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n"+chain))
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCallStatement): IStatement {
|
||||
val scope = functionCall.definingScope()
|
||||
val targetScope = when(functionCall.targetStatement) {
|
||||
is Subroutine -> functionCall.targetStatement as Subroutine
|
||||
else -> functionCall.targetStatement.definingScope()
|
||||
}
|
||||
callGraph.add(scope, targetScope)
|
||||
return super.process(functionCall)
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
val scope = functionCall.definingScope()
|
||||
val targetScope = when(functionCall.targetStatement) {
|
||||
is Subroutine -> functionCall.targetStatement as Subroutine
|
||||
else -> functionCall.targetStatement.definingScope()
|
||||
}
|
||||
callGraph.add(scope, targetScope)
|
||||
return super.process(functionCall)
|
||||
}
|
||||
}
|
@ -6,6 +6,9 @@ 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
|
||||
|
||||
|
||||
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
||||
|
||||
|
||||
|
@ -38,31 +38,28 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
val function = globalNamespace.lookup(functionCall.target.nameInSource, functionCall)
|
||||
if(function!=null) {
|
||||
val scopedName = when(function) {
|
||||
is Label -> function.makeScopedName(function.name)
|
||||
is Subroutine -> function.makeScopedName(function.name)
|
||||
else -> throw AstException("invalid function call target node type")
|
||||
}
|
||||
globalNamespace.registerUsedName(scopedName.joinToString("."))
|
||||
}
|
||||
val target = globalNamespace.lookup(functionCall.target.nameInSource, functionCall)
|
||||
if(target!=null)
|
||||
used(target)
|
||||
return super.process(functionCall)
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCallStatement): IStatement {
|
||||
val function = globalNamespace.lookup(functionCall.target.nameInSource, functionCall)
|
||||
if(function!=null) {
|
||||
val scopedName = when(function) {
|
||||
is Label -> function.makeScopedName(function.name)
|
||||
is Subroutine -> function.makeScopedName(function.name)
|
||||
else -> throw AstException("invalid function call target node type")
|
||||
}
|
||||
globalNamespace.registerUsedName(scopedName.joinToString("."))
|
||||
}
|
||||
val target = globalNamespace.lookup(functionCall.target.nameInSource, functionCall)
|
||||
if(target!=null)
|
||||
used(target)
|
||||
return super.process(functionCall)
|
||||
}
|
||||
|
||||
override fun process(jump: Jump): IStatement {
|
||||
if(jump.identifier!=null) {
|
||||
val target = globalNamespace.lookup(jump.identifier.nameInSource, jump)
|
||||
if (target != null)
|
||||
used(target)
|
||||
}
|
||||
return super.process(jump)
|
||||
}
|
||||
|
||||
override fun process(ifStatement: IfStatement): IStatement {
|
||||
super.process(ifStatement)
|
||||
val constvalue = ifStatement.condition.constValue(globalNamespace)
|
||||
@ -80,6 +77,15 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
private fun used(stmt: IStatement) {
|
||||
val scopedName = when (stmt) {
|
||||
is Label -> stmt.scopedname
|
||||
is Subroutine -> stmt.scopedname
|
||||
else -> throw AstException("invalid call target node type: ${stmt::class}")
|
||||
}
|
||||
globalNamespace.registerUsedName(scopedName.joinToString("."))
|
||||
}
|
||||
|
||||
fun removeUnusedNodes(usedNames: Set<String>, allScopedSymbolDefinitions: MutableMap<String, IStatement>) {
|
||||
for ((name, value) in allScopedSymbolDefinitions) {
|
||||
if(!usedNames.contains(name)) {
|
||||
|
Loading…
Reference in New Issue
Block a user