restructuring of the AST package

This commit is contained in:
Irmen de Jong 2019-07-08 13:30:28 +02:00
parent e84bb8d94a
commit 5a27b035b0
44 changed files with 3389 additions and 3244 deletions

View File

@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="irmen" />
</component>

View File

@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />
<option name="processComments" value="false" />
</inspection_tool>
</profile>
</component>

View File

@ -1,6 +1,12 @@
package prog8
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.base.checkIdentifiers
import prog8.ast.base.checkRecursion
import prog8.ast.base.checkValid
import prog8.ast.base.reorderStatements
import prog8.ast.statements.Directive
import prog8.astvm.AstVm
import prog8.compiler.*
import prog8.compiler.target.c64.AsmGen

File diff suppressed because it is too large Load Diff

View File

@ -1,120 +0,0 @@
package prog8.ast
/**
* Checks for the occurrence of recursive subroutine calls
*/
internal fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace)
checker.process(this)
printErrors(checker.result(), name)
}
private 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 { println(" $it") }
}
val cycle = checkForCycle()
if(cycle.isNotEmpty()) {
println("CYCLIC! $cycle")
}
}
fun checkForCycle(): MutableList<VT> {
val visited = uniqueVertices.associateWith { false }.toMutableMap()
val recStack = uniqueVertices.associateWith { 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
}
}
private class AstRecursionChecker(private val namespace: INameScope) : IAstProcessor {
private val callGraph = DirectedGraph<INameScope>()
internal 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 (a subroutine call in) $chain"))
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val scope = functionCallStatement.definingScope()
val targetStatement = functionCallStatement.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
return super.process(functionCallStatement)
}
override fun process(functionCall: FunctionCall): IExpression {
val scope = functionCall.definingScope()
val targetStatement = functionCall.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
return super.process(functionCall)
}
}

View File

@ -0,0 +1,92 @@
package prog8.ast
import prog8.ast.base.FatalAstException
import prog8.ast.base.NameError
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.statements.Block
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctions
import java.nio.file.Path
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>) {
val namespace = GlobalNamespace(modules)
val heap = HeapValues()
val loadAddress: Int
get() = modules.first().loadAddress
fun entrypoint(): Subroutine? {
val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
if(mainBlocks.size > 1)
throw FatalAstException("more than one 'main' block")
return if(mainBlocks.isEmpty()) {
null
} else {
mainBlocks[0].subScopes()["start"] as Subroutine?
}
}
}
class Module(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position,
val isLibraryModule: Boolean,
val source: Path) : Node, INameScope {
override lateinit var parent: Node
lateinit var program: Program
val importedBy = mutableListOf<Module>()
val imports = mutableSetOf<Module>()
var loadAddress: Int = 0 // can be set with the %address directive
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach {it.linkParents(this)}
}
override fun definingScope(): INameScope = program.namespace
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
}
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<IStatement>()
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(this) }
}
override fun lookup(scopedName: List<String>, localContext: Node): IStatement? {
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
// builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
}
}
}
object BuiltinFunctionScopePlaceholder : INameScope {
override val name = "<<builtin-functions-scope-placeholder>>"
override val position = Position("<<placeholder>>", 0, 0, 0)
override var statements = mutableListOf<IStatement>()
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
}

View File

@ -0,0 +1,194 @@
package prog8.ast
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.*
interface Node {
val position: Position
var parent: Node // will be linked correctly later (late init)
fun linkParents(parent: Node)
fun definingModule(): Module {
if(this is Module)
return this
return findParentNode<Module>(this)!!
}
fun definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
fun definingScope(): INameScope {
val scope = findParentNode<INameScope>(this)
if(scope!=null) {
return scope
}
if(this is Label && this.name.startsWith("builtin::")) {
return BuiltinFunctionScopePlaceholder
}
if(this is GlobalNamespace)
return this
throw FatalAstException("scope missing from $this")
}
}
interface IStatement : Node {
fun process(processor: IAstProcessor) : IStatement
fun makeScopedName(name: String): String {
// easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
// and like this, we can cache the name even,
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
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
}
if(name.isNotEmpty())
scope.add(name)
return scope.joinToString(".")
}
val expensiveToInline: Boolean
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
}
interface IFunctionCall {
var target: IdentifierReference
var arglist: MutableList<IExpression>
}
interface INameScope {
val name: String
val position: Position
val statements: MutableList<IStatement>
val parent: Node
fun linkParents(parent: Node)
fun subScopes(): Map<String, INameScope> {
val subscopes = mutableMapOf<String, INameScope>()
for(stmt in statements) {
when(stmt) {
is INameScope -> subscopes[stmt.name] = stmt
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
}
}
return subscopes
}
fun getLabelOrVariable(name: String): IStatement? {
// TODO this is called A LOT and could perhaps be optimized a bit more, but adding a cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
}
return null
}
fun allDefinedSymbols(): List<Pair<String, IStatement>> {
return statements.mapNotNull {
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
}
fun lookup(scopedName: List<String>, localContext: Node) : IStatement? {
if(scopedName.size>1) {
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScopes()?.get(name)
if(scope==null)
break
}
if(scope!=null) {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScopes()[scopedName.last()] as IStatement?
}
}
return null
} else {
// unqualified name, find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope()
val result = localScope.getLabelOrVariable(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 containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: IStatement) {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
}
}
interface IExpression: Node {
fun constValue(program: Program): LiteralValue?
fun process(processor: IAstProcessor): IExpression
fun referencesIdentifier(name: String): Boolean
fun inferType(program: Program): DataType?
infix fun isSameAs(other: IExpression): Boolean {
if(this===other)
return true
when(this) {
is RegisterExpr ->
return (other is RegisterExpr && other.register==register)
is IdentifierReference ->
return (other is IdentifierReference && other.nameInSource==nameInSource)
is PrefixExpression ->
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
is BinaryExpression ->
return (other is BinaryExpression && other.operator==operator
&& other.left isSameAs left
&& other.right isSameAs right)
is ArrayIndexedExpression -> {
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
}
is LiteralValue -> return (other is LiteralValue && other==this)
}
return false
}
}

View File

@ -0,0 +1,572 @@
package prog8.ast.antlr
import org.antlr.v4.runtime.IntStream
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import java.io.CharConversionException
import java.io.File
import java.nio.file.Path
import prog8.compiler.target.c64.Petscii
import prog8.parser.CustomLexer
import prog8.parser.prog8Parser
/***************** Antlr Extension methods to create AST ****************/
private data class NumericLiteral(val number: Number, val datatype: DataType)
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source)
}
private fun ParserRuleContext.toPosition() : Position {
val customTokensource = this.start.tokenSource as? CustomLexer
val filename =
when {
customTokensource!=null -> customTokensource.modulePath.fileName.toString()
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
else -> File(start.inputStream.sourceName).name
}
// note: be ware of TAB characters in the source text, they count as 1 column...
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
}
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : IStatement {
val directive = directive()?.toAst()
if(directive!=null) return directive
val block = block()?.toAst(isInLibrary)
if(block!=null) return block
throw FatalAstException(text)
}
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : IStatement =
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, 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.ZEROPAGE() != null,
it.arrayindex()?.toAst(),
it.identifier().text,
null,
it.ARRAYSIG() != null || it.arrayindex() != null,
false,
it.toPosition())
}
varinitializer()?.let {
val vd = it.vardecl()
return VarDecl(VarDeclType.VAR,
vd.datatype().toAst(),
vd.ZEROPAGE() != null,
vd.arrayindex()?.toAst(),
vd.identifier().text,
it.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
it.toPosition())
}
constdecl()?.let {
val cvarinit = it.varinitializer()
val vd = cvarinit.vardecl()
return VarDecl(VarDeclType.CONST,
vd.datatype().toAst(),
vd.ZEROPAGE() != null,
vd.arrayindex()?.toAst(),
vd.identifier().text,
cvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
cvarinit.toPosition())
}
memoryvardecl()?.let {
val mvarinit = it.varinitializer()
val vd = mvarinit.vardecl()
return VarDecl(VarDeclType.MEMORY,
vd.datatype().toAst(),
vd.ZEROPAGE() != null,
vd.arrayindex()?.toAst(),
vd.identifier().text,
mvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
mvarinit.toPosition())
}
assignment()?.let {
return Assignment(it.assign_targets().toAst(), null, it.expression().toAst(), it.toPosition())
}
augassignment()?.let {
return Assignment(listOf(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
val forloop = forloop()?.toAst()
if(forloop!=null) return forloop
val repeatloop = repeatloop()?.toAst()
if(repeatloop!=null) return repeatloop
val whileloop = whileloop()?.toAst()
if(whileloop!=null) return whileloop
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val asmsubstmt = asmsubroutine()?.toAst()
if(asmsubstmt!=null) return asmsubstmt
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun prog8Parser.Assign_targetsContext.toAst(): List<AssignTarget> = assign_target().map { it.toAst() }
private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement {
val name = identifier().text
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
val params = asmsub_params()?.toAst() ?: emptyList()
val returns = asmsub_returns()?.toAst() ?: emptyList()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalReturnvalues = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val clobbers = clobber()?.toAst() ?: emptySet()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(name, normalParameters, normalReturnvalues,
paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition())
}
private class AsmSubroutineParameter(name: String,
type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
val stack: Boolean,
position: Position) : SubroutineParameter(name, type, position)
private class AsmSubroutineReturn(val type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
val stack: Boolean,
val position: Position)
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
= this.register().asSequence().map { it.toAst() }.toSet()
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map {
AsmSubroutineParameter(it.vardecl().identifier().text, it.vardecl().datatype().toAst(),
it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition())
}
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
private fun prog8Parser.Functioncall_stmtContext.toAst(): IStatement {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), toPosition())
else
FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCall(location, mutableListOf(), toPosition())
else
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun prog8Parser.InlineasmContext.toAst() =
InlineAssembly(INLINEASMBLOCK().text, toPosition())
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
val values = expression_list()
return Return(values?.toAst() ?: emptyList(), toPosition())
}
private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
val address = integerliteral()?.toAst()?.number?.toInt()
val identifier = scoped_identifier()?.toAst()
return Jump(address, identifier, null, toPosition())
}
private fun prog8Parser.LabeldefContext.toAst(): IStatement =
Label(children[0].text, toPosition())
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
sub_return_part()?.toAst() ?: emptyList(),
emptyList(),
emptyList(),
emptySet(),
null,
false,
statement_block()?.toAst() ?: mutableListOf(),
toPosition())
}
private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
val returns = sub_returns() ?: return emptyList()
return returns.datatype().map { it.toAst() }
}
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
SubroutineParameter(it.identifier().text, it.datatype().toAst(), it.toPosition())
}
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
val register = register()?.toAst()
val identifier = scoped_identifier()
return when {
register!=null -> AssignTarget(register, null, null, null, toPosition())
identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition())
arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition())
}
}
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), 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()?.number?.toInt(), toPosition())
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
val integer: Int
var datatype = DataType.UBYTE
when (radix) {
10 -> {
integer = try {
text.toInt()
} catch(x: NumberFormatException) {
throw AstException("${toPosition()} invalid decimal literal ${x.message}")
}
datatype = when(integer) {
in 0..255 -> DataType.UBYTE
in -128..127 -> DataType.BYTE
in 0..65535 -> DataType.UWORD
in -32768..32767 -> DataType.WORD
else -> DataType.FLOAT
}
}
2 -> {
if(text.length>8)
datatype = DataType.UWORD
try {
integer = text.toInt(2)
} catch(x: NumberFormatException) {
throw AstException("${toPosition()} invalid binary literal ${x.message}")
}
}
16 -> {
if(text.length>2)
datatype = DataType.UWORD
try {
integer = text.toInt(16)
} catch(x: NumberFormatException) {
throw AstException("${toPosition()} invalid hexadecimal literal ${x.message}")
}
}
else -> throw FatalAstException("invalid radix")
}
return NumericLiteral(integer, if (forceWord) DataType.UWORD 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.UBYTE -> LiteralValue(DataType.UBYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = intLit.number.toDouble(), position = litval.toPosition())
else -> throw FatalAstException("invalid datatype for numeric literal")
}
litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition())
litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
litval.charliteral()!=null -> {
try {
LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], position = litval.toPosition())
} catch (ce: CharConversionException) {
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
}
}
litval.arrayliteral()!=null -> {
val array = litval.arrayliteral()?.toAst()
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFolder takes care of that and converts the type if needed.
LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition())
}
else -> throw FatalAstException("invalid parsed literal")
}
}
}
if(register()!=null)
return RegisterExpr(register().toAst(), register().toPosition())
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) {
val step = rangestep?.toAst() ?: LiteralValue(DataType.UBYTE, 1, position = toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
}
if(childCount==3 && children[0].text=="(" && children[2].text==")")
return expression(0).toAst() // expression within ( )
if(arrayindexed()!=null)
return arrayindexed().toAst()
if(typecast()!=null)
return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition())
if(directmemory()!=null)
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
throw FatalAstException(text)
}
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(),
arrayindex().toAst(),
toPosition())
}
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() : Array<IExpression> =
expression().map { it.toAst() }.toTypedArray()
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
val condition = expression().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return IfStatement(condition, trueScope, elseScope, toPosition())
}
private fun prog8Parser.Else_partContext.toAst(): MutableList<IStatement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
}
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return BranchStatement(branchcondition, trueScope, elseScope, toPosition())
}
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst()
val datatype = datatype()?.toAst()
val zeropage = ZEROPAGE()!=null
val loopvar = identifier()?.toAst()
val iterable = expression()!!.toAst()
val scope =
if(statement()!=null)
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
else
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
return ForLoop(loopregister, datatype, zeropage, loopvar, iterable, scope, toPosition())
}
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return WhileLoop(condition, scope, toPosition())
}
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
val untilCondition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return RepeatLoop(scope, untilCondition, toPosition())
}
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
internal fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>()
val iter = str.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {
result.add(c)
}
}
return result.joinToString("")
}

