mirror of
https://github.com/irmen/prog8.git
synced 2024-06-01 06:41:42 +00:00
1475 lines
53 KiB
Kotlin
1475 lines
53 KiB
Kotlin
package prog8.ast
|
|
|
|
import prog8.functions.*
|
|
import prog8.parser.prog8Parser
|
|
import org.antlr.v4.runtime.ParserRuleContext
|
|
import org.antlr.v4.runtime.tree.TerminalNode
|
|
import java.nio.file.Paths
|
|
import kotlin.math.floor
|
|
|
|
|
|
/**************************** AST Data classes ****************************/
|
|
|
|
enum class DataType {
|
|
BYTE,
|
|
WORD,
|
|
FLOAT,
|
|
STR,
|
|
STR_P,
|
|
STR_S,
|
|
STR_PS,
|
|
ARRAY,
|
|
ARRAY_W,
|
|
MATRIX
|
|
}
|
|
|
|
enum class Register {
|
|
A,
|
|
X,
|
|
Y,
|
|
AX,
|
|
AY,
|
|
XY
|
|
}
|
|
|
|
enum class Statusflag {
|
|
Pc,
|
|
Pz,
|
|
Pv,
|
|
Pn
|
|
}
|
|
|
|
enum class BranchCondition {
|
|
CS,
|
|
CC,
|
|
EQ,
|
|
NE,
|
|
VS,
|
|
VC,
|
|
MI,
|
|
PL
|
|
}
|
|
|
|
|
|
class FatalAstException (override var message: String) : Exception(message)
|
|
|
|
open class AstException (override var message: String) : Exception(message)
|
|
|
|
class SyntaxError(override var message: String, val position: Position) : AstException(message) {
|
|
override fun toString() = "$position Syntax error: $message"
|
|
}
|
|
|
|
class NameError(override var message: String, val position: Position) : AstException(message) {
|
|
override fun toString() = "$position Name error: $message"
|
|
}
|
|
|
|
open class ExpressionError(message: String, val position: Position) : AstException(message) {
|
|
override fun toString() = "$position Error: $message"
|
|
}
|
|
|
|
class UndefinedSymbolError(symbol: IdentifierReference)
|
|
: ExpressionError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
|
|
|
|
|
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
|
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
|
}
|
|
|
|
|
|
interface IAstProcessor {
|
|
fun process(module: Module) {
|
|
module.statements = module.statements.asSequence().map { it.process(this) }.toMutableList()
|
|
}
|
|
|
|
fun process(expr: PrefixExpression): IExpression {
|
|
expr.expression = expr.expression.process(this)
|
|
return expr
|
|
}
|
|
|
|
fun process(expr: BinaryExpression): IExpression {
|
|
expr.left = expr.left.process(this)
|
|
expr.right = expr.right.process(this)
|
|
return expr
|
|
}
|
|
|
|
fun process(directive: Directive): IStatement {
|
|
return directive
|
|
}
|
|
|
|
fun process(block: Block): IStatement {
|
|
block.statements = block.statements.asSequence().map { it.process(this) }.toMutableList()
|
|
return block
|
|
}
|
|
|
|
fun process(decl: VarDecl): IStatement {
|
|
decl.value = decl.value?.process(this)
|
|
decl.arrayspec?.process(this)
|
|
return decl
|
|
}
|
|
|
|
fun process(subroutine: Subroutine): IStatement {
|
|
subroutine.statements = subroutine.statements.asSequence().map { it.process(this) }.toMutableList()
|
|
return subroutine
|
|
}
|
|
|
|
fun process(functionCall: FunctionCall): IExpression {
|
|
functionCall.arglist = functionCall.arglist.map { it.process(this) }
|
|
return functionCall
|
|
}
|
|
|
|
fun process(functionCall: FunctionCallStatement): IStatement {
|
|
functionCall.arglist = functionCall.arglist.map { it.process(this) }
|
|
return functionCall
|
|
}
|
|
|
|
fun process(identifier: IdentifierReference): IExpression {
|
|
// note: this is an identifier that is used in an expression.
|
|
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
|
|
return identifier
|
|
}
|
|
|
|
fun process(jump: Jump): IStatement {
|
|
return jump
|
|
}
|
|
|
|
fun process(ifStatement: IfStatement): IStatement {
|
|
ifStatement.condition = ifStatement.condition.process(this)
|
|
ifStatement.statements = ifStatement.statements.map { it.process(this) }
|
|
ifStatement.elsepart = ifStatement.elsepart.map { it.process(this) }
|
|
return ifStatement
|
|
}
|
|
|
|
fun process(branchStatement: BranchStatement): IStatement {
|
|
branchStatement.statements = branchStatement.statements.map { it.process(this) }
|
|
branchStatement.elsepart = branchStatement.elsepart.map { it.process(this) }
|
|
return branchStatement
|
|
}
|
|
|
|
fun process(range: RangeExpr): IExpression {
|
|
range.from = range.from.process(this)
|
|
range.to = range.to.process(this)
|
|
return range
|
|
}
|
|
|
|
fun process(label: Label): IStatement {
|
|
return label
|
|
}
|
|
|
|
fun process(literalValue: LiteralValue): LiteralValue {
|
|
return literalValue
|
|
}
|
|
|
|
fun process(assignment: Assignment): IStatement {
|
|
assignment.target = assignment.target.process(this)
|
|
assignment.value = assignment.value.process(this)
|
|
return assignment
|
|
}
|
|
|
|
fun process(postIncrDecr: PostIncrDecr): IStatement {
|
|
postIncrDecr.target = postIncrDecr.target.process(this)
|
|
return postIncrDecr
|
|
}
|
|
}
|
|
|
|
|
|
interface Node {
|
|
val position: Position
|
|
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("builtin::")) {
|
|
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) {
|
|
if(statementScope is INameScope) {
|
|
scope.add(0, statementScope.name)
|
|
}
|
|
statementScope = statementScope.parent
|
|
}
|
|
scope.add(name)
|
|
return scope
|
|
}
|
|
}
|
|
|
|
|
|
interface IFunctionCall {
|
|
var target: IdentifierReference
|
|
var arglist: List<IExpression>
|
|
}
|
|
|
|
|
|
interface INameScope {
|
|
val name: String
|
|
val position: Position
|
|
var statements: MutableList<IStatement>
|
|
|
|
fun usedNames(): Set<String>
|
|
|
|
fun registerUsedName(name: String)
|
|
|
|
fun subScopes() = statements.asSequence().filter { it is INameScope }.map { it as INameScope }.associate { it.name to it }
|
|
|
|
fun labelsAndVariables() = statements.asSequence().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? {
|
|
if(scopedName.size>1) {
|
|
// it's a qualified name, look it up from the namespace root
|
|
var scope: INameScope? = this
|
|
scopedName.dropLast(1).forEach {
|
|
scope = scope?.subScopes()?.get(it)
|
|
if(scope==null)
|
|
return null
|
|
}
|
|
val foundScope : INameScope = scope!!
|
|
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 = statement
|
|
while(statementScope !is ParentSentinel) {
|
|
val localScope = statementScope.definingScope()
|
|
val result = localScope.labelsAndVariables()[scopedName[0]]
|
|
if (result != null)
|
|
return result
|
|
val subscope = localScope.subScopes()[scopedName[0]] as IStatement?
|
|
if (subscope != null)
|
|
return subscope
|
|
// not found in this scope, look one higher up
|
|
statementScope = statementScope.parent
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
|
|
fun debugPrint() {
|
|
fun printNames(indent: Int, namespace: INameScope) {
|
|
println(" ".repeat(4*indent) + "${namespace.name} -> ${namespace::class.simpleName} at ${namespace.position}")
|
|
namespace.labelsAndVariables().forEach {
|
|
println(" ".repeat(4 * (1 + indent)) + "${it.key} -> ${it.value::class.simpleName} at ${it.value.position}")
|
|
}
|
|
namespace.statements.filter { it is INameScope }.forEach {
|
|
printNames(indent+1, it as INameScope)
|
|
}
|
|
}
|
|
printNames(0, this)
|
|
}
|
|
|
|
fun removeStatement(statement: IStatement) {
|
|
// remove a statement (most likely because it is never referenced such as a subroutine)
|
|
val removed = statements.remove(statement)
|
|
if(!removed) throw AstException("node to remove wasn't found")
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
class AnonymousStatementList(override var parent: Node,
|
|
var statements: List<IStatement>,
|
|
override val position: Position) : IStatement {
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
statements.forEach { it.linkParents(this) }
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor): IStatement {
|
|
statements = statements.map { it.process(processor) }
|
|
return this
|
|
}
|
|
}
|
|
|
|
|
|
private object ParentSentinel : Node {
|
|
override val position = Position("<<sentinel>>", 0, 0, 0)
|
|
override var parent: Node = this
|
|
override fun linkParents(parent: Node) {}
|
|
}
|
|
|
|
object BuiltinFunctionScopePlaceholder : INameScope {
|
|
override val name = "<<builtin-functions-scope-placeholder>>"
|
|
override val position = Position("<<placeholder>>", 0, 0, 0)
|
|
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")
|
|
}
|
|
|
|
object BuiltinFunctionStatementPlaceholder : IStatement {
|
|
override val position = Position("<<placeholder>>", 0, 0, 0)
|
|
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>,
|
|
override val position: Position) : Node, INameScope {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent=parent
|
|
}
|
|
|
|
fun linkParents() {
|
|
parent = ParentSentinel
|
|
statements.forEach {it.linkParents(this)}
|
|
}
|
|
|
|
fun process(processor: IAstProcessor) {
|
|
processor.process(this)
|
|
}
|
|
|
|
override fun definingScope(): INameScope = GlobalNamespace("<<<global>>>", statements, position)
|
|
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.start") // main and main.start are 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()}", 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)
|
|
}
|
|
return stmt
|
|
}
|
|
|
|
override fun registerUsedName(name: String) {
|
|
// make sure to also register each scope separately
|
|
scopedNamesUsed.add(name)
|
|
if(name.contains('.'))
|
|
registerUsedName(name.substringBeforeLast('.'))
|
|
}
|
|
}
|
|
|
|
|
|
class Block(override val name: String,
|
|
val address: Int?,
|
|
override var statements: MutableList<IStatement>,
|
|
override val position: Position) : IStatement, INameScope {
|
|
override lateinit var parent: Node
|
|
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
|
|
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
statements.forEach {it.linkParents(this)}
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
|
|
override fun toString(): String {
|
|
return "Block(name=$name, address=$address, ${statements.size} statements)"
|
|
}
|
|
|
|
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 Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
args.forEach{it.linkParents(this)}
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
}
|
|
|
|
|
|
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
}
|
|
}
|
|
|
|
|
|
data class Label(val name: String, override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
|
|
|
|
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)"
|
|
}
|
|
}
|
|
|
|
|
|
class Return(var values: List<IExpression>, override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
values.forEach {it.linkParents(this)}
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor): IStatement {
|
|
values = values.map { it.process(processor) }
|
|
return this
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return "Return(values: $values, pos=$position)"
|
|
}
|
|
}
|
|
|
|
|
|
class ArraySpec(var x: IExpression, var y: IExpression?, override val position: Position) : Node {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
x.linkParents(this)
|
|
y?.linkParents(this)
|
|
}
|
|
|
|
fun process(processor: IAstProcessor) {
|
|
x = x.process(processor)
|
|
y = y?.process(processor)
|
|
}
|
|
}
|
|
|
|
|
|
enum class VarDeclType {
|
|
VAR,
|
|
CONST,
|
|
MEMORY
|
|
}
|
|
|
|
class VarDecl(val type: VarDeclType,
|
|
declaredDatatype: DataType,
|
|
val arrayspec: ArraySpec?,
|
|
val name: String,
|
|
var value: IExpression?,
|
|
override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
|
|
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
|
|
val datatype = when {
|
|
arrayspec == null -> declaredDatatype
|
|
arrayspec.y != null -> when (declaredDatatype) {
|
|
DataType.BYTE -> DataType.MATRIX
|
|
else -> {
|
|
datatypeErrors.add(SyntaxError("matrix can only contain bytes", position))
|
|
DataType.BYTE
|
|
}
|
|
}
|
|
else -> when (declaredDatatype) {
|
|
DataType.BYTE -> DataType.ARRAY
|
|
DataType.WORD -> DataType.ARRAY_W
|
|
else -> {
|
|
datatypeErrors.add(SyntaxError("array can only contain bytes or words", position))
|
|
DataType.BYTE
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
arrayspec?.linkParents(this)
|
|
value?.linkParents(this)
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
|
|
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
|
|
val memorySize: Int
|
|
get() = when(datatype) {
|
|
DataType.BYTE -> 1
|
|
DataType.WORD -> 2
|
|
DataType.FLOAT -> 5 // MFLPT5
|
|
DataType.STR,
|
|
DataType.STR_P,
|
|
DataType.STR_S,
|
|
DataType.STR_PS -> {
|
|
val lv = value as? LiteralValue ?: throw ExpressionError("need constant initializer value expression", position)
|
|
lv.strvalue!!.length + 1
|
|
}
|
|
DataType.ARRAY -> {
|
|
val aX = arrayspec?.x as? LiteralValue ?: throw ExpressionError("need constant value expression for arrayspec", position)
|
|
aX.asIntegerValue!!
|
|
}
|
|
DataType.ARRAY_W -> {
|
|
val aX = arrayspec?.x as? LiteralValue ?: throw ExpressionError("need constant value expression for arrayspec", position)
|
|
2*aX.asIntegerValue!!
|
|
}
|
|
DataType.MATRIX -> {
|
|
val aX = arrayspec?.x as? LiteralValue ?: throw ExpressionError("need constant value expression for arrayspec", position)
|
|
val aY = arrayspec.y as? LiteralValue ?: throw ExpressionError("need constant value expression for arrayspec", position)
|
|
aX.asIntegerValue!! * aY.asIntegerValue!!
|
|
}
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
|
|
}
|
|
}
|
|
|
|
|
|
class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
target.linkParents(this)
|
|
value.linkParents(this)
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
|
|
override fun toString(): String {
|
|
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
|
|
}
|
|
}
|
|
|
|
data class AssignTarget(val register: Register?, val identifier: IdentifierReference?, override val position: Position) : Node {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
identifier?.linkParents(this)
|
|
}
|
|
|
|
fun process(processor: IAstProcessor) = this
|
|
|
|
fun determineDatatype(namespace: INameScope, stmt: IStatement): DataType {
|
|
if(register!=null)
|
|
return when(register){
|
|
Register.A, Register.X, Register.Y -> DataType.BYTE
|
|
Register.AX, Register.AY, Register.XY -> DataType.WORD
|
|
}
|
|
|
|
val symbol = namespace.lookup(identifier!!.nameInSource, stmt)
|
|
if(symbol is VarDecl) return symbol.datatype
|
|
throw FatalAstException("cannot determine datatype of assignment target $this")
|
|
}
|
|
}
|
|
|
|
|
|
interface IExpression: Node {
|
|
fun constValue(namespace: INameScope): LiteralValue?
|
|
fun process(processor: IAstProcessor): IExpression
|
|
fun referencesIdentifier(name: String): Boolean
|
|
fun resultingDatatype(namespace: INameScope): DataType?
|
|
}
|
|
|
|
|
|
// note: some expression elements are mutable, to be able to rewrite/process the expression tree
|
|
|
|
class PrefixExpression(val operator: String, var expression: IExpression, override val position: Position) : IExpression {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
expression.linkParents(this)
|
|
}
|
|
|
|
override fun constValue(namespace: INameScope): LiteralValue? = null
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
|
|
override fun resultingDatatype(namespace: INameScope): DataType? = expression.resultingDatatype(namespace)
|
|
}
|
|
|
|
|
|
class BinaryExpression(var left: IExpression, val operator: String, var right: IExpression, override val position: Position) : IExpression {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
left.linkParents(this)
|
|
right.linkParents(this)
|
|
}
|
|
|
|
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
|
override fun constValue(namespace: INameScope): LiteralValue? = null
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
|
|
override fun resultingDatatype(namespace: INameScope): DataType? {
|
|
val leftDt = left.resultingDatatype(namespace)
|
|
val rightDt = right.resultingDatatype(namespace)
|
|
return when(operator) {
|
|
"+", "-", "*", "/", "**" -> if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt)
|
|
"&" -> leftDt
|
|
"|" -> leftDt
|
|
"^" -> leftDt
|
|
"and", "or", "xor",
|
|
"<", ">",
|
|
"<=", ">=",
|
|
"==", "!=" -> DataType.BYTE
|
|
else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
|
|
}
|
|
}
|
|
|
|
private fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType {
|
|
return when(leftDt) {
|
|
DataType.BYTE -> when(rightDt) {
|
|
DataType.BYTE -> DataType.BYTE
|
|
DataType.WORD -> DataType.WORD
|
|
DataType.FLOAT -> DataType.FLOAT
|
|
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
|
}
|
|
DataType.WORD -> when(rightDt) {
|
|
DataType.BYTE -> DataType.BYTE
|
|
DataType.WORD -> DataType.WORD
|
|
DataType.FLOAT -> DataType.FLOAT
|
|
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
|
}
|
|
DataType.FLOAT -> when(rightDt) {
|
|
DataType.BYTE -> DataType.FLOAT
|
|
DataType.WORD -> DataType.FLOAT
|
|
DataType.FLOAT -> DataType.FLOAT
|
|
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
|
}
|
|
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
|
}
|
|
}
|
|
}
|
|
|
|
private data class ByteOrWordLiteral(val intvalue: Int, val datatype: DataType) {
|
|
fun asWord() = ByteOrWordLiteral(intvalue, DataType.WORD)
|
|
fun asByte() = ByteOrWordLiteral(intvalue, DataType.BYTE)
|
|
}
|
|
|
|
data class LiteralValue(val type: DataType,
|
|
val bytevalue: Short? = null,
|
|
val wordvalue: Int? = null,
|
|
val floatvalue: Double? = null,
|
|
val strvalue: String? = null,
|
|
val arrayvalue: MutableList<IExpression>? = null,
|
|
override val position: Position) : IExpression {
|
|
override lateinit var parent: Node
|
|
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false
|
|
|
|
val isString = type==DataType.STR || type==DataType.STR_P || type==DataType.STR_S || type==DataType.STR_PS
|
|
|
|
companion object {
|
|
fun fromBoolean(bool: Boolean, position: Position) =
|
|
LiteralValue(DataType.BYTE, bytevalue = if(bool) 1 else 0, position=position)
|
|
|
|
fun optimalNumeric(value: Number, position: Position): LiteralValue {
|
|
val floatval = value.toDouble()
|
|
return if(floatval == floor(floatval) && floatval in -32768..65535) {
|
|
// the floating point value is actually an integer.
|
|
when (floatval) {
|
|
// note: we cheat a little here and allow negative integers during expression evaluations
|
|
in -128..255 -> LiteralValue(DataType.BYTE, bytevalue = floatval.toShort(), position = position)
|
|
in -32768..65535 -> LiteralValue(DataType.WORD, wordvalue = floatval.toInt(), position = position)
|
|
else -> throw FatalAstException("integer overflow: $floatval")
|
|
}
|
|
} else {
|
|
LiteralValue(DataType.FLOAT, floatvalue = floatval, position = position)
|
|
}
|
|
}
|
|
}
|
|
|
|
init {
|
|
when(type){
|
|
DataType.BYTE -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
|
|
DataType.WORD -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
|
|
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
|
|
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> if(strvalue==null) throw FatalAstException("literal value missing strvalue")
|
|
DataType.ARRAY, DataType.ARRAY_W -> if(arrayvalue==null) throw FatalAstException("literal value missing arrayvalue")
|
|
DataType.MATRIX -> TODO("matrix literalvalue? for now, arrays are good enough for this")
|
|
}
|
|
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null)
|
|
throw FatalAstException("literal value without actual value")
|
|
}
|
|
|
|
val asNumericValue: Number? = when {
|
|
bytevalue!=null -> bytevalue
|
|
wordvalue!=null -> wordvalue
|
|
floatvalue!=null -> floatvalue
|
|
else -> null
|
|
}
|
|
|
|
val asIntegerValue: Int? = when {
|
|
bytevalue!=null -> bytevalue.toInt()
|
|
wordvalue!=null -> wordvalue
|
|
else -> null
|
|
}
|
|
|
|
val asBooleanValue: Boolean =
|
|
(floatvalue!=null && floatvalue != 0.0) ||
|
|
(bytevalue!=null && bytevalue != 0.toShort()) ||
|
|
(wordvalue!=null && wordvalue != 0) ||
|
|
(strvalue!=null && strvalue.isNotEmpty()) ||
|
|
(arrayvalue != null && arrayvalue.isNotEmpty())
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
arrayvalue?.forEach {it.linkParents(this)}
|
|
}
|
|
|
|
override fun constValue(namespace: INameScope): LiteralValue? = this
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
|
|
override fun toString(): String {
|
|
return "LiteralValue(byte=$bytevalue, word=$wordvalue, float=$floatvalue, str=$strvalue, array=$arrayvalue pos=$position)"
|
|
}
|
|
|
|
override fun resultingDatatype(namespace: INameScope) = type
|
|
}
|
|
|
|
|
|
class RangeExpr(var from: IExpression, var to: IExpression, override val position: Position) : IExpression {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
from.linkParents(this)
|
|
to.linkParents(this)
|
|
}
|
|
|
|
override fun constValue(namespace: INameScope): LiteralValue? = null
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name)
|
|
override fun resultingDatatype(namespace: INameScope): DataType? {
|
|
val fromDt=from.resultingDatatype(namespace)
|
|
val toDt=to.resultingDatatype(namespace)
|
|
return when {
|
|
fromDt==null || toDt==null -> null
|
|
fromDt==DataType.WORD || toDt==DataType.WORD -> DataType.WORD
|
|
fromDt==DataType.STR || toDt==DataType.STR -> DataType.STR
|
|
fromDt==DataType.STR_P || toDt==DataType.STR_P -> DataType.STR_P
|
|
fromDt==DataType.STR_S || toDt==DataType.STR_S -> DataType.STR_S
|
|
fromDt==DataType.STR_PS || toDt==DataType.STR_PS -> DataType.STR_PS
|
|
else -> DataType.BYTE
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class RegisterExpr(val register: Register, override val position: Position) : IExpression {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
}
|
|
|
|
override fun constValue(namespace: INameScope): LiteralValue? = null
|
|
override fun process(processor: IAstProcessor) = this
|
|
override fun referencesIdentifier(name: String): Boolean = false
|
|
|
|
override fun toString(): String {
|
|
return "RegisterExpr(register=$register, pos=$position)"
|
|
}
|
|
|
|
override fun resultingDatatype(namespace: INameScope): DataType? {
|
|
return when(register){
|
|
Register.A, Register.X, Register.Y -> DataType.BYTE
|
|
Register.AX, Register.AY, Register.XY -> DataType.WORD
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : IExpression {
|
|
override lateinit var parent: Node
|
|
|
|
fun targetStatement(namespace: INameScope) =
|
|
if(nameInSource.size==1 && BuiltinFunctionNames.contains(nameInSource[0]))
|
|
BuiltinFunctionStatementPlaceholder
|
|
else
|
|
namespace.lookup(nameInSource, this)
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
}
|
|
|
|
override fun constValue(namespace: INameScope): LiteralValue? {
|
|
val node = namespace.lookup(nameInSource, this)
|
|
?: throw UndefinedSymbolError(this)
|
|
val vardecl = node as? VarDecl
|
|
if(vardecl==null) {
|
|
throw ExpressionError("name should be a constant, instead of: ${node::class.simpleName}", position)
|
|
} else if(vardecl.type!=VarDeclType.CONST) {
|
|
return null
|
|
}
|
|
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?
|
|
|
|
override fun resultingDatatype(namespace: INameScope): DataType? {
|
|
val targetStmt = targetStatement(namespace)
|
|
if(targetStmt is VarDecl) {
|
|
return targetStmt.datatype
|
|
} else {
|
|
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
target.linkParents(this)
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
|
|
override fun toString(): String {
|
|
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
|
|
}
|
|
}
|
|
|
|
|
|
class Jump(val address: Int?, val identifier: IdentifierReference?, override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
identifier?.linkParents(this)
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
|
|
override fun toString(): String {
|
|
return "Jump(addr: $address, identifier: $identifier, target: pos=$position)"
|
|
}
|
|
}
|
|
|
|
|
|
class FunctionCall(override var target: IdentifierReference,
|
|
override var arglist: List<IExpression>,
|
|
override val position: Position) : IExpression, IFunctionCall {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
target.linkParents(this)
|
|
arglist.forEach { it.linkParents(this) }
|
|
}
|
|
|
|
override fun constValue(namespace: INameScope) = constValue(namespace, true)
|
|
|
|
private fun constValue(namespace: INameScope, withDatatypeCheck: Boolean): LiteralValue? {
|
|
// if the function is a built-in function and the args are consts, should try to const-evaluate!
|
|
if(target.nameInSource.size>1) return null
|
|
try {
|
|
val resultValue = when (target.nameInSource[0]) {
|
|
"sin" -> builtinSin(arglist, position, namespace)
|
|
"cos" -> builtinCos(arglist, position, namespace)
|
|
"abs" -> builtinAbs(arglist, position, namespace)
|
|
"acos" -> builtinAcos(arglist, position, namespace)
|
|
"asin" -> builtinAsin(arglist, position, namespace)
|
|
"tan" -> builtinTan(arglist, position, namespace)
|
|
"atan" -> builtinAtan(arglist, position, namespace)
|
|
"log" -> builtinLog(arglist, position, namespace)
|
|
"log10" -> builtinLog10(arglist, position, namespace)
|
|
"sqrt" -> builtinSqrt(arglist, position, namespace)
|
|
"max" -> builtinMax(arglist, position, namespace)
|
|
"min" -> builtinMin(arglist, position, namespace)
|
|
"round" -> builtinRound(arglist, position, namespace)
|
|
"rad" -> builtinRad(arglist, position, namespace)
|
|
"deg" -> builtinDeg(arglist, position, namespace)
|
|
"sum" -> builtinSum(arglist, position, namespace)
|
|
"avg" -> builtinAvg(arglist, position, namespace)
|
|
"len" -> builtinLen(arglist, position, namespace)
|
|
"lsb" -> builtinLsb(arglist, position, namespace)
|
|
"msb" -> builtinMsb(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 ExpressionError("builtin function rol can't be used in expressions because it doesn't return a value", position)
|
|
"rol2" -> throw ExpressionError("builtin function rol2 can't be used in expressions because it doesn't return a value", position)
|
|
"ror" -> throw ExpressionError("builtin function ror can't be used in expressions because it doesn't return a value", position)
|
|
"ror2" -> throw ExpressionError("builtin function ror2 can't be used in expressions because it doesn't return a value", position)
|
|
"P_carry" -> throw ExpressionError("builtin function P_carry can't be used in expressions because it doesn't return a value", position)
|
|
"P_irqd" -> throw ExpressionError("builtin function P_irqd can't be used in expressions because it doesn't return a value", position)
|
|
else -> null
|
|
}
|
|
if(withDatatypeCheck) {
|
|
val resultDt = this.resultingDatatype(namespace)
|
|
if(resultValue==null || resultDt == resultValue.type)
|
|
return resultValue
|
|
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
|
|
} else {
|
|
return resultValue
|
|
}
|
|
}
|
|
catch(x: NotConstArgumentException) {
|
|
// const-evaluating the builtin function call failed.
|
|
return null
|
|
}
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return "FunctionCall(target=$target, pos=$position)"
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
|
|
|
|
override fun resultingDatatype(namespace: INameScope): DataType? {
|
|
val constVal = constValue(namespace, false)
|
|
if(constVal!=null)
|
|
return constVal.resultingDatatype(namespace)
|
|
val stmt = target.targetStatement(namespace)
|
|
if(stmt is BuiltinFunctionStatementPlaceholder) {
|
|
if(target.nameInSource[0] == "P_carry" || target.nameInSource[0]=="P_irqd") {
|
|
return null // these have no return value
|
|
}
|
|
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, namespace)
|
|
}
|
|
else if(stmt is Subroutine) {
|
|
if(stmt.returnvalues.isEmpty()) {
|
|
return null // no return value
|
|
}
|
|
if(stmt.returnvalues.size==1) {
|
|
return when(stmt.returnvalues[0].register) {
|
|
Register.A, Register.X, Register.Y -> DataType.BYTE
|
|
Register.AX, Register.AY, Register.XY -> DataType.WORD
|
|
else -> TODO("return type for non-register result from subroutine $stmt")
|
|
}
|
|
}
|
|
TODO("return type for subroutine with multiple return values $stmt")
|
|
}
|
|
TODO("datatype of functioncall to $stmt")
|
|
}
|
|
}
|
|
|
|
|
|
class FunctionCallStatement(override var target: IdentifierReference,
|
|
override var arglist: List<IExpression>,
|
|
override val position: Position) : IStatement, IFunctionCall {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
target.linkParents(this)
|
|
arglist.forEach { it.linkParents(this) }
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
|
|
override fun toString(): String {
|
|
return "FunctionCall(target=$target, pos=$position)"
|
|
}
|
|
}
|
|
|
|
|
|
class InlineAssembly(val assembly: String, override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = this
|
|
}
|
|
|
|
|
|
class Subroutine(override val name: String,
|
|
val parameters: List<SubroutineParameter>,
|
|
val returnvalues: List<SubroutineReturnvalue>,
|
|
val address: Int?,
|
|
override var statements: MutableList<IStatement>,
|
|
override val position: Position) : IStatement, INameScope {
|
|
override lateinit var parent: Node
|
|
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
|
|
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
parameters.forEach { it.linkParents(this) }
|
|
returnvalues.forEach { it.linkParents(this) }
|
|
statements.forEach { it.linkParents(this) }
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor) = processor.process(this)
|
|
|
|
override fun toString(): String {
|
|
return "Subroutine(name=$name, address=$address, parameters=$parameters, returnvalues=$returnvalues, ${statements.size} statements)"
|
|
}
|
|
|
|
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 SubroutineParameter(val name: String,
|
|
val register: Register?,
|
|
val statusflag: Statusflag?,
|
|
override val position: Position) : Node {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
}
|
|
}
|
|
|
|
|
|
data class SubroutineReturnvalue(val register: Register?,
|
|
val statusflag: Statusflag?,
|
|
val clobbered: Boolean,
|
|
override val position: Position) : Node {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
}
|
|
}
|
|
|
|
|
|
class IfStatement(var condition: IExpression,
|
|
var statements: List<IStatement>,
|
|
var elsepart: List<IStatement>,
|
|
override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
condition.linkParents(this)
|
|
statements.forEach { it.linkParents(this) }
|
|
elsepart.forEach { it.linkParents(this) }
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
|
|
}
|
|
|
|
|
|
class BranchStatement(var condition: BranchCondition,
|
|
var statements: List<IStatement>,
|
|
var elsepart: List<IStatement>,
|
|
override val position: Position) : IStatement {
|
|
override lateinit var parent: Node
|
|
|
|
override fun linkParents(parent: Node) {
|
|
this.parent = parent
|
|
statements.forEach { it.linkParents(this) }
|
|
elsepart.forEach { it.linkParents(this) }
|
|
}
|
|
|
|
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
|
|
|
|
override fun toString(): String {
|
|
return "Branch(cond: $condition, ${statements.size} stmts, ${elsepart.size} else-stmts, pos=$position)"
|
|
}
|
|
}
|
|
|
|
|
|
/***************** Antlr Extension methods to create AST ****************/
|
|
|
|
fun prog8Parser.ModuleContext.toAst(name: String) : Module =
|
|
Module(name, modulestatement().asSequence().map { it.toAst() }.toMutableList(), toPosition())
|
|
|
|
|
|
/************** Helper extension methods (private) ************/
|
|
|
|
private fun ParserRuleContext.toPosition() : Position {
|
|
val file = Paths.get(this.start.inputStream.sourceName).fileName.toString()
|
|
// note: be ware of TAB characters in the source text, they count as 1 column...
|
|
return Position(file, start.line, start.charPositionInLine, stop.charPositionInLine+stop.text.length)
|
|
}
|
|
|
|
|
|
private fun prog8Parser.ModulestatementContext.toAst() : IStatement {
|
|
val directive = directive()?.toAst()
|
|
if(directive!=null) return directive
|
|
|
|
val block = block()?.toAst()
|
|
if(block!=null) return block
|
|
|
|
throw FatalAstException(text)
|
|
}
|
|
|
|
|
|
private fun prog8Parser.BlockContext.toAst() : IStatement =
|
|
Block(identifier().text, integerliteral()?.toAst()?.intvalue, statement_block().toAst(), toPosition())
|
|
|
|
|
|
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<IStatement> =
|
|
statement().asSequence().map { it.toAst() }.toMutableList()
|
|
|
|
|
|
private fun prog8Parser.StatementContext.toAst() : IStatement {
|
|
vardecl()?.let {
|
|
return VarDecl(VarDeclType.VAR,
|
|
it.datatype().toAst(),
|
|
it.arrayspec()?.toAst(),
|
|
it.identifier().text,
|
|
null,
|
|
it.toPosition())
|
|
}
|
|
|
|
varinitializer()?.let {
|
|
return VarDecl(VarDeclType.VAR,
|
|
it.datatype().toAst(),
|
|
it.arrayspec()?.toAst(),
|
|
it.identifier().text,
|
|
it.expression().toAst(),
|
|
it.toPosition())
|
|
}
|
|
|
|
constdecl()?.let {
|
|
val cvarinit = it.varinitializer()
|
|
return VarDecl(VarDeclType.CONST,
|
|
cvarinit.datatype().toAst(),
|
|
cvarinit.arrayspec()?.toAst(),
|
|
cvarinit.identifier().text,
|
|
cvarinit.expression().toAst(),
|
|
cvarinit.toPosition())
|
|
}
|
|
|
|
memoryvardecl()?.let {
|
|
val mvarinit = it.varinitializer()
|
|
return VarDecl(VarDeclType.MEMORY,
|
|
mvarinit.datatype().toAst(),
|
|
mvarinit.arrayspec()?.toAst(),
|
|
mvarinit.identifier().text,
|
|
mvarinit.expression().toAst(),
|
|
mvarinit.toPosition())
|
|
}
|
|
|
|
assignment()?.let {
|
|
return Assignment(it.assign_target().toAst(),null, it.expression().toAst(), it.toPosition())
|
|
}
|
|
|
|
augassignment()?.let {
|
|
return Assignment(it.assign_target().toAst(),
|
|
it.operator.text,
|
|
it.expression().toAst(),
|
|
it.toPosition())
|
|
}
|
|
|
|
postincrdecr()?.let {
|
|
return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition())
|
|
}
|
|
|
|
val directive = directive()?.toAst()
|
|
if(directive!=null) return directive
|
|
|
|
val label = labeldef()?.toAst()
|
|
if(label!=null) return label
|
|
|
|
val jump = unconditionaljump()?.toAst()
|
|
if(jump!=null) return jump
|
|
|
|
val fcall = functioncall_stmt()?.toAst()
|
|
if(fcall!=null) return fcall
|
|
|
|
val ifstmt = if_stmt()?.toAst()
|
|
if(ifstmt!=null) return ifstmt
|
|
|
|
val returnstmt = returnstmt()?.toAst()
|
|
if(returnstmt!=null) return returnstmt
|
|
|
|
val sub = subroutine()?.toAst()
|
|
if(sub!=null) return sub
|
|
|
|
val asm = inlineasm()?.toAst()
|
|
if(asm!=null) return asm
|
|
|
|
val branchstmt = branch_stmt()?.toAst()
|
|
if(branchstmt!=null) return branchstmt
|
|
|
|
throw FatalAstException("unprocessed source text: $text")
|
|
}
|
|
|
|
private fun prog8Parser.Functioncall_stmtContext.toAst(): IStatement {
|
|
val location =
|
|
if(identifier()!=null) identifier()?.toAst()
|
|
else scoped_identifier()?.toAst()
|
|
return if(expression_list() ==null)
|
|
FunctionCallStatement(location!!, emptyList(), toPosition())
|
|
else
|
|
FunctionCallStatement(location!!, expression_list().toAst(), toPosition())
|
|
}
|
|
|
|
|
|
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
|
val location =
|
|
if(identifier()!=null) identifier()?.toAst()
|
|
else scoped_identifier()?.toAst()
|
|
return if(expression_list() ==null)
|
|
FunctionCall(location!!, emptyList(), toPosition())
|
|
else
|
|
FunctionCall(location!!, expression_list().toAst(), toPosition())
|
|
}
|
|
|
|
|
|
private fun prog8Parser.InlineasmContext.toAst(): IStatement =
|
|
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
|
|
|
|
|
private fun prog8Parser.ReturnstmtContext.toAst() : IStatement {
|
|
val values = expression_list()
|
|
return Return(values?.toAst() ?: emptyList(), toPosition())
|
|
}
|
|
|
|
private fun prog8Parser.UnconditionaljumpContext.toAst(): IStatement {
|
|
|
|
val address = integerliteral()?.toAst()?.intvalue
|
|
val identifier =
|
|
if(identifier()!=null) identifier()?.toAst()
|
|
else scoped_identifier()?.toAst()
|
|
|
|
return Jump(address, identifier, toPosition())
|
|
}
|
|
|
|
|
|
private fun prog8Parser.LabeldefContext.toAst(): IStatement =
|
|
Label(children[0].text, toPosition())
|
|
|
|
|
|
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
|
return Subroutine(identifier().text,
|
|
if(sub_params() ==null) emptyList() else sub_params().toAst(),
|
|
if(sub_returns() ==null) emptyList() else sub_returns().toAst(),
|
|
sub_address()?.integerliteral()?.toAst()?.intvalue,
|
|
if(statement_block() ==null) mutableListOf() else statement_block().toAst(),
|
|
toPosition())
|
|
}
|
|
|
|
|
|
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
|
sub_param().map {
|
|
SubroutineParameter(it.identifier().text, it.register()?.toAst(), it.statusflag()?.toAst(), it.toPosition())
|
|
}
|
|
|
|
|
|
private fun prog8Parser.Sub_returnsContext.toAst(): List<SubroutineReturnvalue> =
|
|
sub_return().map {
|
|
val isClobber = it.childCount==2 && it.children[1].text == "?"
|
|
SubroutineReturnvalue(it.register()?.toAst(), it.statusflag()?.toAst(), isClobber, it.toPosition())
|
|
}
|
|
|
|
|
|
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
|
val register = register()?.toAst()
|
|
val identifier = identifier()
|
|
return if(identifier!=null)
|
|
AssignTarget(register, identifier.toAst(), toPosition())
|
|
else
|
|
AssignTarget(register, scoped_identifier()?.toAst(), toPosition())
|
|
}
|
|
|
|
|
|
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
|
|
|
|
private fun prog8Parser.StatusflagContext.toAst() = Statusflag.valueOf(text)
|
|
|
|
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
|
|
|
|
|
|
private fun prog8Parser.ArrayspecContext.toAst() : ArraySpec =
|
|
ArraySpec(expression(0).toAst(), if (expression().size > 1) expression(1).toAst() else null, toPosition())
|
|
|
|
|
|
private fun prog8Parser.DirectiveContext.toAst() : Directive =
|
|
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
|
|
|
|
|
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg =
|
|
DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.intvalue, toPosition())
|
|
|
|
|
|
private fun prog8Parser.IntegerliteralContext.toAst(): ByteOrWordLiteral {
|
|
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): ByteOrWordLiteral {
|
|
val integer: Int
|
|
var datatype = DataType.BYTE
|
|
if(radix==10) {
|
|
integer = text.toInt()
|
|
if(integer in 256..65535)
|
|
datatype = DataType.WORD
|
|
} else if(radix==2) {
|
|
if(text.length>8)
|
|
datatype = DataType.WORD
|
|
integer = text.toInt(2)
|
|
} else if(radix==16) {
|
|
if(text.length>2)
|
|
datatype = DataType.WORD
|
|
integer = text.toInt(16)
|
|
} else {
|
|
throw FatalAstException("invalid radix")
|
|
}
|
|
return ByteOrWordLiteral(integer, if(forceWord) DataType.WORD else datatype)
|
|
}
|
|
val terminal: TerminalNode = children[0] as TerminalNode
|
|
val integerPart = this.intpart.text
|
|
return when (terminal.symbol.type) {
|
|
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null)
|
|
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null)
|
|
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null)
|
|
else -> throw FatalAstException(terminal.text)
|
|
}
|
|
}
|
|
|
|
|
|
private fun prog8Parser.ExpressionContext.toAst() : IExpression {
|
|
|
|
val litval = literalvalue()
|
|
if(litval!=null) {
|
|
val booleanlit = litval.booleanliteral()?.toAst()
|
|
return if(booleanlit!=null) {
|
|
LiteralValue.fromBoolean(booleanlit, litval.toPosition())
|
|
}
|
|
else {
|
|
val intLit = litval.integerliteral()?.toAst()
|
|
when {
|
|
intLit!=null -> when(intLit.datatype) {
|
|
DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.intvalue.toShort(), position = litval.toPosition())
|
|
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.intvalue, position = litval.toPosition())
|
|
else -> throw FatalAstException("invalid datatype for integer literal")
|
|
}
|
|
litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition())
|
|
litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = litval.stringliteral().text, position = litval.toPosition())
|
|
litval.arrayliteral()!=null -> LiteralValue(DataType.ARRAY, arrayvalue = litval.arrayliteral()?.toAst(), position = litval.toPosition())
|
|
// @todo byte/word array difference needed for literal array values?
|
|
else -> throw FatalAstException("invalid parsed literal")
|
|
}
|
|
}
|
|
}
|
|
|
|
if(register()!=null)
|
|
return RegisterExpr(register().toAst(), register().toPosition())
|
|
|
|
if(identifier()!=null)
|
|
return identifier().toAst()
|
|
|
|
if(scoped_identifier()!=null)
|
|
return scoped_identifier().toAst()
|
|
|
|
if(bop!=null)
|
|
return BinaryExpression(left.toAst(), bop.text, right.toAst(), toPosition())
|
|
|
|
if(prefix!=null)
|
|
return PrefixExpression(prefix.text, expression(0).toAst(), toPosition())
|
|
|
|
val funcall = functioncall()?.toAst()
|
|
if(funcall!=null) return funcall
|
|
|
|
if (rangefrom!=null && rangeto!=null)
|
|
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), toPosition())
|
|
|
|
if(childCount==3 && children[0].text=="(" && children[2].text==")")
|
|
return expression(0).toAst() // expression within ( )
|
|
|
|
throw FatalAstException(text)
|
|
}
|
|
|
|
|
|
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
|
|
|
|
|
|
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
|
|
IdentifierReference(listOf(text), toPosition())
|
|
|
|
|
|
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
|
|
IdentifierReference(NAME().map { it.text }, toPosition())
|
|
|
|
|
|
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
|
|
|
|
|
|
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
|
|
"true" -> true
|
|
"false" -> false
|
|
else -> throw FatalAstException(text)
|
|
}
|
|
|
|
|
|
private fun prog8Parser.ArrayliteralContext.toAst() =
|
|
expression().asSequence().map { it.toAst() }.toMutableList()
|
|
|
|
|
|
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
|
|
val condition = expression().toAst()
|
|
val statements = statement_block()?.toAst() ?: listOf(statement().toAst())
|
|
val elsepart = else_part()?.toAst() ?: emptyList()
|
|
return IfStatement(condition, statements, elsepart, toPosition())
|
|
}
|
|
|
|
private fun prog8Parser.Else_partContext.toAst(): List<IStatement> {
|
|
return statement_block()?.toAst() ?: listOf(statement().toAst())
|
|
}
|
|
|
|
|
|
private fun prog8Parser.Branch_stmtContext.toAst(): IStatement {
|
|
val branchcondition = branchcondition().toAst()
|
|
val statements = statement_block()?.toAst() ?: listOf(statement().toAst())
|
|
val elsepart = else_part()?.toAst() ?: emptyList()
|
|
return BranchStatement(branchcondition, statements, elsepart, toPosition())
|
|
}
|
|
|
|
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|