recursion checking and bugfix in subroutine usage determination

This commit is contained in:
Irmen de Jong 2018-09-03 23:19:25 +02:00
parent 6ed6a3a552
commit 76d07a2de8
9 changed files with 313 additions and 106 deletions

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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)

View 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)
}
}

View File

@ -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")

View File

@ -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)) {