View File

@ -0,0 +1,130 @@
package prog8.ast.base
import prog8.ast.Node
/**************************** AST Data classes ****************************/
enum class DataType {
UBYTE,
BYTE,
UWORD,
WORD,
FLOAT,
STR,
STR_S,
ARRAY_UB,
ARRAY_B,
ARRAY_UW,
ARRAY_W,
ARRAY_F;
/**
* is the type assignable to the given other type?
*/
infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision?
when(this) {
UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
UWORD -> targetType == UWORD || targetType == FLOAT
WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT
FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S
STR_S -> targetType == STR || targetType==STR_S
in ArrayDatatypes -> targetType === this
else -> false
}
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
infix fun biggerThan(other: DataType) =
when(this) {
in ByteDatatypes -> false
in WordDatatypes -> other in ByteDatatypes
else -> true
}
}
enum class Register {
A,
X,
Y
}
enum class RegisterOrPair {
A,
X,
Y,
AX,
AY,
XY
} // only used in parameter and return value specs in asm subroutines
enum class Statusflag {
Pc,
Pz,
Pv,
Pn
}
enum class BranchCondition {
CS,
CC,
EQ,
Z,
NE,
NZ,
VS,
VC,
MI,
NEG,
PL,
POS
}
enum class VarDeclType {
VAR,
CONST,
MEMORY
}
val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val ArrayElementTypes = mapOf(
DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE,
DataType.ARRAY_W to DataType.WORD,
DataType.ARRAY_UW to DataType.UWORD,
DataType.ARRAY_F to DataType.FLOAT)
// 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
}
object ParentSentinel : Node {
override val position = Position("<<sentinel>>", 0, 0, 0)
override var parent: Node = this
override fun linkParents(parent: Node) {}
}
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}]"
}

View File

@ -0,0 +1,37 @@
package prog8.ast.base
import prog8.parser.ParsingFailedError
fun printErrors(errors: List<Any>, moduleName: String) {
val reportedMessages = mutableSetOf<String>()
print("\u001b[91m") // bright red
errors.forEach {
val msg = it.toString()
if(msg !in reportedMessages) {
System.err.println(msg)
reportedMessages.add(msg)
}
}
print("\u001b[0m") // reset color
if(reportedMessages.isNotEmpty())
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
}
fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
print("\u001b[93m") // bright yellow
print("$position Warning: $msg")
if(detailInfo==null)
print("\n")
else
println(": $detailInfo\n")
print("\u001b[0m") // normal
}
fun printWarning(msg: String) {
print("\u001b[93m") // bright yellow
print("Warning: $msg")
print("\u001b[0m\n") // normal
}

View File

@ -0,0 +1,22 @@
package prog8.ast.base
import prog8.ast.expressions.IdentifierReference
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)

View File

@ -0,0 +1,84 @@
package prog8.ast.base
import prog8.ast.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.compiler.CompilationOptions
// the name of the subroutine that should be called for every block to initialize its variables
internal const val initvarsSubName="prog8_init_vars"
// prefix for literal values that are turned into a variable on the heap
internal const val autoHeapValuePrefix = "auto_heap_value_"
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(this, compilerOptions)
checker.process(this)
printErrors(checker.result(), name)
}
internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
initvalueCreator.process(this)
val checker = StatementReorderer(this)
checker.process(this)
}
internal fun Module.checkImportedValid() {
val checker = ImportedAstChecker()
checker.process(this)
printErrors(checker.result(), name)
}
internal fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace)
checker.process(this)
printErrors(checker.result(), name)
}
internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(namespace)
checker.process(this)
if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
// add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable
for (variable in checker.anonymousVariablesFromHeap.values) {
val scope = variable.first.definingScope()
scope.statements.add(variable.second)
val parent = variable.first.parent
when {
parent is Assignment && parent.value === variable.first -> {
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.value = idref
}
parent is IFunctionCall -> {
val parameterPos = parent.arglist.indexOf(variable.first)
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.arglist[parameterPos] = idref
}
parent is ForLoop -> {
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.iterable = idref
}
else -> TODO("replace literalvalue by identifierref: $variable (in $parent)")
}
variable.second.linkParents(scope as Node)
}
printErrors(checker.result(), name)
}

View File

@ -0,0 +1,793 @@
package prog8.ast.expressions
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.RuntimeValue
import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import kotlin.math.abs
import kotlin.math.floor
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(program: Program): LiteralValue? = null
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
override fun inferType(program: Program): DataType? = expression.inferType(program)
override fun toString(): String {
return "Prefix($operator $expression)"
}
}
class BinaryExpression(var left: IExpression, var 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)
}
override fun toString(): String {
return "[$left $operator $right]"
}
// binary expression should actually have been optimized away into a single value, before const value was requested...
override fun constValue(program: Program): LiteralValue? = null
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
override fun inferType(program: Program): DataType? {
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
return when (operator) {
"+", "-", "*", "**", "%" -> if (leftDt == null || rightDt == null) null else {
try {
arithmeticOpDt(leftDt, rightDt)
} catch (x: FatalAstException) {
null
}
}
"/" -> if (leftDt == null || rightDt == null) null else divisionOpDt(leftDt, rightDt)
"&" -> leftDt
"|" -> leftDt
"^" -> leftDt
"and", "or", "xor",
"<", ">",
"<=", ">=",
"==", "!=" -> DataType.UBYTE
"<<", ">>" -> leftDt
else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
}
}
companion object {
fun divisionOpDt(leftDt: DataType, rightDt: DataType): DataType {
return when (leftDt) {
DataType.UBYTE -> when (rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UBYTE
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.BYTE
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.BYTE -> when (rightDt) {
in NumericDatatypes -> DataType.BYTE
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.UWORD -> when (rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
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) {
in NumericDatatypes -> DataType.WORD
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.FLOAT -> when (rightDt) {
in NumericDatatypes -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
}
fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType {
return when (leftDt) {
DataType.UBYTE -> when (rightDt) {
DataType.UBYTE -> DataType.UBYTE
DataType.BYTE -> DataType.BYTE
DataType.UWORD -> DataType.UWORD
DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.BYTE -> when (rightDt) {
in ByteDatatypes -> DataType.BYTE
in WordDatatypes -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.UWORD -> when (rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
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) {
in IntegerDatatypes -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.FLOAT -> when (rightDt) {
in NumericDatatypes -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
}
}
fun commonDatatype(leftDt: DataType, rightDt: DataType,
left: IExpression, right: IExpression): Pair<DataType, IExpression?> {
// byte + byte -> byte
// byte + word -> word
// word + byte -> word
// word + word -> word
// a combination with a float will be float (but give a warning about this!)
if(this.operator=="/") {
// division is a bit weird, don't cast the operands
val commondt = divisionOpDt(leftDt, rightDt)
return Pair(commondt, null)
}
return when (leftDt) {
DataType.UBYTE -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.UBYTE, null)
DataType.BYTE -> Pair(DataType.BYTE, left)
DataType.UWORD -> Pair(DataType.UWORD, left)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
}
}
DataType.BYTE -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.BYTE, right)
DataType.BYTE -> Pair(DataType.BYTE, null)
DataType.UWORD -> Pair(DataType.WORD, left)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
}
}
DataType.UWORD -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.UWORD, right)
DataType.BYTE -> Pair(DataType.UWORD, right)
DataType.UWORD -> Pair(DataType.UWORD, null)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
}
}
DataType.WORD -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.WORD, right)
DataType.BYTE -> Pair(DataType.WORD, right)
DataType.UWORD -> Pair(DataType.WORD, right)
DataType.WORD -> Pair(DataType.WORD, null)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
}
}
DataType.FLOAT -> {
Pair(DataType.FLOAT, right)
}
else -> throw FatalAstException("non-numeric datatype $leftDt")
}
}
}
class ArrayIndexedExpression(val identifier: IdentifierReference,
var arrayspec: ArrayIndex,
override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.linkParents(this)
arrayspec.linkParents(this)
}
override fun constValue(program: Program): LiteralValue? = null
override fun process(processor: IAstProcessor): IExpression = processor.process(this)
override fun referencesIdentifier(name: String) = identifier.referencesIdentifier(name)
override fun inferType(program: Program): DataType? {
val target = identifier.targetStatement(program.namespace)
if (target is VarDecl) {
return when (target.datatype) {
in NumericDatatypes -> null
in StringDatatypes -> DataType.UBYTE
DataType.ARRAY_UB -> DataType.UBYTE
DataType.ARRAY_B -> DataType.BYTE
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
else -> throw FatalAstException("invalid dt")
}
}
return null
}
override fun toString(): String {
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
}
}
class TypecastExpression(var expression: IExpression, var type: DataType, val implicit: Boolean, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
expression.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
override fun inferType(program: Program): DataType? = type
override fun constValue(program: Program): LiteralValue? {
val cv = expression.constValue(program) ?: return null
val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
return LiteralValue.fromNumber(value.numericValue(), value.type, position)
}
override fun toString(): String {
return "Typecast($expression as $type)"
}
}
data class AddressOf(val identifier: IdentifierReference, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.parent=this
}
var scopedname: String? = null // will be set in a later state by the compiler
override fun constValue(program: Program): LiteralValue? = null
override fun referencesIdentifier(name: String) = false
override fun inferType(program: Program) = DataType.UWORD
override fun process(processor: IAstProcessor) = processor.process(this)
}
class DirectMemoryRead(var addressExpression: IExpression, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
this.addressExpression.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = false
override fun inferType(program: Program): DataType? = DataType.UBYTE
override fun constValue(program: Program): LiteralValue? = null
override fun toString(): String {
return "DirectMemoryRead($addressExpression)"
}
}
class DirectMemoryWrite(var addressExpression: IExpression, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
this.addressExpression.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = false
override fun inferType(program: Program): DataType? = DataType.UBYTE
override fun constValue(program: Program): LiteralValue? = null
override fun toString(): String {
return "DirectMemoryWrite($addressExpression)"
}
}
open class LiteralValue(val type: DataType,
val bytevalue: Short? = null,
val wordvalue: Int? = null,
val floatvalue: Double? = null,
val strvalue: String? = null,
val arrayvalue: Array<IExpression>? = null,
initHeapId: Int? =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 in StringDatatypes
val isNumeric = type in NumericDatatypes
val isArray = type in ArrayDatatypes
var heapId = initHeapId
private set
companion object {
fun fromBoolean(bool: Boolean, position: Position) =
LiteralValue(DataType.UBYTE, bytevalue = if (bool) 1 else 0, position = position)
fun fromNumber(value: Number, type: DataType, position: Position) : LiteralValue {
return when(type) {
in ByteDatatypes -> LiteralValue(type, bytevalue = value.toShort(), position = position)
in WordDatatypes -> LiteralValue(type, wordvalue = value.toInt(), position = position)
DataType.FLOAT -> LiteralValue(type, floatvalue = value.toDouble(), position = position)
else -> throw FatalAstException("non numeric datatype")
}
}
fun optimalNumeric(value: Number, position: Position): LiteralValue {
return if(value is Double) {
LiteralValue(DataType.FLOAT, floatvalue = value, position = position)
} else {
when (val intval = value.toInt()) {
in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = intval.toShort(), position = position)
in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = intval.toShort(), position = position)
in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = intval, position = position)
in -32768..32767 -> LiteralValue(DataType.WORD, wordvalue = intval, position = position)
else -> LiteralValue(DataType.FLOAT, floatvalue = intval.toDouble(), position = position)
}
}
}
fun optimalInteger(value: Number, position: Position): LiteralValue {
val intval = value.toInt()
if(intval.toDouble() != value.toDouble())
throw FatalAstException("value is not an integer: $value")
return when (intval) {
in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position)
in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = value.toShort(), position = position)
in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = value.toInt(), position = position)
else -> throw FatalAstException("integer overflow: $value")
}
}
}
init {
when(type){
in ByteDatatypes -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
in WordDatatypes -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
in StringDatatypes ->
if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
in ArrayDatatypes ->
if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId")
else -> throw FatalAstException("invalid type $type")
}
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==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
// don't round a float value, otherwise code will not detect that it's not an integer
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(program: Program): LiteralValue? {
if(arrayvalue!=null) {
for(v in arrayvalue) {
if(v.constValue(program)==null) return null
}
}
return this
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
val vstr = when(type) {
DataType.UBYTE -> "ubyte:$bytevalue"
DataType.BYTE -> "byte:$bytevalue"
DataType.UWORD -> "uword:$wordvalue"
DataType.WORD -> "word:$wordvalue"
DataType.FLOAT -> "float:$floatvalue"
in StringDatatypes -> "str:$strvalue"
in ArrayDatatypes -> "array:$arrayvalue"
else -> throw FatalAstException("weird datatype")
}
return "LiteralValue($vstr)"
}
override fun inferType(program: Program) = type
override fun hashCode(): Int {
val bh = bytevalue?.hashCode() ?: 0x10001234
val wh = wordvalue?.hashCode() ?: 0x01002345
val fh = floatvalue?.hashCode() ?: 0x00103456
val sh = strvalue?.hashCode() ?: 0x00014567
val ah = arrayvalue?.hashCode() ?: 0x11119876
var hash = bh * 31 xor wh
hash = hash*31 xor fh
hash = hash*31 xor sh
hash = hash*31 xor ah
hash = hash*31 xor type.hashCode()
return hash
}
override fun equals(other: Any?): Boolean {
if(other==null || other !is LiteralValue)
return false
if(isNumeric && other.isNumeric)
return asNumericValue?.toDouble()==other.asNumericValue?.toDouble()
if(isArray && other.isArray)
return arrayvalue!!.contentEquals(other.arrayvalue!!) && heapId==other.heapId
if(isString && other.isString)
return strvalue==other.strvalue && heapId==other.heapId
if(type!=other.type)
return false
return compareTo(other) == 0
}
operator fun compareTo(other: LiteralValue): Int {
val numLeft = asNumericValue?.toDouble()
val numRight = other.asNumericValue?.toDouble()
if(numLeft!=null && numRight!=null)
return numLeft.compareTo(numRight)
if(strvalue!=null && other.strvalue!=null)
return strvalue.compareTo(other.strvalue)
throw ExpressionError("cannot order compare type $type with ${other.type}", other.position)
}
fun intoDatatype(targettype: DataType): LiteralValue? {
if(type==targettype)
return this
when(type) {
DataType.UBYTE -> {
if(targettype== DataType.BYTE && bytevalue!! <= 127)
return LiteralValue(targettype, bytevalue = bytevalue, position = position)
if(targettype== DataType.WORD || targettype== DataType.UWORD)
return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position)
if(targettype== DataType.FLOAT)
return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position)
}
DataType.BYTE -> {
if(targettype== DataType.UBYTE && bytevalue!! >= 0)
return LiteralValue(targettype, bytevalue = bytevalue, position = position)
if(targettype== DataType.UWORD && bytevalue!! >= 0)
return LiteralValue(targettype, wordvalue = bytevalue.toInt(), position = position)
if(targettype== DataType.WORD)
return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position)
if(targettype== DataType.FLOAT)
return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position)
}
DataType.UWORD -> {
if(targettype== DataType.BYTE && wordvalue!! <= 127)
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
if(targettype== DataType.UBYTE && wordvalue!! <= 255)
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
if(targettype== DataType.WORD && wordvalue!! <= 32767)
return LiteralValue(targettype, wordvalue = wordvalue, position = position)
if(targettype== DataType.FLOAT)
return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position)
}
DataType.WORD -> {
if(targettype== DataType.BYTE && wordvalue!! in -128..127)
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
if(targettype== DataType.UBYTE && wordvalue!! in 0..255)
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
if(targettype== DataType.UWORD && wordvalue!! >=0)
return LiteralValue(targettype, wordvalue = wordvalue, position = position)
if(targettype== DataType.FLOAT)
return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position)
}
DataType.FLOAT -> {
if(floor(floatvalue!!) ==floatvalue) {
val value = floatvalue.toInt()
if (targettype == DataType.BYTE && value in -128..127)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.UBYTE && value in 0..255)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.WORD && value in -32768..32767)
return LiteralValue(targettype, wordvalue = value, position = position)
if (targettype == DataType.UWORD && value in 0..65535)
return LiteralValue(targettype, wordvalue = value, position = position)
}
}
in StringDatatypes -> {
if(targettype in StringDatatypes)
return this
}
else -> {}
}
return null // invalid type conversion from $this to $targettype
}
fun addToHeap(heap: HeapValues) {
if(heapId==null) {
if (strvalue != null) {
heapId = heap.addString(type, strvalue)
}
else if (arrayvalue!=null) {
if(arrayvalue.any {it is AddressOf }) {
val intArrayWithAddressOfs = arrayvalue.map {
when (it) {
is AddressOf -> IntegerOrAddressOf(null, it)
is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null)
else -> throw FatalAstException("invalid datatype in array")
}
}
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
} else {
val valuesInArray = arrayvalue.map { (it as LiteralValue).asNumericValue!! }
heapId = if(type== DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray()
heap.addDoublesArray(doubleArray)
} else {
val integerArray = valuesInArray.map { it.toInt() }
heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
}
}
}
}
}
}
class RangeExpr(var from: IExpression,
var to: IExpression,
var step: 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)
step.linkParents(this)
}
override fun constValue(program: Program): LiteralValue? = null
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name)
override fun inferType(program: Program): DataType? {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
return when {
fromDt==null || toDt==null -> null
fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.UBYTE
fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.UWORD
fromDt== DataType.STR && toDt== DataType.STR -> DataType.STR
fromDt== DataType.STR_S && toDt== DataType.STR_S -> DataType.STR_S
fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.WORD
fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.BYTE
else -> DataType.UBYTE
}
}
override fun toString(): String {
return "RangeExpr(from $from, to $to, step $step, pos=$position)"
}
fun size(): Int? {
val fromLv = (from as? LiteralValue)
val toLv = (to as? LiteralValue)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange()?.count()
}
fun toConstantIntegerRange(): IntProgression? {
val fromLv = from as? LiteralValue
val toLv = to as? LiteralValue
if(fromLv==null || toLv==null)
return null // non-constant range
val fromVal: Int
val toVal: Int
if(fromLv.isString && toLv.isString) {
// string range -> int range over petscii values
fromVal = Petscii.encodePetscii(fromLv.strvalue!!, true)[0].toInt()
toVal = Petscii.encodePetscii(toLv.strvalue!!, true)[0].toInt()
} else {
// integer range
fromVal = (from as LiteralValue).asIntegerValue!!
toVal = (to as LiteralValue).asIntegerValue!!
}
val stepVal = (step as? LiteralValue)?.asIntegerValue ?: 1
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
}
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(program: Program): 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 inferType(program: Program) = DataType.UBYTE
}
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 && nameInSource[0] in BuiltinFunctions)
BuiltinFunctionStatementPlaceholder(nameInSource[0], position)
else
namespace.lookup(nameInSource, this)
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun constValue(program: Program): LiteralValue? {
val node = program.namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl
if(vardecl==null) {
return null
} else if(vardecl.type!= VarDeclType.CONST) {
return null
}
return vardecl.value?.constValue(program)
}
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 inferType(program: Program): DataType? {
val targetStmt = targetStatement(program.namespace)
if(targetStmt is VarDecl) {
return targetStmt.datatype
} else {
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
}
}
fun heapId(namespace: INameScope): Int {
val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this)
return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap: $this")
}
}
class FunctionCall(override var target: IdentifierReference,
override var arglist: MutableList<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(program: Program) = constValue(program, true)
private fun constValue(program: Program, withDatatypeCheck: Boolean): LiteralValue? {
// if the function is a built-in function and the args are consts, should try to const-evaluate!
// lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1) return null
try {
var resultValue: LiteralValue? = null
val func = BuiltinFunctions[target.nameInSource[0]]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null)
resultValue = exprfunc(arglist, position, program)
else if(func.returntype==null)
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
}
if(withDatatypeCheck) {
val resultDt = this.inferType(program)
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 inferType(program: Program): DataType? {
val constVal = constValue(program ,false)
if(constVal!=null)
return constVal.type
val stmt = target.targetStatement(program.namespace) ?: return null
when (stmt) {
is BuiltinFunctionStatementPlaceholder -> {
if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" ||
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return null // these have no return value
}
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program)
}
is Subroutine -> {
if(stmt.returntypes.isEmpty())
return null // no return value
if(stmt.returntypes.size==1)
return stmt.returntypes[0]
return null // has multiple return types... so not a single resulting datatype possible
}
is Label -> return null
}
return null // calling something we don't recognise...
}
}

