mirror of
https://github.com/irmen/prog8.git
synced 2024-06-12 18:58:42 +00:00
189 lines
8.3 KiB
Kotlin
189 lines
8.3 KiB
Kotlin
package prog8.compiler.astprocessing
|
|
|
|
import prog8.ast.IFunctionCall
|
|
import prog8.ast.IPipe
|
|
import prog8.ast.Node
|
|
import prog8.ast.Program
|
|
import prog8.ast.expressions.FunctionCallExpression
|
|
import prog8.ast.expressions.StringLiteral
|
|
import prog8.ast.statements.*
|
|
import prog8.ast.walk.IAstVisitor
|
|
import prog8.code.core.Position
|
|
import prog8.compilerinterface.BuiltinFunctions
|
|
import prog8.compilerinterface.ICompilationTarget
|
|
import prog8.compilerinterface.IErrorReporter
|
|
|
|
|
|
internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|
private val program: Program,
|
|
private val compTarget: ICompilationTarget) : IAstVisitor {
|
|
private var blocks = mutableMapOf<String, Block>()
|
|
|
|
private fun nameError(name: String, position: Position, existing: Statement) {
|
|
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
|
|
}
|
|
|
|
override fun visit(block: Block) {
|
|
if(block.name in compTarget.machine.opcodeNames)
|
|
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
|
|
|
val existing = blocks[block.name]
|
|
if(existing!=null) {
|
|
if(block.isInLibrary)
|
|
nameError(existing.name, existing.position, block)
|
|
else
|
|
nameError(block.name, block.position, existing)
|
|
}
|
|
else
|
|
blocks[block.name] = block
|
|
|
|
super.visit(block)
|
|
}
|
|
|
|
override fun visit(decl: VarDecl) {
|
|
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
|
|
|
if(decl.name in BuiltinFunctions)
|
|
errors.err("builtin function cannot be redefined", decl.position)
|
|
|
|
if(decl.name in compTarget.machine.opcodeNames)
|
|
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
|
|
|
val existing = decl.definingScope.lookup(listOf(decl.name))
|
|
if (existing != null && existing !== decl)
|
|
nameError(decl.name, decl.position, existing)
|
|
|
|
if(decl.definingBlock.name==decl.name)
|
|
nameError(decl.name, decl.position, decl.definingBlock)
|
|
if(decl.definingSubroutine?.name==decl.name)
|
|
nameError(decl.name, decl.position, decl.definingSubroutine!!)
|
|
|
|
super.visit(decl)
|
|
}
|
|
|
|
override fun visit(subroutine: Subroutine) {
|
|
if(subroutine.name in compTarget.machine.opcodeNames) {
|
|
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
|
} else if(subroutine.name in BuiltinFunctions) {
|
|
// the builtin functions can't be redefined
|
|
errors.err("builtin function cannot be redefined", subroutine.position)
|
|
} else {
|
|
// already reported elsewhere:
|
|
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
|
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
|
|
|
val existing = subroutine.lookup(listOf(subroutine.name))
|
|
if (existing != null && existing !== subroutine)
|
|
nameError(subroutine.name, subroutine.position, existing)
|
|
|
|
// check that there are no local symbols (variables, labels, subs) that redefine the subroutine's parameters.
|
|
val symbolsInSub = subroutine.allDefinedSymbols
|
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
|
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
|
val paramsToCheck = paramNames.intersect(namesInSub)
|
|
for(name in paramsToCheck) {
|
|
val symbol = subroutine.searchSymbol(name)
|
|
if(symbol!=null && (symbol as? VarDecl)?.subroutineParameter==null)
|
|
nameError(name, symbol.position, subroutine)
|
|
}
|
|
|
|
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
|
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
|
}
|
|
|
|
if(subroutine.name == subroutine.definingBlock.name) {
|
|
// subroutines cannot have the same name as their enclosing block,
|
|
// because this causes symbol scoping issues in the resulting assembly source
|
|
nameError(subroutine.name, subroutine.position, subroutine.definingBlock)
|
|
}
|
|
}
|
|
|
|
super.visit(subroutine)
|
|
}
|
|
|
|
override fun visit(label: Label) {
|
|
if(label.name in compTarget.machine.opcodeNames)
|
|
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
|
|
|
if(label.name in BuiltinFunctions) {
|
|
// the builtin functions can't be redefined
|
|
errors.err("builtin function cannot be redefined", label.position)
|
|
} else {
|
|
val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name)
|
|
for(el in existing) {
|
|
if(el === label || el.name != label.name)
|
|
continue
|
|
else {
|
|
nameError(label.name, label.position, el)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
super.visit(label)
|
|
}
|
|
|
|
override fun visit(string: StringLiteral) {
|
|
if (string.value.length > 255)
|
|
errors.err("string literal length max is 255", string.position)
|
|
|
|
super.visit(string)
|
|
}
|
|
|
|
override fun visit(functionCallExpr: FunctionCallExpression) = visitFunctionCall(functionCallExpr)
|
|
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
|
|
|
private fun visitFunctionCall(call: IFunctionCall) {
|
|
val isPartOfPipeSegments = (call.parent as? IPipe)?.segments?.contains(call as Node) == true
|
|
val errormessageAboutArgs = if(isPartOfPipeSegments) "invalid number of arguments in piped call" else "invalid number of arguments"
|
|
when (val target = call.target.targetStatement(program)) {
|
|
is Subroutine -> {
|
|
// if the call is part of a Pipe, the number of arguments in the call should be 1 less than the number of parameters
|
|
val expectedNumberOfArgs: Int = if(isPartOfPipeSegments) {
|
|
target.parameters.size - 1
|
|
} else {
|
|
target.parameters.size
|
|
}
|
|
if(call.args.size != expectedNumberOfArgs) {
|
|
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
|
errors.err(errormessageAboutArgs, pos)
|
|
}
|
|
}
|
|
is BuiltinFunctionPlaceholder -> {
|
|
val func = BuiltinFunctions.getValue(target.name)
|
|
// if the call is part of a Pipe, the number of arguments in the call should be 1 less than the number of parameters
|
|
val expectedNumberOfArgs: Int = if(isPartOfPipeSegments) {
|
|
func.parameters.size-1
|
|
} else {
|
|
func.parameters.size
|
|
}
|
|
if(call.args.size != expectedNumberOfArgs) {
|
|
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
|
errors.err(errormessageAboutArgs, pos)
|
|
}
|
|
if(func.name=="memory") {
|
|
val name = call.args[0] as? StringLiteral
|
|
if(name!=null) {
|
|
val processed = name.value.map {
|
|
if(it.isLetterOrDigit())
|
|
it
|
|
else
|
|
'_'
|
|
}.joinToString("")
|
|
call.args[0] = StringLiteral(processed, compTarget.defaultEncoding, name.position)
|
|
call.args[0].linkParents(call as Node)
|
|
}
|
|
}
|
|
}
|
|
is Label -> {
|
|
if(call.args.isNotEmpty()) {
|
|
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
|
errors.err("cannot use arguments when calling a label", pos)
|
|
}
|
|
}
|
|
null -> {}
|
|
else -> errors.err("cannot call this as a subroutine or function", call.target.position)
|
|
}
|
|
}
|
|
}
|