View File

@ -1,58 +1,19 @@
package prog8.ast
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.base.printWarning
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.CompilationOptions
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import prog8.functions.BuiltinFunctions
import prog8.parser.ParsingFailedError
import java.io.File
/**
* General checks on the Ast
*/
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(this, compilerOptions)
checker.process(this)
printErrors(checker.result(), name)
}
fun printErrors(errors: List<Any>, moduleName: String) {
val reportedMessages = mutableSetOf<String>()
print("\u001b[91m") // bright red
errors.forEach {
val msg = it.toString()
if(msg !in reportedMessages) {
System.err.println(msg)
reportedMessages.add(msg)
}
}
print("\u001b[0m") // reset color
if(reportedMessages.isNotEmpty())
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
}
fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
print("\u001b[93m") // bright yellow
print("$position Warning: $msg")
if(detailInfo==null)
print("\n")
else
println(": $detailInfo\n")
print("\u001b[0m") // normal
}
fun printWarning(msg: String) {
print("\u001b[93m") // bright yellow
print("Warning: $msg")
print("\u001b[0m\n") // normal
}
private class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions) : IAstProcessor {
internal class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf()
private val heapStringSentinel: Int
init {
@ -142,7 +103,7 @@ private class AstChecker(private val program: Program,
for (rv in expectedReturnValues.withIndex().zip(returnStmt.values)) {
val valueDt=rv.second.inferType(program)
if(rv.first.value!=valueDt)
checkResult.add(ExpressionError("type $valueDt of return value #${rv.first.index+1} doesn't match subroutine return type ${rv.first.value}", rv.second.position))
checkResult.add(ExpressionError("type $valueDt of return value #${rv.first.index + 1} doesn't match subroutine return type ${rv.first.value}", rv.second.position))
}
return super.process(returnStmt)
}
@ -158,36 +119,36 @@ private class AstChecker(private val program: Program,
if (forLoop.loopRegister != null) {
printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position)
// loop register
if (iterableDt != DataType.UBYTE && iterableDt!=DataType.ARRAY_UB && iterableDt !in StringDatatypes)
if (iterableDt != DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt !in StringDatatypes)
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
} else {
// loop variable
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
if(loopvar==null || loopvar.type==VarDeclType.CONST) {
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position))
} else {
when (loopvar.datatype) {
DataType.UBYTE -> {
if(iterableDt!=DataType.UBYTE && iterableDt!=DataType.ARRAY_UB && iterableDt !in StringDatatypes)
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt !in StringDatatypes)
checkResult.add(ExpressionError("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position))
}
DataType.UWORD -> {
if(iterableDt!=DataType.UBYTE && iterableDt!=DataType.UWORD && iterableDt !in StringDatatypes &&
iterableDt !=DataType.ARRAY_UB && iterableDt!=DataType.ARRAY_UW)
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt !in StringDatatypes &&
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
checkResult.add(ExpressionError("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position))
}
DataType.BYTE -> {
if(iterableDt!=DataType.BYTE && iterableDt!=DataType.ARRAY_B)
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
checkResult.add(ExpressionError("byte loop variable can only loop over bytes", forLoop.position))
}
DataType.WORD -> {
if(iterableDt!=DataType.BYTE && iterableDt!=DataType.WORD &&
iterableDt !=DataType.ARRAY_B && iterableDt!=DataType.ARRAY_W)
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.WORD &&
iterableDt != DataType.ARRAY_B && iterableDt!= DataType.ARRAY_W)
checkResult.add(ExpressionError("word loop variable can only loop over bytes or words", forLoop.position))
}
DataType.FLOAT -> {
if(iterableDt!=DataType.FLOAT && iterableDt != DataType.ARRAY_F)
if(iterableDt!= DataType.FLOAT && iterableDt != DataType.ARRAY_F)
checkResult.add(ExpressionError("float loop variable can only loop over floats", forLoop.position))
}
else -> checkResult.add(ExpressionError("loop variable must be numeric type", forLoop.position))
@ -351,7 +312,7 @@ private class AstChecker(private val program: Program,
// This is not easy to fix because strings and arrays are treated a bit simplistic (a "virtual" pointer to the value on the heap)
// while passing them as subroutine parameters would require a "real" pointer OR copying the VALUE to the subroutine's parameter variable (which is very inefficient).
// For now, don't pass strings and arrays as parameters and instead create the workaround as suggested in the error message below.
if(!subroutine.parameters.all{it.type in NumericDatatypes}) {
if(!subroutine.parameters.all{it.type in NumericDatatypes }) {
err("Non-asm subroutine can only take numerical parameters (no str/array types) for now. Workaround (for nested subroutine): access the variable from the outer scope directly.")
}
}
@ -494,19 +455,19 @@ private class AstChecker(private val program: Program,
}
// CONST can only occur on simple types (byte, word, float)
if(decl.type==VarDeclType.CONST) {
if(decl.type== VarDeclType.CONST) {
if (decl.datatype !in NumericDatatypes)
err("const modifier can only be used on numeric types (byte, word, float)")
}
// FLOATS
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!=VarDeclType.MEMORY) {
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY) {
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", decl.position))
}
// ARRAY without size specifier MUST have an iterable initializer value
if(decl.isArray && decl.arraysize==null) {
if(decl.type==VarDeclType.MEMORY)
if(decl.type== VarDeclType.MEMORY)
checkResult.add(SyntaxError("memory mapped array must have a size specification", decl.position))
if(decl.value==null) {
checkResult.add(SyntaxError("array variable is missing a size specification or an initialization value", decl.position))
@ -528,15 +489,15 @@ private class AstChecker(private val program: Program,
// initialize numeric var with value zero by default.
val litVal =
when {
decl.datatype in ByteDatatypes -> LiteralValue(decl.datatype, bytevalue=0, position = decl.position)
decl.datatype in WordDatatypes -> LiteralValue(decl.datatype, wordvalue=0, position = decl.position)
else -> LiteralValue(decl.datatype, floatvalue=0.0, position = decl.position)
decl.datatype in ByteDatatypes -> LiteralValue(decl.datatype, bytevalue = 0, position = decl.position)
decl.datatype in WordDatatypes -> LiteralValue(decl.datatype, wordvalue = 0, position = decl.position)
else -> LiteralValue(decl.datatype, floatvalue = 0.0, position = decl.position)
}
litVal.parent = decl
decl.value = litVal
}
decl.type==VarDeclType.VAR -> {
val litVal = LiteralValue(decl.datatype, initHeapId = heapStringSentinel, position=decl.position) // point to the sentinel heap value instead
decl.type== VarDeclType.VAR -> {
val litVal = LiteralValue(decl.datatype, initHeapId = heapStringSentinel, position = decl.position) // point to the sentinel heap value instead
litVal.parent=decl
decl.value = litVal
}
@ -728,7 +689,7 @@ private class AstChecker(private val program: Program,
if(divisor==0.0)
checkResult.add(ExpressionError("division by zero", expr.right.position))
if(expr.operator=="%") {
if ((rightDt != DataType.UBYTE && rightDt != DataType.UWORD) || (leftDt!=DataType.UBYTE && leftDt!=DataType.UWORD))
if ((rightDt != DataType.UBYTE && rightDt != DataType.UWORD) || (leftDt!= DataType.UBYTE && leftDt!= DataType.UWORD))
checkResult.add(ExpressionError("remainder can only be used on unsigned integer operands", expr.right.position))
}
}
@ -864,7 +825,7 @@ private class AstChecker(private val program: Program,
val argDt = arg.first.value.inferType(program)
if(argDt!=null && !(argDt isAssignableTo arg.second.type)) {
// for asm subroutines having STR param it's okay to provide a UWORD too (pointer value)
if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt==DataType.UWORD))
if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt== DataType.UWORD))
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position))
}
@ -878,13 +839,13 @@ private class AstChecker(private val program: Program,
val asmParamReg = target.asmParameterRegisters[arg.first.index]
if(asmParamReg.statusflag!=null) {
if(argDt !in ByteDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be byte type for statusflag", position))
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for statusflag", position))
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if(argDt !in ByteDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be byte type for single register", position))
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for single register", position))
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if(argDt !in WordDatatypes+ IterableDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be word type for register pair", position))
if(argDt !in WordDatatypes + IterableDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position))
}
}
}
@ -899,7 +860,7 @@ private class AstChecker(private val program: Program,
if(target==null) {
checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position))
} else {
if(target !is VarDecl || target.type==VarDeclType.CONST) {
if(target !is VarDecl || target.type== VarDeclType.CONST) {
checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position))
} else if(target.datatype !in NumericDatatypes) {
checkResult.add(SyntaxError("can only increment or decrement a byte/float/word variable", postIncrDecr.position))
@ -945,7 +906,7 @@ private class AstChecker(private val program: Program,
// check index value 0..255
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program)
if(dtx!=DataType.UBYTE && dtx!=DataType.BYTE)
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position))
return super.process(arrayIndexedExpression)
@ -1121,7 +1082,7 @@ private class AstChecker(private val program: Program,
value.arrayvalue.map {it.constValue(program)?.asNumericValue!!.toDouble()}.toDoubleArray()
else
heap.get(value.heapId!!).doubleArray!!
if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE})
if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE })
return err("floating point value overflow")
return true
}
@ -1190,26 +1151,26 @@ private class AstChecker(private val program: Program,
checkResult.add(SyntaxError("can't assign a range value", position))
val result = when(targetDatatype) {
DataType.BYTE -> sourceDatatype==DataType.BYTE
DataType.UBYTE -> sourceDatatype==DataType.UBYTE
DataType.WORD -> sourceDatatype==DataType.BYTE || sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.WORD
DataType.UWORD -> sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.UWORD
DataType.BYTE -> sourceDatatype== DataType.BYTE
DataType.UBYTE -> sourceDatatype== DataType.UBYTE
DataType.WORD -> sourceDatatype== DataType.BYTE || sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.WORD
DataType.UWORD -> sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.UWORD
DataType.FLOAT -> sourceDatatype in NumericDatatypes
DataType.STR -> sourceDatatype==DataType.STR
DataType.STR_S -> sourceDatatype==DataType.STR_S
DataType.STR -> sourceDatatype== DataType.STR
DataType.STR_S -> sourceDatatype== DataType.STR_S
else -> checkResult.add(SyntaxError("cannot assign new value to variable of type $targetDatatype", position))
}
if(result)
return true
if((sourceDatatype==DataType.UWORD || sourceDatatype==DataType.WORD) && (targetDatatype==DataType.UBYTE || targetDatatype==DataType.BYTE)) {
if((sourceDatatype== DataType.UWORD || sourceDatatype== DataType.WORD) && (targetDatatype== DataType.UBYTE || targetDatatype== DataType.BYTE)) {
if(assignTargets.size==2 && assignTargets[0].register!=null && assignTargets[1].register!=null)
return true // for asm subroutine calls that return a (U)WORD that's going to be stored into two BYTES (registers), we make an exception.
else
checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position))
}
else if(sourceDatatype==DataType.FLOAT && targetDatatype in IntegerDatatypes)
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position))
else
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position))

View File

@ -1,54 +1,14 @@
package prog8.ast
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.base.autoHeapValuePrefix
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
/**
* Checks the validity of all identifiers (no conflicts)
* Also makes sure that subroutine's parameters also become local variable decls in the subroutine's scope.
* Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly.
*/
internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(namespace)
checker.process(this)
if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
// add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable
for (variable in checker.anonymousVariablesFromHeap.values) {
val scope = variable.first.definingScope()
scope.statements.add(variable.second)
val parent = variable.first.parent
when {
parent is Assignment && parent.value === variable.first -> {
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.value = idref
}
parent is IFunctionCall -> {
val parameterPos = parent.arglist.indexOf(variable.first)
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.arglist[parameterPos] = idref
}
parent is ForLoop -> {
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.iterable = idref
}
else -> TODO("replace literalvalue by identifierref: $variable (in $parent)")
}
variable.second.linkParents(scope as Node)
}
printErrors(checker.result(), name)
}
private class AstIdentifiersChecker(private val namespace: INameScope) : IAstProcessor {
internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf()
private var blocks: MutableMap<String, Block> = mutableMapOf()
@ -202,7 +162,7 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
}
override fun process(assignTarget: AssignTarget): AssignTarget {
if(assignTarget.register==Register.X)
if(assignTarget.register== Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
return super.process(assignTarget)
}
@ -254,5 +214,3 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
}
}
internal const val autoHeapValuePrefix = "auto_heap_value_"

View File

@ -0,0 +1,117 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.AstException
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
internal class AstRecursionChecker(private val namespace: INameScope) : IAstProcessor {
private val callGraph = DirectedGraph<INameScope>()
internal 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 (a subroutine call in) $chain"))
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val scope = functionCallStatement.definingScope()
val targetStatement = functionCallStatement.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
return super.process(functionCallStatement)
}
override fun process(functionCall: FunctionCall): IExpression {
val scope = functionCall.definingScope()
val targetStatement = functionCall.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
return super.process(functionCall)
}
private 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 { println(" $it") }
}
val cycle = checkForCycle()
if(cycle.isNotEmpty()) {
println("CYCLIC! $cycle")
}
}
fun checkForCycle(): MutableList<VT> {
val visited = uniqueVertices.associateWith { false }.toMutableMap()
val recStack = uniqueVertices.associateWith { 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
}
}
}

View File

@ -0,0 +1,197 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstProcessor {
fun process(program: Program) {
program.modules.forEach { process(it) }
}
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.arraysize?.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 {
val newtarget = functionCall.target.process(this)
if(newtarget is IdentifierReference)
functionCall.target = newtarget
functionCall.arglist = functionCall.arglist.map { it.process(this) }.toMutableList()
return functionCall
}
fun process(functionCallStatement: FunctionCallStatement): IStatement {
val newtarget = functionCallStatement.target.process(this)
if(newtarget is IdentifierReference)
functionCallStatement.target = newtarget
functionCallStatement.arglist = functionCallStatement.arglist.map { it.process(this) }.toMutableList()
return functionCallStatement
}
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 {
if(jump.identifier!=null) {
val ident = jump.identifier.process(this)
if(ident is IdentifierReference && ident!==jump.identifier) {
return Jump(null, ident, null, jump.position)
}
}
return jump
}
fun process(ifStatement: IfStatement): IStatement {
ifStatement.condition = ifStatement.condition.process(this)
ifStatement.truepart = ifStatement.truepart.process(this) as AnonymousScope
ifStatement.elsepart = ifStatement.elsepart.process(this) as AnonymousScope
return ifStatement
}
fun process(branchStatement: BranchStatement): IStatement {
branchStatement.truepart = branchStatement.truepart.process(this) as AnonymousScope
branchStatement.elsepart = branchStatement.elsepart.process(this) as AnonymousScope
return branchStatement
}
fun process(range: RangeExpr): IExpression {
range.from = range.from.process(this)
range.to = range.to.process(this)
range.step = range.step.process(this)
return range
}
fun process(label: Label): IStatement {
return label
}
fun process(literalValue: LiteralValue): LiteralValue {
if(literalValue.arrayvalue!=null) {
for(av in literalValue.arrayvalue.withIndex()) {
val newvalue = av.value.process(this)
literalValue.arrayvalue[av.index] = newvalue
}
}
return literalValue
}
fun process(assignment: Assignment): IStatement {
assignment.targets = assignment.targets.map { it.process(this) }
assignment.value = assignment.value.process(this)
return assignment
}
fun process(postIncrDecr: PostIncrDecr): IStatement {
postIncrDecr.target = postIncrDecr.target.process(this)
return postIncrDecr
}
fun process(contStmt: Continue): IStatement {
return contStmt
}
fun process(breakStmt: Break): IStatement {
return breakStmt
}
fun process(forLoop: ForLoop): IStatement {
forLoop.loopVar?.process(this)
forLoop.iterable = forLoop.iterable.process(this)
forLoop.body = forLoop.body.process(this) as AnonymousScope
return forLoop
}
fun process(whileLoop: WhileLoop): IStatement {
whileLoop.condition = whileLoop.condition.process(this)
whileLoop.body = whileLoop.body.process(this) as AnonymousScope
return whileLoop
}
fun process(repeatLoop: RepeatLoop): IStatement {
repeatLoop.untilCondition = repeatLoop.untilCondition.process(this)
repeatLoop.body = repeatLoop.body.process(this) as AnonymousScope
return repeatLoop
}
fun process(returnStmt: Return): IStatement {
returnStmt.values = returnStmt.values.map { it.process(this) }
return returnStmt
}
fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
arrayIndexedExpression.identifier.process(this)
arrayIndexedExpression.arrayspec.process(this)
return arrayIndexedExpression
}
fun process(assignTarget: AssignTarget): AssignTarget {
assignTarget.arrayindexed?.process(this)
assignTarget.identifier?.process(this)
assignTarget.memoryAddress?.process(this)
return assignTarget
}
fun process(scope: AnonymousScope): IStatement {
scope.statements = scope.statements.asSequence().map { it.process(this) }.toMutableList()
return scope
}
fun process(typecast: TypecastExpression): IExpression {
typecast.expression = typecast.expression.process(this)
return typecast
}
fun process(memread: DirectMemoryRead): IExpression {
memread.addressExpression = memread.addressExpression.process(this)
return memread
}
fun process(memwrite: DirectMemoryWrite): IExpression {
memwrite.addressExpression = memwrite.addressExpression.process(this)
return memwrite
}
fun process(addressOf: AddressOf): IExpression {
addressOf.identifier.process(this)
return addressOf
}
fun process(inlineAssembly: InlineAssembly): IStatement {
return inlineAssembly
}
}

View File

@ -1,18 +1,11 @@
package prog8.ast
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.SyntaxError
import prog8.ast.base.printWarning
import prog8.ast.statements.Directive
/**
* Checks that are specific for imported modules.
*/
internal fun Module.checkImportedValid() {
val checker = ImportedAstChecker()
checker.process(this)
printErrors(checker.result(), name)
}
private class ImportedAstChecker : IAstProcessor {
internal class ImportedAstChecker : IAstProcessor {
private val checkResult: MutableList<SyntaxError> = mutableListOf()
internal fun result(): List<SyntaxError> {

View File

@ -1,19 +1,17 @@
package prog8.ast
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
initvalueCreator.process(this)
val checker = StatementReorderer(this)
checker.process(this)
}
internal const val initvarsSubName="prog8_init_vars" // the name of the subroutine that should be called for every block to initialize its variables
private class StatementReorderer(private val program: Program): IAstProcessor {
internal class StatementReorderer(private val program: Program): IAstProcessor {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - blocks are ordered by address, where blocks without address are put at the end.
@ -144,7 +142,7 @@ private class StatementReorderer(private val program: Program): IAstProcessor {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti
if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) {
if (subroutine.statements.lastOrNull {it !is VarDecl} !is Return) {
if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) {
val returnStmt = Return(emptyList(), subroutine.position)
returnStmt.linkParents(subroutine)
subroutine.statements.add(returnStmt)
@ -237,8 +235,7 @@ private class StatementReorderer(private val program: Program): IAstProcessor {
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
// see if a typecast is needed to convert the arguments into the required parameter's type
val sub = call.target.targetStatement(scope)
when(sub) {
when(val sub = call.target.targetStatement(scope)) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
val argtype = arg.second.value.inferType(program)
@ -312,119 +309,3 @@ private class StatementReorderer(private val program: Program): IAstProcessor {
return super.process(typecast)
}
}
private class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstProcessor {
// For VarDecls that declare an initialization value:
// Replace the vardecl with an assignment (to set the initial value),
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
// This makes sure the variables get reset to the intended value on a next run of the program.
// Variable decls without a value don't get this treatment, which means they retain the last
// value they had when restarting the program.
// This is done in a separate step because it interferes with the namespace lookup of symbols
// in other ast processors.
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableMap<String, VarDecl>>()
override fun process(module: Module) {
vardeclsToAdd.clear()
super.process(module)
// add any new vardecls to the various scopes
for(decl in vardeclsToAdd)
for(d in decl.value) {
d.value.linkParents(decl.key as Node)
decl.key.statements.add(0, d.value)
}
}
override fun process(decl: VarDecl): IStatement {
super.process(decl)
if(decl.type!=VarDeclType.VAR || decl.value==null)
return decl
if(decl.datatype in NumericDatatypes) {
val scope = decl.definingScope()
addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!!
val value =
if(declvalue is LiteralValue) {
val converted = declvalue.intoDatatype(decl.datatype)
converted ?: declvalue
}
else
declvalue
val identifierName = listOf(decl.name) // // TODO this was: (scoped name) decl.scopedname.split(".")
return VariableInitializationAssignment(
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
null,
value,
decl.position
)
}
return decl
}
override fun process(functionCall: FunctionCall): IExpression {
val targetStatement = functionCall.target.targetSubroutine(namespace)
if(targetStatement!=null) {
var node: Node = functionCall
while(node !is IStatement)
node=node.parent
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node)
}
return functionCall
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
if(targetStatement!=null)
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
return functionCallStatement
}
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<IExpression>, parent: IStatement) {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? LiteralValue
if(idref!=null) {
val variable = idref.targetVarDecl(namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
else if(strvalue!=null) {
if(strvalue.isString) {
// replace the argument with &autovar
val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}"
val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.scopedname = parent.makeScopedName(autoVarName)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue,
isArray = false, autoGenerated = false, position=strvalue.position)
addVarDecl(strvalue.definingScope(), variable)
}
}
}
}
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableMapOf()
vardeclsToAdd.getValue(scope)[variable.name]=variable
}
}

View File

@ -0,0 +1,125 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.base.autoHeapValuePrefix
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.*
internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstProcessor {
// For VarDecls that declare an initialization value:
// Replace the vardecl with an assignment (to set the initial value),
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
// This makes sure the variables get reset to the intended value on a next run of the program.
// Variable decls without a value don't get this treatment, which means they retain the last
// value they had when restarting the program.
// This is done in a separate step because it interferes with the namespace lookup of symbols
// in other ast processors.
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableMap<String, VarDecl>>()
override fun process(module: Module) {
vardeclsToAdd.clear()
super.process(module)
// add any new vardecls to the various scopes
for(decl in vardeclsToAdd)
for(d in decl.value) {
d.value.linkParents(decl.key as Node)
decl.key.statements.add(0, d.value)
}
}
override fun process(decl: VarDecl): IStatement {
super.process(decl)
if(decl.type!= VarDeclType.VAR || decl.value==null)
return decl
if(decl.datatype in NumericDatatypes) {
val scope = decl.definingScope()
addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!!
val value =
if(declvalue is LiteralValue) {
val converted = declvalue.intoDatatype(decl.datatype)
converted ?: declvalue
}
else
declvalue
val identifierName = listOf(decl.name) // // TODO this was: (scoped name) decl.scopedname.split(".")
return VariableInitializationAssignment(
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
null,
value,
decl.position
)
}
return decl
}
override fun process(functionCall: FunctionCall): IExpression {
val targetStatement = functionCall.target.targetSubroutine(namespace)
if(targetStatement!=null) {
var node: Node = functionCall
while(node !is IStatement)
node=node.parent
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node)
}
return functionCall
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
if(targetStatement!=null)
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
return functionCallStatement
}
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<IExpression>, parent: IStatement) {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type== DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? LiteralValue
if(idref!=null) {
val variable = idref.targetVarDecl(namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
else if(strvalue!=null) {
if(strvalue.isString) {
// replace the argument with &autovar
val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}"
val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.scopedname = parent.makeScopedName(autoVarName)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue,
isArray = false, autoGenerated = false, position = strvalue.position)
addVarDecl(strvalue.definingScope(), variable)
}
}
}
}
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableMapOf()
vardeclsToAdd.getValue(scope)[variable.name]=variable
}
}

View File

@ -0,0 +1,639 @@
package prog8.ast.statements
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstProcessor
import prog8.compiler.HeapValues
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement {
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
override fun process(processor: IAstProcessor): IStatement = this
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
override val expensiveToInline = false
}
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean?)
class Block(override val name: String,
val address: Int?,
override var statements: MutableList<IStatement>,
val isInLibrary: Boolean,
override val position: Position) : IStatement, INameScope {
override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
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)"
}
fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet()
}
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
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
override val expensiveToInline = false
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)"
}
val scopedname: String by lazy { makeScopedName(name) }
}
open class Return(var values: List<IExpression>, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = values.any { it !is LiteralValue }
override fun linkParents(parent: Node) {
this.parent = parent
values.forEach {it.linkParents(this)}
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "Return(values: $values, pos=$position)"
}
}
class ReturnFromIrq(override val position: Position) : Return(emptyList(), position) {
override fun process(processor: IAstProcessor) = this
override fun toString(): String {
return "ReturnFromIrq(pos=$position)"
}
}
class Continue(override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent=parent
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
class Break(override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent=parent
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
class VarDecl(val type: VarDeclType,
private val declaredDatatype: DataType,
val zeropage: Boolean,
var arraysize: ArrayIndex?,
val name: String,
var value: IExpression?,
val isArray: Boolean,
val autoGenerated: Boolean,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline
get() = value!=null && value !is LiteralValue
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
val datatype =
if (!isArray) declaredDatatype
else when (declaredDatatype) {
DataType.UBYTE -> DataType.ARRAY_UB
DataType.BYTE -> DataType.ARRAY_B
DataType.UWORD -> DataType.ARRAY_UW
DataType.WORD -> DataType.ARRAY_W
DataType.FLOAT -> DataType.ARRAY_F
else -> {
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
DataType.UBYTE
}
}
override fun linkParents(parent: Node) {
this.parent = parent
arraysize?.linkParents(this)
value?.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
val scopedname: String by lazy { makeScopedName(name) }
override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, array=$isArray, value=$value, pos=$position)"
}
fun asDefaultValueDecl(parent: Node?): VarDecl {
val constValue = when(declaredDatatype) {
DataType.UBYTE -> LiteralValue(DataType.UBYTE, 0, position = position)
DataType.BYTE -> LiteralValue(DataType.BYTE, 0, position = position)
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = 0, position = position)
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = 0, position = position)
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = 0.0, position = position)
else -> throw FatalAstException("can only set a default value for a numeric type")
}
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, true, position)
if(parent!=null)
decl.linkParents(parent)
return decl
}
}
class ArrayIndex(var index: IExpression, override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
index.linkParents(this)
}
companion object {
fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex {
val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize
return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position)
}
}
fun process(processor: IAstProcessor) {
index = index.process(processor)
}
override fun toString(): String {
return("ArrayIndex($index, pos=$position)")
}
fun size() = (index as? LiteralValue)?.asIntegerValue
}
open class Assignment(var targets: List<AssignTarget>, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline
get() = value !is LiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
targets.forEach { it.linkParents(this) }
value.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return("Assignment(augop: $aug_op, targets: $targets, value: $value, pos=$position)")
}
val singleTarget: AssignTarget?
get() {
return targets.singleOrNull() // common case
}
}
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
// or just a regular assignment. It may optimize the initialization step from this.
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: IExpression, position: Position)
: Assignment(listOf(target), aug_op, value, position)
data class AssignTarget(val register: Register?,
val identifier: IdentifierReference?,
val arrayindexed: ArrayIndexedExpression?,
var memoryAddress: DirectMemoryWrite?,
override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier?.linkParents(this)
arrayindexed?.linkParents(this)
memoryAddress?.linkParents(this)
}
fun process(processor: IAstProcessor) = processor.process(this)
companion object {
fun fromExpr(expr: IExpression): AssignTarget {
return when (expr) {
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position)
is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
is DirectMemoryWrite -> AssignTarget(null, null, null, expr, expr.position)
else -> throw FatalAstException("invalid expression object $expr")
}
}
}
fun inferType(program: Program, stmt: IStatement): DataType? {
if(register!=null)
return DataType.UBYTE
if(identifier!=null) {
val symbol = program.namespace.lookup(identifier.nameInSource, stmt) ?: return null
if (symbol is VarDecl) return symbol.datatype
}
if(arrayindexed!=null) {
val dt = arrayindexed.inferType(program)
if(dt!=null)
return dt
}
if(memoryAddress!=null)
return DataType.UBYTE
return null
}
fun shortString(withTypePrefix: Boolean=false): String {
if(register!=null)
return (if(withTypePrefix) "0register::" else "") + register.name
if(identifier!=null)
return (if(withTypePrefix) "3identifier::" else "") + identifier.nameInSource.last()
if(arrayindexed!=null)
return (if(withTypePrefix) "2arrayidx::" else "") + arrayindexed.identifier.nameInSource.last()
val address = memoryAddress?.addressExpression
if(address is LiteralValue)
return (if(withTypePrefix) "1address::" else "") +address.asIntegerValue.toString()
return if(withTypePrefix) "???::???" else "???"
}
fun isMemoryMapped(namespace: INameScope): Boolean =
memoryAddress!=null || (identifier?.targetVarDecl(namespace)?.type== VarDeclType.MEMORY)
infix fun isSameAs(value: IExpression): Boolean {
return when {
this.memoryAddress!=null -> false
this.register!=null -> value is RegisterExpr && value.register==register
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier.nameInSource
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
value.identifier.nameInSource==arrayindexed.identifier.nameInSource &&
value.arrayspec.size()!=null &&
arrayindexed.arrayspec.size()!=null &&
value.arrayspec.size()==arrayindexed.arrayspec.size()
else -> false
}
}
fun isSameAs(other: AssignTarget, program: Program): Boolean {
if(this===other)
return true
if(this.register!=null && other.register!=null)
return this.register==other.register
if(this.identifier!=null && other.identifier!=null)
return this.identifier.nameInSource==other.identifier.nameInSource
if(this.memoryAddress!=null && other.memoryAddress!=null) {
val addr1 = this.memoryAddress!!.addressExpression.constValue(program)
val addr2 = other.memoryAddress!!.addressExpression.constValue(program)
return addr1!=null && addr2!=null && addr1==addr2
}
if(this.arrayindexed!=null && other.arrayindexed!=null) {
if(this.arrayindexed.identifier.nameInSource == other.arrayindexed.identifier.nameInSource) {
val x1 = this.arrayindexed.arrayspec.index.constValue(program)
val x2 = other.arrayindexed.arrayspec.index.constValue(program)
return x1!=null && x2!=null && x1==x2
}
}
return false
}
fun isNotMemory(namespace: INameScope): Boolean {
if(this.register!=null)
return true
if(this.memoryAddress!=null)
return false
if(this.arrayindexed!=null) {
val targetStmt = this.arrayindexed.identifier.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!= VarDeclType.MEMORY
}
if(this.identifier!=null) {
val targetStmt = this.identifier.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!= VarDeclType.MEMORY
}
return false
}
}
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
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?,
val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
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, label: $generatedLabel; pos=$position)"
}
}
class FunctionCallStatement(override var target: IdentifierReference,
override var arglist: MutableList<IExpression>,
override val position: Position) : IStatement, IFunctionCall {
override lateinit var parent: Node
override val expensiveToInline
get() = arglist.any { it !is LiteralValue }
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 "FunctionCallStatement(target=$target, pos=$position)"
}
}
class InlineAssembly(val assembly: String, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
class AnonymousScope(override var statements: MutableList<IStatement>,
override val position: Position) : INameScope, IStatement {
override val name: String
override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
init {
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++
}
companion object {
private var sequenceNumber = 1
}
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach { it.linkParents(this) }
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
class NopStatement(override val position: Position): IStatement {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun process(processor: IAstProcessor) = this
}
// the subroutine class covers both the normal user-defined subroutines,
// and also the predefined/ROM/register-based subroutines.
// (multiple return types can only occur for the latter type)
class Subroutine(override val name: String,
val parameters: List<SubroutineParameter>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<Register>,
val asmAddress: Int?,
val isAsmSubroutine: Boolean,
override var statements: MutableList<IStatement>,
override val position: Position) : IStatement, INameScope {
var keepAlways: Boolean = false
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
override lateinit var parent: Node
val calledBy = mutableListOf<Node>()
val calls = mutableSetOf<Subroutine>()
val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) {
this.parent = parent
parameters.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, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
}
fun amountOfRtsInAsm(): Int = statements
.asSequence()
.filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
val canBeAsmSubroutine =false // TODO disabled for now, see below about problem with converting to asm subroutine
// !isAsmSubroutine
// && ((parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD))
// || (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE }))
fun intoAsmSubroutine(): Subroutine {
// TODO turn subroutine into asm calling convention. Requires rethinking of how parameters are handled (conflicts with local vardefs now, see AstIdentifierChecker...)
return this // TODO
// println("TO ASM $this") // TODO
// val paramregs = if (parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE))
// listOf(RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else if (parameters.size == 1 && parameters[0].type in setOf(DataType.WORD, DataType.UWORD))
// listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null))
// else if (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE })
// listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else throw FatalAstException("cannot convert subroutine to asm parameters")
//
// val asmsub=Subroutine(
// name,
// parameters,
// returntypes,
// paramregs,
// emptyList(),
// emptySet(),
// null,
// true,
// statements,
// position
// )
// asmsub.linkParents(parent)
// return asmsub
}
}
open class SubroutineParameter(val name: String,
val type: DataType,
override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
}
class IfStatement(var condition: IExpression,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) {
this.parent = parent
condition.linkParents(this)
truepart.linkParents(this)
elsepart.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
}
class BranchStatement(var condition: BranchCondition,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) {
this.parent = parent
truepart.linkParents(this)
elsepart.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
}
class ForLoop(val loopRegister: Register?,
val decltype: DataType?,
val zeropage: Boolean,
val loopVar: IdentifierReference?,
var iterable: IExpression,
var body: AnonymousScope,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent=parent
loopVar?.linkParents(if(decltype==null) this else body)
iterable.linkParents(this)
body.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
}
companion object {
const val iteratorLoopcounterVarname = "prog8forloopcounter"
}
}
class WhileLoop(var condition: IExpression,
var body: AnonymousScope,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
condition.linkParents(this)
body.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
}
class RepeatLoop(var body: AnonymousScope,
var untilCondition: IExpression,
override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
untilCondition.linkParents(this)
body.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
}

View File

@ -1,6 +1,11 @@
package prog8.astvm
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.base.initvarsSubName
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.*
import prog8.compiler.RuntimeValue
import prog8.compiler.RuntimeValueRange
import prog8.compiler.target.c64.Petscii

View File

@ -1,6 +1,6 @@
package prog8.astvm
import prog8.ast.DataType
import prog8.ast.base.DataType
import prog8.compiler.RuntimeValue
import java.lang.Math.toDegrees
import java.lang.Math.toRadians

View File

@ -1,6 +1,14 @@
package prog8.astvm
import prog8.ast.*
import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compiler.RuntimeValue
import prog8.compiler.RuntimeValueRange
import kotlin.math.abs

View File

@ -1,6 +1,10 @@
package prog8.astvm
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.LiteralValue
import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.VarDecl
import prog8.compiler.HeapValues
import prog8.compiler.RuntimeValue

View File

@ -1,7 +1,11 @@
package prog8.compiler
import prog8.ast.*
import prog8.ast.RegisterOrPair.*
import prog8.ast.base.*
import prog8.ast.base.RegisterOrPair.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.*
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.Opcode
import prog8.compiler.intermediate.branchOpcodes
@ -383,7 +387,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
// The compiler could then convert it to a special system call
val sub = stmt.parent as? Subroutine
val scopename =
if(sub!=null && sub.statements.filter{it !is VarDecl}.size==1)
if(sub!=null && sub.statements.filter{it !is VarDecl }.size==1)
sub.scopedname
else
null
@ -1413,7 +1417,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
// @todo use convertType()????
when(targetDt) {
in ByteDatatypes ->
if(valueDt!=DataType.BYTE && valueDt!=DataType.UBYTE)
if(valueDt!= DataType.BYTE && valueDt!= DataType.UBYTE)
throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
DataType.WORD -> {
when (valueDt) {
@ -1619,11 +1623,11 @@ internal class Compiler(private val program: Program): IAstProcessor {
}
private fun translateForOverIterableVar(loop: ForLoop, loopvarDt: DataType, iterableValue: LiteralValue) {
if(loopvarDt==DataType.UBYTE && iterableValue.type !in setOf(DataType.STR, DataType.STR_S, DataType.ARRAY_UB))
if(loopvarDt== DataType.UBYTE && iterableValue.type !in setOf(DataType.STR, DataType.STR_S, DataType.ARRAY_UB))
throw CompilerException("loop variable type doesn't match iterableValue type")
else if(loopvarDt==DataType.UWORD && iterableValue.type != DataType.ARRAY_UW)
else if(loopvarDt== DataType.UWORD && iterableValue.type != DataType.ARRAY_UW)
throw CompilerException("loop variable type doesn't match iterableValue type")
else if(loopvarDt==DataType.FLOAT && iterableValue.type != DataType.ARRAY_F)
else if(loopvarDt== DataType.FLOAT && iterableValue.type != DataType.ARRAY_F)
throw CompilerException("loop variable type doesn't match iterableValue type")
val numElements: Int
@ -1645,7 +1649,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
else -> throw CompilerException("weird datatype")
}
if(loop.loopRegister!=null && loop.loopRegister==Register.X)
if(loop.loopRegister!=null && loop.loopRegister== Register.X)
throw CompilerException("loopVar cannot use X register because that is used as internal stack pointer")
/**
@ -1848,10 +1852,10 @@ internal class Compiler(private val program: Program): IAstProcessor {
val condition =
if(literalStepValue > 0) {
// if LV > last goto break
BinaryExpression(loopVar,">", range.to, range.position)
BinaryExpression(loopVar, ">", range.to, range.position)
} else {
// if LV < last goto break
BinaryExpression(loopVar,"<", range.to, range.position)
BinaryExpression(loopVar, "<", range.to, range.position)
}
val ifstmt = IfStatement(condition,
AnonymousScope(mutableListOf(Jump(null, null, breakLabel, range.position)), range.position),
@ -1881,7 +1885,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
TODO("can't generate code for step other than 1 or -1 right now")
// LV++ / LV--
val postIncr = PostIncrDecr(lvTarget, if(step==1) "++" else "--", range.position)
val postIncr = PostIncrDecr(lvTarget, if (step == 1) "++" else "--", range.position)
postIncr.linkParents(body)
translate(postIncr)
if(lvTarget.register!=null)
@ -2067,10 +2071,10 @@ internal class Compiler(private val program: Program): IAstProcessor {
private fun translate(addrof: AddressOf) {
val target = addrof.identifier.targetVarDecl(program.namespace)!!
if(target.datatype in ArrayDatatypes || target.datatype in StringDatatypes|| target.datatype==DataType.FLOAT) {
if(target.datatype in ArrayDatatypes || target.datatype in StringDatatypes || target.datatype== DataType.FLOAT) {
pushHeapVarAddress(addrof.identifier, false)
}
else if(target.datatype==DataType.FLOAT) {
else if(target.datatype== DataType.FLOAT) {
pushFloatAddress(addrof.identifier)
}
else

View File

@ -1,6 +1,7 @@
package prog8.compiler
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.LiteralValue
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
import kotlin.math.pow
@ -174,7 +175,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
DataType.UBYTE, DataType.UWORD -> {
// storing a negative number in an unsigned one is done by storing the 2's complement instead
val number = abs(result.toDouble().toInt())
if(leftDt==DataType.UBYTE)
if(leftDt== DataType.UBYTE)
RuntimeValue(DataType.UBYTE, (number xor 255) + 1)
else
RuntimeValue(DataType.UBYTE, (number xor 65535) + 1)

View File

@ -1,6 +1,7 @@
package prog8.compiler
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.base.printWarning
class ZeropageDepletedError(message: String) : Exception(message)

View File

@ -1,6 +1,10 @@
package prog8.compiler.intermediate
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.base.printWarning
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.VarDecl
import prog8.compiler.RuntimeValue
import prog8.compiler.CompilerException
import prog8.compiler.HeapValues
@ -412,7 +416,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
VarDeclType.MEMORY -> {
// note that constants are all folded away, but assembly code may still refer to them
val lv = decl.value as LiteralValue
if(lv.type!=DataType.UWORD && lv.type!=DataType.UBYTE)
if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE)
throw CompilerException("expected integer memory address $lv")
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype)
}

View File

@ -3,7 +3,10 @@ package prog8.compiler.target.c64
// note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning
import prog8.compiler.RuntimeValue
import prog8.compiler.*
import prog8.compiler.intermediate.*
@ -63,7 +66,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
// make a list of all const floats that are used
for(block in program.blocks) {
for(ins in block.instructions.filter{it.arg?.type==DataType.FLOAT}) {
for(ins in block.instructions.filter{it.arg?.type== DataType.FLOAT}) {
val float = ins.arg!!.numericValue().toDouble()
if(float !in globalFloatConsts)
globalFloatConsts[float] = "prog8_const_float_${globalFloatConsts.size}"
@ -186,7 +189,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block")
for(block in program.blocks) {
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name==initvarsSubName } as? LabelInstr
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name== initvarsSubName } as? LabelInstr
if(initVarsLabel!=null)
out(" jsr ${block.name}.${initVarsLabel.name}")
}
@ -362,10 +365,10 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
private fun makeArrayFillDataUnsigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId!!).array!!
return when {
value.type==DataType.ARRAY_UB ->
value.type== DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map { "$"+it.integer!!.toString(16).padStart(2, '0') }
value.type==DataType.ARRAY_UW -> array.map {
value.type== DataType.ARRAY_UW -> array.map {
when {
it.integer!=null -> "$"+it.integer.toString(16).padStart(2, '0')
it.addressOf!=null -> symname(it.addressOf.scopedname!!, block)

View File

@ -1,6 +1,11 @@
package prog8.functions
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.VarDecl
import prog8.compiler.CompilerException
import kotlin.math.*
@ -111,13 +116,13 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
fun datatypeFromIterableArg(arglist: IExpression): DataType {
if(arglist is LiteralValue) {
if(arglist.type==DataType.ARRAY_UB || arglist.type==DataType.ARRAY_UW || arglist.type==DataType.ARRAY_F) {
if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) {
val dt = arglist.arrayvalue!!.map {it.inferType(program)}
if(dt.any { it!=DataType.UBYTE && it!=DataType.UWORD && it!=DataType.FLOAT}) {
if(dt.any { it!= DataType.UBYTE && it!= DataType.UWORD && it!= DataType.FLOAT}) {
throw FatalAstException("fuction $function only accepts arraysize of numeric values")
}
if(dt.any { it==DataType.FLOAT }) return DataType.FLOAT
if(dt.any { it==DataType.UWORD }) return DataType.UWORD
if(dt.any { it== DataType.FLOAT }) return DataType.FLOAT
if(dt.any { it== DataType.UWORD }) return DataType.UWORD
return DataType.UBYTE
}
}
@ -186,7 +191,7 @@ private fun oneDoubleArg(args: List<IExpression>, position: Position, program: P
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type!=DataType.FLOAT)
if(constval.type!= DataType.FLOAT)
throw SyntaxError("built-in function requires one floating point argument", position)
val float = constval.asNumericValue?.toDouble()!!
@ -197,16 +202,16 @@ private fun oneDoubleArgOutputWord(args: List<IExpression>, position: Position,
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type!=DataType.FLOAT)
if(constval.type!= DataType.FLOAT)
throw SyntaxError("built-in function requires one floating point argument", position)
return LiteralValue(DataType.WORD, wordvalue=function(constval.asNumericValue!!.toDouble()).toInt(), position=args[0].position)
return LiteralValue(DataType.WORD, wordvalue = function(constval.asNumericValue!!.toDouble()).toInt(), position = args[0].position)
}
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, program: Program, function: (arg: Int)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type!=DataType.UBYTE && constval.type!=DataType.UWORD)
if(constval.type!= DataType.UBYTE && constval.type!= DataType.UWORD)
throw SyntaxError("built-in function requires one integer argument", position)
val integer = constval.asNumericValue?.toInt()!!
@ -378,7 +383,7 @@ private fun builtinSin8(args: List<IExpression>, position: Position, program: Pr
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0* sin(rad)).toShort(), position = position)
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * sin(rad)).toShort(), position = position)
}
private fun builtinSin8u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
@ -386,7 +391,7 @@ private fun builtinSin8u(args: List<IExpression>, position: Position, program: P
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0+127.5*sin(rad)).toShort(), position = position)
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * sin(rad)).toShort(), position = position)
}
private fun builtinCos8(args: List<IExpression>, position: Position, program: Program): LiteralValue {
@ -394,7 +399,7 @@ private fun builtinCos8(args: List<IExpression>, position: Position, program: Pr
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0* cos(rad)).toShort(), position = position)
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * cos(rad)).toShort(), position = position)
}
private fun builtinCos8u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
@ -402,7 +407,7 @@ private fun builtinCos8u(args: List<IExpression>, position: Position, program: P
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5*cos(rad)).toShort(), position = position)
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * cos(rad)).toShort(), position = position)
}
private fun builtinSin16(args: List<IExpression>, position: Position, program: Program): LiteralValue {
@ -410,7 +415,7 @@ private fun builtinSin16(args: List<IExpression>, position: Position, program: P
throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0* sin(rad)).toInt(), position = position)
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * sin(rad)).toInt(), position = position)
}
private fun builtinSin16u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
@ -418,7 +423,7 @@ private fun builtinSin16u(args: List<IExpression>, position: Position, program:
throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5*sin(rad)).toInt(), position = position)
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * sin(rad)).toInt(), position = position)
}
private fun builtinCos16(args: List<IExpression>, position: Position, program: Program): LiteralValue {
@ -426,7 +431,7 @@ private fun builtinCos16(args: List<IExpression>, position: Position, program: P
throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0* cos(rad)).toInt(), position = position)
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * cos(rad)).toInt(), position = position)
}
private fun builtinCos16u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
@ -434,7 +439,7 @@ private fun builtinCos16u(args: List<IExpression>, position: Position, program:
throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5* cos(rad)).toInt(), position = position)
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * cos(rad)).toInt(), position = position)
}
private fun numericLiteral(value: Number, position: Position): LiteralValue {

View File

@ -1,6 +1,13 @@
package prog8.optimizing
import prog8.ast.*
import prog8.ast.base.ParentSentinel
import prog8.ast.base.VarDeclType
import prog8.ast.base.initvarsSubName
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.*
import prog8.compiler.loadAsmIncludeFile
@ -98,7 +105,7 @@ class CallGraph(private val program: Program): IAstProcessor {
override fun process(subroutine: Subroutine): IStatement {
if((subroutine.name=="start" && subroutine.definingScope().name=="main")
|| subroutine.name==initvarsSubName || subroutine.definingModule().isLibraryModule) {
|| subroutine.name== initvarsSubName || subroutine.definingModule().isLibraryModule) {
// make sure the entrypoint is mentioned in the used symbols
addNodeAndParentScopes(subroutine)
}

View File

@ -1,7 +1,11 @@
package prog8.optimizing
import prog8.ast.*
import prog8.compiler.HeapValues
import prog8.ast.base.DataType
import prog8.ast.base.ExpressionError
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.expressions.LiteralValue
import kotlin.math.pow
@ -40,7 +44,7 @@ class ConstExprEvaluator {
if(left.asIntegerValue==null || amount.asIntegerValue==null)
throw ExpressionError("cannot compute $left >> $amount", left.position)
val result =
if(left.type==DataType.UBYTE || left.type==DataType.UWORD)
if(left.type== DataType.UBYTE || left.type== DataType.UWORD)
left.asIntegerValue.ushr(amount.asIntegerValue)
else
left.asIntegerValue.shr(amount.asIntegerValue)

View File

@ -1,7 +1,10 @@
package prog8.optimizing
import prog8.ast.*
import prog8.compiler.CompilerException
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
@ -75,19 +78,19 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
val eltType = rangeExpr.inferType(program)!!
if(eltType in ByteDatatypes) {
decl.value = LiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, bytevalue=it.toShort(), position = decl.value!!.position ) }
.toTypedArray(), position=decl.value!!.position)
arrayvalue = constRange.map { LiteralValue(eltType, bytevalue = it.toShort(), position = decl.value!!.position) }
.toTypedArray(), position = decl.value!!.position)
} else {
decl.value = LiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, wordvalue= it, position = decl.value!!.position ) }
.toTypedArray(), position=decl.value!!.position)
arrayvalue = constRange.map { LiteralValue(eltType, wordvalue = it, position = decl.value!!.position) }
.toTypedArray(), position = decl.value!!.position)
}
decl.value!!.linkParents(decl)
optimizationsDone++
return decl
}
}
if(litval?.type==DataType.FLOAT)
if(litval?.type== DataType.FLOAT)
errors.add(ExpressionError("arraysize requires only integers here", litval.position))
val size = decl.arraysize?.size() ?: return decl
if ((litval==null || !litval.isArray) && rangeExpr==null) {
@ -96,24 +99,29 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.add(ExpressionError("ubyte value overflow", litval?.position ?: decl.position))
errors.add(ExpressionError("ubyte value overflow", litval?.position
?: decl.position))
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.add(ExpressionError("byte value overflow", litval?.position ?: decl.position))
errors.add(ExpressionError("byte value overflow", litval?.position
?: decl.position))
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.add(ExpressionError("uword value overflow", litval?.position ?: decl.position))
errors.add(ExpressionError("uword value overflow", litval?.position
?: decl.position))
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.add(ExpressionError("word value overflow", litval?.position ?: decl.position))
errors.add(ExpressionError("word value overflow", litval?.position
?: decl.position))
}
else -> {}
}
val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position ?: decl.position)
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position
?: decl.position)
optimizationsDone++
return decl
}
@ -124,10 +132,12 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0
if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval?.position ?: decl.position))
errors.add(ExpressionError("float value overflow", litval?.position
?: decl.position))
else {
val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue })
decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position ?: decl.position)
decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position
?: decl.position)
optimizationsDone++
return decl
}
@ -154,7 +164,7 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(array.array!=null) {
program.heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null))
decl.value = LiteralValue(decl.datatype, initHeapId=heapId, position = litval.position)
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
}
}
DataType.ARRAY_F -> {
@ -385,7 +395,7 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
expr
} else
BinaryExpression(
BinaryExpression(expr.left, if(expr.operator=="-") "+" else "*", subExpr.right, subExpr.position),
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position)
} else {
return if(subleftIsConst) {
@ -394,7 +404,7 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
} else
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if(expr.operator=="-") "+" else "*", subExpr.right, subExpr.position),
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.position)
}
}
@ -562,25 +572,25 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
val stepLiteral = iterableRange.step as? LiteralValue
when(loopvar.datatype) {
DataType.UBYTE -> {
if(rangeFrom.type!=DataType.UBYTE) {
if(rangeFrom.type!= DataType.UBYTE) {
// attempt to translate the iterable into ubyte values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.BYTE -> {
if(rangeFrom.type!=DataType.BYTE) {
if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.UWORD -> {
if(rangeFrom.type!=DataType.UWORD) {
if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.WORD -> {
if(rangeFrom.type!=DataType.WORD) {
if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
@ -614,7 +624,7 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
val typesInArray = array.mapNotNull { it.inferType(program) }.toSet()
val arrayDt =
when {
array.any { it is AddressOf} -> DataType.ARRAY_UW
array.any { it is AddressOf } -> DataType.ARRAY_UW
DataType.FLOAT in typesInArray -> DataType.ARRAY_F
DataType.WORD in typesInArray -> DataType.ARRAY_W
else -> {
@ -657,57 +667,57 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
when(assignment.singleTarget?.inferType(program, assignment)) {
DataType.UWORD -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type==DataType.UBYTE)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position=lv.position)
else if(lv.type==DataType.BYTE && lv.bytevalue!!>=0)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position=lv.position)
else if(lv.type==DataType.WORD && lv.wordvalue!!>=0)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position=lv.position)
else if(lv.type==DataType.FLOAT) {
if(lv.type== DataType.UBYTE)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
else if(lv.type== DataType.BYTE && lv.bytevalue!!>=0)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
else if(lv.type== DataType.WORD && lv.wordvalue!!>=0)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!!
if(floor(d)==d && d>=0 && d<=65535)
assignment.value = LiteralValue(DataType.UWORD, wordvalue=floor(d).toInt(), position=lv.position)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = floor(d).toInt(), position = lv.position)
}
}
DataType.UBYTE -> {
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
if(lv.type==DataType.UWORD && lv.wordvalue!! <= 255)
assignment.value = LiteralValue(DataType.UBYTE, lv.wordvalue.toShort(), position=lv.position)
else if(lv.type==DataType.BYTE && lv.bytevalue!! >=0)
assignment.value = LiteralValue(DataType.UBYTE, lv.bytevalue.toShort(), position=lv.position)
else if(lv.type==DataType.FLOAT) {
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 255)
assignment.value = LiteralValue(DataType.UBYTE, lv.wordvalue.toShort(), position = lv.position)
else if(lv.type== DataType.BYTE && lv.bytevalue!! >=0)
assignment.value = LiteralValue(DataType.UBYTE, lv.bytevalue.toShort(), position = lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!!
if(floor(d)==d && d >=0 && d<=255)
assignment.value = LiteralValue(DataType.UBYTE, floor(d).toShort(), position=lv.position)
assignment.value = LiteralValue(DataType.UBYTE, floor(d).toShort(), position = lv.position)
}
}
DataType.BYTE -> {
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
if(lv.type==DataType.UWORD && lv.wordvalue!! <= 127)
assignment.value = LiteralValue(DataType.BYTE, lv.wordvalue.toShort(), position=lv.position)
else if(lv.type==DataType.UBYTE && lv.bytevalue!! <= 127)
assignment.value = LiteralValue(DataType.BYTE, lv.bytevalue, position=lv.position)
else if(lv.type==DataType.FLOAT) {
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 127)
assignment.value = LiteralValue(DataType.BYTE, lv.wordvalue.toShort(), position = lv.position)
else if(lv.type== DataType.UBYTE && lv.bytevalue!! <= 127)
assignment.value = LiteralValue(DataType.BYTE, lv.bytevalue, position = lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!!
if(floor(d)==d && d>=0 && d<=127)
assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position=lv.position)
assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position = lv.position)
}
}
DataType.WORD -> {
// we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767,
if(lv.type==DataType.UBYTE || lv.type==DataType.BYTE)
assignment.value = LiteralValue(DataType.WORD, wordvalue=lv.bytevalue!!.toInt(), position=lv.position)
else if(lv.type==DataType.UWORD && lv.wordvalue!! <= 32767)
assignment.value = LiteralValue(DataType.WORD, wordvalue=lv.wordvalue, position=lv.position)
else if(lv.type==DataType.FLOAT) {
if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE)
assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.bytevalue!!.toInt(), position = lv.position)
else if(lv.type== DataType.UWORD && lv.wordvalue!! <= 32767)
assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.wordvalue, position = lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!!
if(floor(d)==d && d>=-32768 && d<=32767)
assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position=lv.position)
assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position = lv.position)
}
}
DataType.FLOAT -> {
if(lv.isNumeric)
assignment.value = LiteralValue(DataType.FLOAT, floatvalue= lv.asNumericValue?.toDouble(), position=lv.position)
assignment.value = LiteralValue(DataType.FLOAT, floatvalue = lv.asNumericValue?.toDouble(), position = lv.position)
}
else -> {}
}

View File

@ -1,6 +1,8 @@
package prog8.optimizing
import prog8.ast.*
import prog8.ast.base.AstException
import prog8.ast.statements.NopStatement
import prog8.parser.ParsingFailedError

View File

@ -1,6 +1,13 @@
package prog8.optimizing
import prog8.ast.*
import prog8.ast.base.AstException
import prog8.ast.base.DataType
import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.*
import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.Assignment
import kotlin.math.abs
import kotlin.math.log2
@ -336,48 +343,48 @@ internal class SimplifyExpressions(private val program: Program) : IAstProcessor
DataType.UBYTE -> {
if (targetDt == DataType.BYTE) {
if(value.bytevalue!! < 127)
return Pair(true, LiteralValue(targetDt, value.bytevalue, position=value.position))
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position))
}
else if (targetDt == DataType.UWORD || targetDt == DataType.WORD)
return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position=value.position))
return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position = value.position))
}
DataType.BYTE -> {
if (targetDt == DataType.UBYTE) {
if(value.bytevalue!! >= 0)
return Pair(true, LiteralValue(targetDt, value.bytevalue, position=value.position))
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position))
}
else if (targetDt == DataType.UWORD) {
if(value.bytevalue!! >= 0)
return Pair(true, LiteralValue(targetDt, wordvalue=value.bytevalue.toInt(), position=value.position))
return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue.toInt(), position = value.position))
}
else if (targetDt == DataType.WORD) return Pair(true, LiteralValue(targetDt, wordvalue=value.bytevalue!!.toInt(), position=value.position))
else if (targetDt == DataType.WORD) return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position = value.position))
}
DataType.UWORD -> {
if (targetDt == DataType.UBYTE) {
if(value.wordvalue!! <= 255)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position))
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
}
else if (targetDt == DataType.BYTE) {
if(value.wordvalue!! <= 127)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position))
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
}
else if (targetDt == DataType.WORD) {
if(value.wordvalue!! <= 32767)
return Pair(true, LiteralValue(targetDt, wordvalue=value.wordvalue, position=value.position))
return Pair(true, LiteralValue(targetDt, wordvalue = value.wordvalue, position = value.position))
}
}
DataType.WORD -> {
if (targetDt == DataType.UBYTE) {
if(value.wordvalue!! in 0..255)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position))
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
}
else if (targetDt == DataType.BYTE) {
if(value.wordvalue!! in -128..127)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position))
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
}
else if (targetDt == DataType.UWORD) {
if(value.wordvalue!! >= 0)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position))
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
}
}
else -> {}

View File

@ -1,6 +1,10 @@
package prog8.optimizing
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
import kotlin.math.floor
@ -419,7 +423,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
private fun hasContinueOrBreak(scope: INameScope): Boolean {
class Searcher:IAstProcessor
class Searcher: IAstProcessor
{
var count=0
@ -481,7 +485,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble()
if(cv==null) {
if(bexpr.operator=="+" && targetDt!=DataType.FLOAT) {
if(bexpr.operator=="+" && targetDt!= DataType.FLOAT) {
if (bexpr.left isSameAs bexpr.right && target isSameAs bexpr.left) {
bexpr.operator = "*"
bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position)

View File

@ -2,6 +2,12 @@ package prog8.parser
import org.antlr.v4.runtime.*
import prog8.ast.*
import prog8.ast.antlr.toAst
import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
import prog8.ast.base.checkImportedValid
import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.Path
@ -45,8 +51,8 @@ internal fun importModule(program: Program, filePath: Path): Module {
internal fun importLibraryModule(program: Program, name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0 ,0))
), Position("<<<implicit-import>>>", 0, 0 ,0))
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(program, import, Paths.get(""))
}

View File

@ -1,6 +1,9 @@
package prog8.stackvm
import prog8.ast.*
import prog8.ast.antlr.unescape
import prog8.ast.base.*
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.compiler.RuntimeValue
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
@ -89,7 +92,7 @@ class Program (val name: String,
}
heapvalues.sortedBy { it.first }.forEach {
when(it.second) {
DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length-1), Position("<stackvmsource>", 0, 0, 0)))
DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length - 1), Position("<stackvmsource>", 0, 0, 0)))
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numbers = it.third.substring(1, it.third.length-1).split(',')
@ -98,8 +101,8 @@ class Program (val name: String,
if(num.startsWith("&")) {
// it's AddressOf
val scopedname = num.substring(1)
val iref = IdentifierReference(scopedname.split('.'), Position("<intermediate>", 0,0,0))
val addrOf = AddressOf(iref, Position("<intermediate>", 0,0,0))
val iref = IdentifierReference(scopedname.split('.'), Position("<intermediate>", 0, 0, 0))
val addrOf = AddressOf(iref, Position("<intermediate>", 0, 0, 0))
addrOf.scopedname=scopedname
IntegerOrAddressOf(null, addrOf)
} else {

View File

@ -1,6 +1,10 @@
package prog8.stackvm
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.IterableDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.Register
import prog8.ast.base.initvarsSubName
import prog8.astvm.BitmapScreenPanel
import prog8.astvm.Memory
import prog8.compiler.RuntimeValue
@ -376,7 +380,7 @@ class StackVm(private var traceOutputFile: String?) {
val value = evalstack.pop()
checkDt(value, DataType.BYTE, DataType.UBYTE)
val address = ins.arg!!.integerValue()
if(value.type==DataType.BYTE)
if(value.type== DataType.BYTE)
mem.setSByte(address, value.integerValue().toShort())
else
mem.setUByte(address, value.integerValue().toShort())
@ -386,7 +390,7 @@ class StackVm(private var traceOutputFile: String?) {
val value = evalstack.pop()
checkDt(value, DataType.WORD, DataType.UWORD)
val address = ins.arg!!.integerValue()
if(value.type==DataType.WORD)
if(value.type== DataType.WORD)
mem.setSWord(address, value.integerValue())
else
mem.setUWord(address, value.integerValue())
@ -1673,7 +1677,7 @@ class StackVm(private var traceOutputFile: String?) {
} else {
// normal variable
val variable = getVar(ins.callLabel!!)
if(variable.type==DataType.UWORD) {
if(variable.type== DataType.UWORD) {
// assume the variable is a pointer (address) and get the word value from that memory location
RuntimeValue(DataType.UWORD, mem.getUWord(variable.integerValue()))
} else {
@ -1708,7 +1712,7 @@ class StackVm(private var traceOutputFile: String?) {
if(ins.callLabel in memoryPointers) {
val variable = memoryPointers.getValue(ins.callLabel!!)
val address = variable.first + index*5
if(variable.second==DataType.ARRAY_F)
if(variable.second== DataType.ARRAY_F)
RuntimeValue(DataType.FLOAT, mem.getFloat(address))
else
throw VmExecutionException("not a proper arraysize var with float elements")
@ -1737,13 +1741,13 @@ class StackVm(private var traceOutputFile: String?) {
val memloc = memoryPointers[varname]
if(memloc!=null) {
// variable is the name of a pointer, write the byte value to that memory location
if(value.type==DataType.UBYTE) {
if(memloc.second!=DataType.ARRAY_UB)
if(value.type== DataType.UBYTE) {
if(memloc.second!= DataType.ARRAY_UB)
throw VmExecutionException("invalid memory pointer type $memloc")
mem.setUByte(memloc.first, value.integerValue().toShort())
}
else {
if(memloc.second!=DataType.ARRAY_B)
if(memloc.second!= DataType.ARRAY_B)
throw VmExecutionException("invalid memory pointer type $memloc")
mem.setSByte(memloc.first, value.integerValue().toShort())
}
@ -1751,7 +1755,7 @@ class StackVm(private var traceOutputFile: String?) {
val variable = getVar(varname)
if (variable.type == DataType.UWORD) {
// assume the variable is a pointer (address) and write the byte value to that memory location
if(value.type==DataType.UBYTE)
if(value.type== DataType.UBYTE)
mem.setUByte(variable.integerValue(), value.integerValue().toShort())
else
mem.setSByte(variable.integerValue(), value.integerValue().toShort())
@ -1785,13 +1789,13 @@ class StackVm(private var traceOutputFile: String?) {
val memloc = memoryPointers[varname]
if(memloc!=null) {
// variable is the name of a pointer, write the word value to that memory location
if(value.type==DataType.UWORD) {
if(memloc.second!=DataType.ARRAY_UW)
if(value.type== DataType.UWORD) {
if(memloc.second!= DataType.ARRAY_UW)
throw VmExecutionException("invalid memory pointer type $memloc")
mem.setUWord(memloc.first+index*2, value.integerValue())
}
else {
if(memloc.second!=DataType.ARRAY_W)
if(memloc.second!= DataType.ARRAY_W)
throw VmExecutionException("invalid memory pointer type $memloc")
mem.setSWord(memloc.first+index*2, value.integerValue())
}
@ -1799,7 +1803,7 @@ class StackVm(private var traceOutputFile: String?) {
val variable = getVar(varname)
if (variable.type == DataType.UWORD) {
// assume the variable is a pointer (address) and write the word value to that memory location
if(value.type==DataType.UWORD)
if(value.type== DataType.UWORD)
mem.setUWord(variable.integerValue()+index*2, value.integerValue())
else
mem.setSWord(variable.integerValue()+index*2, value.integerValue())
@ -1824,7 +1828,7 @@ class StackVm(private var traceOutputFile: String?) {
val memloc = memoryPointers[varname]
if(memloc!=null) {
// variable is the name of a pointer, write the float value to that memory location
if(memloc.second!=DataType.ARRAY_F)
if(memloc.second!= DataType.ARRAY_F)
throw VmExecutionException("invalid memory pointer type $memloc")
mem.setFloat(memloc.first+index*5, value.numericValue().toDouble())
} else {
@ -2268,9 +2272,9 @@ class StackVm(private var traceOutputFile: String?) {
val numbytes = evalstack.pop().integerValue()
val bytevalue = value.integerValue().toShort()
when {
value.type==DataType.UBYTE -> for(addr in address until address+numbytes)
value.type== DataType.UBYTE -> for(addr in address until address+numbytes)
mem.setUByte(addr, bytevalue)
value.type==DataType.BYTE -> for(addr in address until address+numbytes)
value.type== DataType.BYTE -> for(addr in address until address+numbytes)
mem.setSByte(addr, bytevalue)
else -> throw VmExecutionException("(u)byte value expected")
}
@ -2281,9 +2285,9 @@ class StackVm(private var traceOutputFile: String?) {
val numwords = evalstack.pop().integerValue()
val wordvalue = value.integerValue()
when {
value.type==DataType.UWORD -> for(addr in address until address+numwords*2 step 2)
value.type== DataType.UWORD -> for(addr in address until address+numwords*2 step 2)
mem.setUWord(addr, wordvalue)
value.type==DataType.WORD -> for(addr in address until address+numwords*2 step 2)
value.type== DataType.WORD -> for(addr in address until address+numwords*2 step 2)
mem.setSWord(addr, wordvalue)
else -> throw VmExecutionException("(u)word value expected")
}

View File

@ -2,9 +2,9 @@ package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.DataType
import prog8.ast.LiteralValue
import prog8.ast.Position
import prog8.ast.base.DataType
import prog8.ast.expressions.LiteralValue
import prog8.ast.base.Position
import kotlin.test.*
@ -16,7 +16,7 @@ private fun sameValueAndType(lv1: LiteralValue, lv2: LiteralValue): Boolean {
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestParserLiteralValue {
private val dummyPos = Position("test", 0,0,0)
private val dummyPos = Position("test", 0, 0, 0)
@Test
fun testIdentity() {
@ -33,99 +33,99 @@ class TestParserLiteralValue {
@Test
fun testEqualsAndNotEquals() {
assertEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UBYTE, 100, position=dummyPos))
assertEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=100, position=dummyPos))
assertEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos), LiteralValue(DataType.UBYTE, 254, position=dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12345.0, position=dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos), LiteralValue(DataType.UBYTE, 100, position=dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue=22239.0, position=dummyPos), LiteralValue(DataType.UWORD,wordvalue=22239, position=dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos))
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos))
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos))
assertTrue(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UBYTE, 100, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=100, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos), LiteralValue(DataType.UBYTE, 254, position=dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12345.0, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos), LiteralValue(DataType.UBYTE, 100, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=22239.0, position=dummyPos), LiteralValue(DataType.UWORD,wordvalue=22239, position=dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos)))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UBYTE, 101, position=dummyPos))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=101, position=dummyPos))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=101.0, position=dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue=245, position=dummyPos), LiteralValue(DataType.UBYTE, 246, position=dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=12346, position=dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12346.0, position=dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.UBYTE, 9, position=dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=9, position=dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.0, position=dummyPos))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 101, position = dummyPos))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UBYTE, 101, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=101, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=101.0, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=245, position=dummyPos), LiteralValue(DataType.UBYTE, 246, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=12346, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12346.0, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.UBYTE, 9, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=9, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.0, position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 101, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position=dummyPos), LiteralValue(DataType.STR, strvalue="hello", position=dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position=dummyPos), LiteralValue(DataType.STR, strvalue="bye", position=dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos), LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos), LiteralValue(DataType.STR, strvalue = "bye", position = dummyPos)))
val lvOne = LiteralValue(DataType.UBYTE, 1, position=dummyPos)
val lvTwo = LiteralValue(DataType.UBYTE, 2, position=dummyPos)
val lvThree = LiteralValue(DataType.UBYTE, 3, position=dummyPos)
val lvOneR = LiteralValue(DataType.UBYTE, 1, position=dummyPos)
val lvTwoR = LiteralValue(DataType.UBYTE, 2, position=dummyPos)
val lvThreeR = LiteralValue(DataType.UBYTE, 3, position=dummyPos)
val lvFour= LiteralValue(DataType.UBYTE, 4, position=dummyPos)
val lv1 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOne, lvTwo, lvThree), position=dummyPos)
val lv2 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvThreeR), position=dummyPos)
val lv3 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvFour), position=dummyPos)
val lvOne = LiteralValue(DataType.UBYTE, 1, position = dummyPos)
val lvTwo = LiteralValue(DataType.UBYTE, 2, position = dummyPos)
val lvThree = LiteralValue(DataType.UBYTE, 3, position = dummyPos)
val lvOneR = LiteralValue(DataType.UBYTE, 1, position = dummyPos)
val lvTwoR = LiteralValue(DataType.UBYTE, 2, position = dummyPos)
val lvThreeR = LiteralValue(DataType.UBYTE, 3, position = dummyPos)
val lvFour= LiteralValue(DataType.UBYTE, 4, position = dummyPos)
val lv1 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos)
val lv2 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos)
val lv3 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos)
assertEquals(lv1, lv2)
assertNotEquals(lv1, lv3)
}
@Test
fun testGreaterThan(){
assertTrue(LiteralValue(DataType.UBYTE, 100, position=dummyPos) > LiteralValue(DataType.UBYTE, 99, position=dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) > LiteralValue(DataType.UWORD, wordvalue=253, position=dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) > LiteralValue(DataType.FLOAT, floatvalue=99.9, position=dummyPos))
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 99, position = dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos))
assertTrue(LiteralValue(DataType.UBYTE, 100, position=dummyPos) >= LiteralValue(DataType.UBYTE, 100, position=dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) >= LiteralValue(DataType.UWORD,wordvalue= 254, position=dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) >= LiteralValue(DataType.UBYTE, 100, position = dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position=dummyPos) > LiteralValue(DataType.UBYTE, 100, position=dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) > LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) > LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 100, position = dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position=dummyPos) >= LiteralValue(DataType.UBYTE, 101, position=dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) >= LiteralValue(DataType.UWORD,wordvalue= 255, position=dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue=100.1, position=dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) >= LiteralValue(DataType.UBYTE, 101, position = dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos))
}
@Test
fun testLessThan() {
assertTrue(LiteralValue(DataType.UBYTE, 100, position=dummyPos) < LiteralValue(DataType.UBYTE, 101, position=dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) < LiteralValue(DataType.UWORD, wordvalue=255, position=dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) < LiteralValue(DataType.FLOAT, floatvalue=100.1, position=dummyPos))
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 101, position = dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos))
assertTrue(LiteralValue(DataType.UBYTE, 100, position=dummyPos) <= LiteralValue(DataType.UBYTE, 100, position=dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) <= LiteralValue(DataType.UWORD,wordvalue= 254, position=dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) <= LiteralValue(DataType.UBYTE, 100, position = dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position=dummyPos) < LiteralValue(DataType.UBYTE, 100, position=dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) < LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) < LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 100, position = dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position=dummyPos) <= LiteralValue(DataType.UBYTE, 99, position=dummyPos))
assertFalse(LiteralValue(DataType.UWORD,wordvalue= 254, position=dummyPos) <= LiteralValue(DataType.UWORD,wordvalue= 253, position=dummyPos))
assertFalse(LiteralValue(DataType.FLOAT,floatvalue= 100.0, position=dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue=99.9, position=dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) <= LiteralValue(DataType.UBYTE, 99, position = dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos))
}
}

View File

@ -2,7 +2,7 @@ package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.DataType
import prog8.ast.base.DataType
import prog8.compiler.RuntimeValue
import kotlin.test.*

View File

@ -4,10 +4,10 @@ import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.empty
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.ByteDatatypes
import prog8.ast.DataType
import prog8.ast.IterableDatatypes
import prog8.ast.WordDatatypes
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.IterableDatatypes
import prog8.ast.base.WordDatatypes
import prog8.compiler.RuntimeValue
import prog8.compiler.HeapValues
import prog8.compiler.intermediate.Instruction

View File

@ -5,7 +5,9 @@ import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.LiteralValue
import prog8.compiler.RuntimeValue
import prog8.compiler.*
import prog8.compiler.target.c64.*
@ -334,8 +336,8 @@ class TestPetscii {
@Test
fun testLiteralValueComparisons() {
val ten = LiteralValue(DataType.UWORD, wordvalue=10, position=Position("", 0 ,0 ,0))
val nine = LiteralValue(DataType.UBYTE, bytevalue=9, position=Position("", 0 ,0 ,0))
val ten = LiteralValue(DataType.UWORD, wordvalue = 10, position = Position("", 0, 0, 0))
val nine = LiteralValue(DataType.UBYTE, bytevalue = 9, position = Position("", 0, 0, 0))
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
@ -351,8 +353,8 @@ class TestPetscii {
assertTrue(ten <= ten)
assertFalse(ten < ten)
val abc = LiteralValue(DataType.STR, strvalue = "abc", position=Position("", 0 ,0 ,0))
val abd = LiteralValue(DataType.STR, strvalue = "abd", position=Position("", 0 ,0 ,0))
val abc = LiteralValue(DataType.STR, strvalue = "abc", position = Position("", 0, 0, 0))
val abd = LiteralValue(DataType.STR, strvalue = "abd", position = Position("", 0, 0, 0))
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)