mirror of
https://github.com/irmen/prog8.git
synced 2026-10-23 12:02:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c23ae14ab | ||
|
|
6f000d0d26 | ||
|
|
9d7eb3be5a | ||
|
|
835555171e | ||
|
|
68ce4a1bf0 | ||
|
|
a995867deb | ||
|
|
6bd99d63b4 | ||
|
|
baf5d3041a | ||
|
|
a326ffa00a | ||
|
|
d28dd92b47 | ||
|
|
1de328b2e8 | ||
|
|
51bb902162 | ||
|
|
4fd14f1366 | ||
|
|
91d9559f79 | ||
|
|
3245a9b157 | ||
|
|
2b28493bba | ||
|
|
1382728bd2 | ||
|
|
0422ad080a | ||
|
|
64d682bfde | ||
|
|
b182f7e693 | ||
|
|
e6be428589 | ||
|
|
85c7f8314b | ||
|
|
796d07a7f8 | ||
|
|
2af86a10b2 | ||
|
|
7fbe486dff | ||
|
|
87e5a9859a |
No files matched your search
@@ -34,6 +34,7 @@ dependencies {
|
||||
implementation 'org.antlr:antlr4-runtime:4.8'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
|
||||
// implementation 'net.razorvine:ksim65:1.6'
|
||||
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
|
||||
implementation project(':parser')
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
@@ -100,7 +101,7 @@ test {
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -975,6 +975,7 @@ asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64utils.uword2decimal
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
ldy #0
|
||||
- lda c64utils.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
|
||||
@@ -651,6 +651,18 @@ greatereq_w .proc
|
||||
bmi equal_b._equal_b_false
|
||||
.pend
|
||||
|
||||
|
||||
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
|
||||
|
||||
func_exit .proc
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
lda c64.ESTACK_LO+1,x
|
||||
ldx orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
.pend
|
||||
|
||||
|
||||
func_read_flags .proc
|
||||
; -- put the processor status register on the stack
|
||||
php
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.80
|
||||
1.81
|
||||
@@ -2,7 +2,8 @@ package prog8
|
||||
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
|
||||
@@ -331,6 +331,11 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
whileLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(foreverLoop: ForeverLoop) {
|
||||
output("forever ")
|
||||
foreverLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
output("repeat ")
|
||||
repeatLoop.body.accept(this)
|
||||
|
||||
@@ -158,6 +158,30 @@ interface INameScope {
|
||||
if(!statements.remove(stmt))
|
||||
throw FatalAstException("stmt to remove wasn't found in scope")
|
||||
}
|
||||
|
||||
fun getAllLabels(label: String): List<Label> {
|
||||
val result = mutableListOf<Label>()
|
||||
|
||||
fun find(scope: INameScope) {
|
||||
scope.statements.forEach {
|
||||
when(it) {
|
||||
is Label -> result.add(it)
|
||||
is INameScope -> find(it)
|
||||
is IfStatement -> {
|
||||
find(it.truepart)
|
||||
find(it.elsepart)
|
||||
}
|
||||
is RepeatLoop -> find(it.body)
|
||||
is ForeverLoop -> find(it.body)
|
||||
is WhileLoop -> find(it.body)
|
||||
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
find(this)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
interface IAssignable {
|
||||
@@ -246,7 +270,7 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
||||
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
||||
null -> null
|
||||
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
|
||||
else -> throw SyntaxError("wrong identifier target: $stmt", stmt.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ import java.nio.file.Path
|
||||
|
||||
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
||||
|
||||
|
||||
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
|
||||
internal 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)
|
||||
val directives = this.directive().map { it.toAst() }
|
||||
val blocks = this.block().map { it.toAst(isLibrary) }
|
||||
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source)
|
||||
}
|
||||
|
||||
|
||||
private fun ParserRuleContext.toPosition() : Position {
|
||||
val customTokensource = this.start.tokenSource as? CustomLexer
|
||||
val filename =
|
||||
@@ -38,27 +38,23 @@ private fun ParserRuleContext.toPosition() : Position {
|
||||
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : Statement {
|
||||
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) : Statement {
|
||||
val blockstatements = block_statement().map {
|
||||
when {
|
||||
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
|
||||
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
|
||||
it.directive()!=null -> it.directive().toAst()
|
||||
it.inlineasm()!=null -> it.inlineasm().toAst()
|
||||
else -> throw FatalAstException("weird block statement $it")
|
||||
}
|
||||
}
|
||||
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement =
|
||||
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
|
||||
|
||||
|
||||
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||
|
||||
|
||||
private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
|
||||
vardecl()?.let { return it.toAst() }
|
||||
|
||||
varinitializer()?.let {
|
||||
@@ -66,7 +62,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
return VarDecl(
|
||||
VarDeclType.VAR,
|
||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.varname.text,
|
||||
null,
|
||||
@@ -114,7 +110,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
return VarDecl(
|
||||
VarDeclType.CONST,
|
||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.varname.text,
|
||||
null,
|
||||
@@ -131,7 +127,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
return VarDecl(
|
||||
VarDeclType.MEMORY,
|
||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.varname.text,
|
||||
null,
|
||||
@@ -142,6 +138,28 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
)
|
||||
}
|
||||
|
||||
structdecl()?.let {
|
||||
return StructDecl(it.identifier().text,
|
||||
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
|
||||
toPosition())
|
||||
}
|
||||
|
||||
throw FatalAstException("weird variable decl $this")
|
||||
}
|
||||
|
||||
private fun prog8Parser.SubroutinedeclarationContext.toAst() : Subroutine {
|
||||
return when {
|
||||
subroutine()!=null -> subroutine().toAst()
|
||||
asmsubroutine()!=null -> asmsubroutine().toAst()
|
||||
romsubroutine()!=null -> romsubroutine().toAst()
|
||||
else -> throw FatalAstException("weird subroutine decl $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
val vardecl = variabledeclaration()?.toAst()
|
||||
if(vardecl!=null) return vardecl
|
||||
|
||||
assignment()?.let {
|
||||
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
|
||||
}
|
||||
@@ -175,8 +193,8 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
val returnstmt = returnstmt()?.toAst()
|
||||
if(returnstmt!=null) return returnstmt
|
||||
|
||||
val sub = subroutine()?.toAst()
|
||||
if(sub!=null) return sub
|
||||
val subroutine = subroutinedeclaration()?.toAst()
|
||||
if(subroutine!=null) return subroutine
|
||||
|
||||
val asm = inlineasm()?.toAst()
|
||||
if(asm!=null) return asm
|
||||
@@ -193,31 +211,22 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
val whileloop = whileloop()?.toAst()
|
||||
if(whileloop!=null) return whileloop
|
||||
|
||||
val foreverloop = foreverloop()?.toAst()
|
||||
if(foreverloop!=null) return foreverloop
|
||||
|
||||
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
|
||||
|
||||
val romsubstmt = romsubroutine()?.toAst()
|
||||
if(romsubstmt!=null) return romsubstmt
|
||||
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
if(whenstmt!=null) return whenstmt
|
||||
|
||||
structdecl()?.let {
|
||||
return StructDecl(it.identifier().text,
|
||||
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
|
||||
toPosition())
|
||||
}
|
||||
|
||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||
}
|
||||
|
||||
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
|
||||
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
|
||||
val subdecl = asmsub_decl().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf()
|
||||
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||
@@ -225,7 +234,7 @@ private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
|
||||
subdecl.asmClobbers, null, true, statements, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.RomsubroutineContext.toAst(): Statement {
|
||||
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
|
||||
val subdecl = asmsub_decl().toAst()
|
||||
val address = integerliteral().toAst().number.toInt()
|
||||
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||
@@ -233,7 +242,6 @@ private fun prog8Parser.RomsubroutineContext.toAst(): Statement {
|
||||
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
|
||||
}
|
||||
|
||||
|
||||
private class AsmsubDecl(val name: String,
|
||||
val parameters: List<SubroutineParameter>,
|
||||
val returntypes: List<DataType>,
|
||||
@@ -241,7 +249,6 @@ private class AsmsubDecl(val name: String,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<Register>)
|
||||
|
||||
|
||||
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
val name = identifier().text
|
||||
val params = asmsub_params()?.toAst() ?: emptyList()
|
||||
@@ -254,7 +261,6 @@ private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
|
||||
}
|
||||
|
||||
|
||||
private class AsmSubroutineParameter(name: String,
|
||||
type: DataType,
|
||||
val registerOrPair: RegisterOrPair?,
|
||||
@@ -271,7 +277,6 @@ private class AsmSubroutineReturn(val type: DataType,
|
||||
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()) }
|
||||
|
||||
@@ -285,10 +290,8 @@ private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParamete
|
||||
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
|
||||
|
||||
|
||||
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||
val void = this.VOID() != null
|
||||
val location = scoped_identifier().toAst()
|
||||
@@ -298,7 +301,6 @@ private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
||||
val location = scoped_identifier().toAst()
|
||||
return if(expression_list() == null)
|
||||
@@ -307,11 +309,9 @@ private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
||||
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.InlineasmContext.toAst() =
|
||||
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
||||
|
||||
|
||||
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
|
||||
return Return(expression()?.toAst(), toPosition())
|
||||
}
|
||||
@@ -322,11 +322,9 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
|
||||
return Jump(address, identifier, null, toPosition())
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
||||
Label(children[0].text, toPosition())
|
||||
|
||||
|
||||
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
||||
return Subroutine(identifier().text,
|
||||
sub_params()?.toAst() ?: emptyList(),
|
||||
@@ -345,14 +343,12 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
|
||||
return returns.datatype().map { it.toAst() }
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
vardecl().map {
|
||||
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
|
||||
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
||||
val register = register()?.toAst()
|
||||
val identifier = scoped_identifier()
|
||||
@@ -371,15 +367,12 @@ private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperC
|
||||
|
||||
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 {
|
||||
val str = stringliteral()
|
||||
if(str?.ALT_STRING_ENCODING() != null)
|
||||
@@ -388,7 +381,6 @@ private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
|
||||
return 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
|
||||
@@ -440,7 +432,6 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||
|
||||
val litval = literalvalue()
|
||||
@@ -526,39 +517,31 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||
throw FatalAstException(text)
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue =
|
||||
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
|
||||
|
||||
|
||||
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<Expression> =
|
||||
expression().map { it.toAst() }.toTypedArray()
|
||||
|
||||
@@ -576,7 +559,6 @@ private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
|
||||
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())
|
||||
@@ -589,7 +571,6 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
||||
|
||||
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|
||||
|
||||
|
||||
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||
val loopregister = register()?.toAst()
|
||||
val loopvar = identifier()?.toAst()
|
||||
@@ -602,12 +583,10 @@ private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||
return ForLoop(loopregister, 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())
|
||||
@@ -616,6 +595,12 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
||||
return WhileLoop(condition, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.ForeverloopContext.toAst(): ForeverLoop {
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return ForeverLoop(scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
val untilCondition = expression().toAst()
|
||||
@@ -681,4 +666,3 @@ internal fun unescape(str: String, position: Position): String {
|
||||
}
|
||||
return result.joinToString("")
|
||||
}
|
||||
|
||||
@@ -3,35 +3,42 @@ package prog8.ast.base
|
||||
import prog8.parser.ParsingFailedError
|
||||
|
||||
|
||||
fun printErrors(errors: List<Any>, moduleName: String) {
|
||||
val reportedMessages = mutableSetOf<String>()
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
errors.forEach {
|
||||
val msg = it.toString()
|
||||
if(msg !in reportedMessages) {
|
||||
System.err.println(msg)
|
||||
reportedMessages.add(msg)
|
||||
}
|
||||
class ErrorReporter {
|
||||
private enum class MessageSeverity {
|
||||
WARNING,
|
||||
ERROR
|
||||
}
|
||||
System.err.print("\u001b[0m") // reset color
|
||||
if(reportedMessages.isNotEmpty())
|
||||
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
|
||||
}
|
||||
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position?)
|
||||
|
||||
private val messages = mutableListOf<CompilerMessage>()
|
||||
private val alreadyReportedMessages = mutableSetOf<String>()
|
||||
|
||||
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 err(msg: String, position: Position?) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
|
||||
fun warn(msg: String, position: Position?) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
|
||||
|
||||
fun handle() {
|
||||
var numErrors = 0
|
||||
var numWarnings = 0
|
||||
messages.forEach {
|
||||
when(it.severity) {
|
||||
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
|
||||
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
|
||||
}
|
||||
val msg = "${it.position} ${it.severity} ${it.message}".trim()
|
||||
if(msg !in alreadyReportedMessages) {
|
||||
System.err.println(msg)
|
||||
alreadyReportedMessages.add(msg)
|
||||
when(it.severity) {
|
||||
MessageSeverity.WARNING -> numWarnings++
|
||||
MessageSeverity.ERROR -> numErrors++
|
||||
}
|
||||
}
|
||||
System.err.print("\u001b[0m") // reset color
|
||||
}
|
||||
messages.clear()
|
||||
if(numErrors>0)
|
||||
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
||||
}
|
||||
|
||||
fun printWarning(msg: String) {
|
||||
print("\u001b[93m") // bright yellow
|
||||
print("Warning: $msg")
|
||||
print("\u001b[0m\n") // normal
|
||||
fun isEmpty() = messages.isEmpty()
|
||||
}
|
||||
@@ -6,17 +6,13 @@ 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) {
|
||||
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
|
||||
override fun toString() = "$position Syntax error: $message"
|
||||
}
|
||||
|
||||
open class NameError(override var message: String, val position: Position) : AstException(message) {
|
||||
override fun toString() = "$position Name error: $message"
|
||||
}
|
||||
|
||||
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
||||
override fun toString() = "$position Error: $message"
|
||||
}
|
||||
|
||||
class UndefinedSymbolError(symbol: IdentifierReference)
|
||||
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||
@@ -17,17 +17,15 @@ internal fun Program.removeNopsFlattenAnonScopes() {
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
|
||||
val checker = AstChecker(this, compilerOptions)
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
|
||||
val checker = AstChecker(this, compilerOptions, errors)
|
||||
checker.visit(this)
|
||||
printErrors(checker.result(), name)
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.anonscopeVarsCleanup() {
|
||||
val mover = AnonymousScopeVarsCleanup(this)
|
||||
internal fun Program.anonscopeVarsCleanup(errors: ErrorReporter) {
|
||||
val mover = AnonymousScopeVarsCleanup(errors)
|
||||
mover.visit(this)
|
||||
printErrors(mover.result(), name)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,31 +37,34 @@ internal fun Program.reorderStatements() {
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts() {
|
||||
val caster = TypecastsAdder(this)
|
||||
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
val caster = TypecastsAdder(this, errors)
|
||||
caster.visit(this)
|
||||
}
|
||||
|
||||
internal fun Module.checkImportedValid() {
|
||||
val checker = ImportedModuleDirectiveRemover()
|
||||
internal fun Module.checkImportedValid(errors: ErrorReporter) {
|
||||
val checker = ImportedModuleDirectiveRemover(errors)
|
||||
checker.visit(this)
|
||||
printErrors(checker.result(), name)
|
||||
}
|
||||
|
||||
internal fun Program.checkRecursion() {
|
||||
val checker = AstRecursionChecker(namespace)
|
||||
internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||
val checker = AstRecursionChecker(namespace, errors)
|
||||
checker.visit(this)
|
||||
printErrors(checker.result(), name)
|
||||
checker.processMessages(name)
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.checkIdentifiers() {
|
||||
val checker = AstIdentifiersChecker(this)
|
||||
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
val checker = AstIdentifiersChecker(this, errors)
|
||||
checker.visit(this)
|
||||
|
||||
if(modules.map {it.name}.toSet().size != modules.size) {
|
||||
throw FatalAstException("modules should all be unique")
|
||||
}
|
||||
}
|
||||
|
||||
printErrors(checker.result(), name)
|
||||
|
||||
internal fun Program.makeForeverLoops() {
|
||||
val checker = MakeForeverLoops()
|
||||
checker.visit(this)
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.NotConstArgumentException
|
||||
import prog8.functions.builtinFunctionReturnType
|
||||
import java.util.Objects
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package prog8.ast.expressions
|
||||
|
||||
import java.util.Objects
|
||||
import prog8.ast.base.DataType
|
||||
import java.util.*
|
||||
|
||||
|
||||
object InferredTypes {
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.ast.base.NameError
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Statement
|
||||
import prog8.ast.statements.VarDecl
|
||||
|
||||
class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
|
||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||
class AnonymousScopeVarsCleanup(private val errors: ErrorReporter): IAstModifyingVisitor {
|
||||
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
|
||||
|
||||
fun result(): List<AstException> {
|
||||
return checkResult
|
||||
}
|
||||
|
||||
override fun visit(program: Program) {
|
||||
varsToMove.clear()
|
||||
super.visit(program)
|
||||
@@ -25,7 +19,7 @@ class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
|
||||
decls.forEach {
|
||||
val existing = existingVariables[it.name]
|
||||
if (existing!=null) {
|
||||
checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position))
|
||||
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
|
||||
conflicts = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
@@ -13,27 +12,23 @@ import prog8.functions.BuiltinFunctions
|
||||
import java.io.File
|
||||
|
||||
internal class AstChecker(private val program: Program,
|
||||
private val compilerOptions: CompilationOptions) : IAstVisitor {
|
||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||
|
||||
fun result(): List<AstException> {
|
||||
return checkResult
|
||||
}
|
||||
private val compilerOptions: CompilationOptions,
|
||||
private val errors: ErrorReporter) : IAstVisitor {
|
||||
|
||||
override fun visit(program: Program) {
|
||||
assert(program === this.program)
|
||||
// there must be a single 'main' block with a 'start' subroutine for the program entry point.
|
||||
val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
|
||||
if(mainBlocks.size>1)
|
||||
checkResult.add(SyntaxError("more than one 'main' block", mainBlocks[0].position))
|
||||
errors.err("more than one 'main' block", mainBlocks[0].position)
|
||||
|
||||
for(mainBlock in mainBlocks) {
|
||||
val startSub = mainBlock.subScopes()["start"] as? Subroutine
|
||||
if (startSub == null) {
|
||||
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position))
|
||||
errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)
|
||||
} else {
|
||||
if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
|
||||
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position))
|
||||
errors.err("program entrypoint subroutine can't have parameters and/or return values", startSub.position)
|
||||
}
|
||||
|
||||
// the main module cannot contain 'regular' statements (they will never be executed!)
|
||||
@@ -50,7 +45,7 @@ internal class AstChecker(private val program: Program,
|
||||
else -> false
|
||||
}
|
||||
if (!ok) {
|
||||
checkResult.add(SyntaxError("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position))
|
||||
errors.err("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -60,12 +55,12 @@ internal class AstChecker(private val program: Program,
|
||||
// which will be used as the 60hz irq routine in the vm if it's present
|
||||
val irqBlocks = program.modules.flatMap { it.statements }.filter { it is Block && it.name=="irq" }.map { it as Block }
|
||||
if(irqBlocks.size>1)
|
||||
checkResult.add(SyntaxError("more than one 'irq' block", irqBlocks[0].position))
|
||||
errors.err("more than one 'irq' block", irqBlocks[0].position)
|
||||
for(irqBlock in irqBlocks) {
|
||||
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine
|
||||
if (irqSub != null) {
|
||||
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
|
||||
checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position))
|
||||
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +73,7 @@ internal class AstChecker(private val program: Program,
|
||||
directives.filter { it.value.size > 1 }.forEach{ entry ->
|
||||
when(entry.key) {
|
||||
"%output", "%launcher", "%zeropage", "%address" ->
|
||||
entry.value.mapTo(checkResult) { SyntaxError("directive can just occur once", it.position) }
|
||||
entry.value.forEach { errors.err("directive can just occur once", it.position) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,18 +85,18 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
if(expectedReturnValues.isEmpty() && returnStmt.value!=null) {
|
||||
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
|
||||
errors.err("invalid number of return values", returnStmt.position)
|
||||
}
|
||||
if(expectedReturnValues.isNotEmpty() && returnStmt.value==null) {
|
||||
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
|
||||
errors.err("invalid number of return values", returnStmt.position)
|
||||
}
|
||||
if(expectedReturnValues.size==1 && returnStmt.value!=null) {
|
||||
val valueDt = returnStmt.value!!.inferType(program)
|
||||
if(!valueDt.isKnown) {
|
||||
checkResult.add(ExpressionError("return value type mismatch", returnStmt.value!!.position))
|
||||
errors.err("return value type mismatch", returnStmt.value!!.position)
|
||||
} else {
|
||||
if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.STRUCT))
|
||||
checkResult.add(ExpressionError("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position))
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position)
|
||||
}
|
||||
}
|
||||
super.visit(returnStmt)
|
||||
@@ -109,53 +104,53 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(ifStatement: IfStatement) {
|
||||
if(ifStatement.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
checkResult.add(ExpressionError("condition value should be an integer type", ifStatement.condition.position))
|
||||
errors.err("condition value should be an integer type", ifStatement.condition.position)
|
||||
super.visit(ifStatement)
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
if(forLoop.body.containsNoCodeNorVars())
|
||||
printWarning("for loop body is empty", forLoop.position)
|
||||
errors.warn("for loop body is empty", forLoop.position)
|
||||
|
||||
val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
||||
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
|
||||
errors.err("can only loop over an iterable type", forLoop.position)
|
||||
} else {
|
||||
if (forLoop.loopRegister != null) {
|
||||
// loop register
|
||||
if (iterableDt != DataType.ARRAY_UB && iterableDt != DataType.ARRAY_B && iterableDt != DataType.STR)
|
||||
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
|
||||
errors.err("register can only loop over bytes", forLoop.position)
|
||||
if(forLoop.loopRegister!=Register.A)
|
||||
checkResult.add(ExpressionError("it's only possible to use A as a loop register", forLoop.position))
|
||||
errors.err("it's only possible to use A as a loop register", forLoop.position)
|
||||
} else {
|
||||
// loop variable
|
||||
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
|
||||
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
|
||||
checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position))
|
||||
errors.err("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 != DataType.STR)
|
||||
checkResult.add(ExpressionError("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position))
|
||||
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
||||
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))
|
||||
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
|
||||
checkResult.add(ExpressionError("byte loop variable can only loop over bytes", forLoop.position))
|
||||
errors.err("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)
|
||||
checkResult.add(ExpressionError("word loop variable can only loop over bytes or words", forLoop.position))
|
||||
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
checkResult.add(ExpressionError("for loop only supports integers", forLoop.position))
|
||||
errors.err("for loop only supports integers", forLoop.position)
|
||||
}
|
||||
else -> checkResult.add(ExpressionError("loop variable must be numeric type", forLoop.position))
|
||||
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,18 +164,18 @@ internal class AstChecker(private val program: Program,
|
||||
val targetStatement = checkFunctionOrLabelExists(jump.identifier, jump)
|
||||
if(targetStatement!=null) {
|
||||
if(targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
checkResult.add(SyntaxError("can't jump to a builtin function", jump.position))
|
||||
errors.err("can't jump to a builtin function", jump.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(jump.address!=null && (jump.address < 0 || jump.address > 65535))
|
||||
checkResult.add(SyntaxError("jump address must be valid integer 0..\$ffff", jump.position))
|
||||
errors.err("jump address must be valid integer 0..\$ffff", jump.position)
|
||||
super.visit(jump)
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
if(block.address!=null && (block.address<0 || block.address>65535)) {
|
||||
checkResult.add(SyntaxError("block memory address must be valid integer 0..\$ffff", block.position))
|
||||
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
|
||||
}
|
||||
|
||||
super.visit(block)
|
||||
@@ -189,15 +184,13 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(label: Label) {
|
||||
// scope check
|
||||
if(label.parent !is Block && label.parent !is Subroutine && label.parent !is AnonymousScope) {
|
||||
checkResult.add(SyntaxError("Labels can only be defined in the scope of a block, a loop body, or within another subroutine", label.position))
|
||||
errors.err("Labels can only be defined in the scope of a block, a loop body, or within another subroutine", label.position)
|
||||
}
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
fun err(msg: String) {
|
||||
checkResult.add(SyntaxError(msg, subroutine.position))
|
||||
}
|
||||
fun err(msg: String) = errors.err(msg, subroutine.position)
|
||||
|
||||
if(subroutine.name in BuiltinFunctions)
|
||||
err("cannot redefine a built-in function")
|
||||
@@ -327,36 +320,37 @@ internal class AstChecker(private val program: Program,
|
||||
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword for their address, or access the variable from the outer scope directly.")
|
||||
}
|
||||
}
|
||||
|
||||
visitStatements(subroutine.statements)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
|
||||
printWarning("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
|
||||
if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
checkResult.add(ExpressionError("condition value should be an integer type", repeatLoop.untilCondition.position))
|
||||
errors.err("condition value should be an integer type", repeatLoop.untilCondition.position)
|
||||
super.visit(repeatLoop)
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop) {
|
||||
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
|
||||
printWarning("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
|
||||
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
checkResult.add(ExpressionError("condition value should be an integer type", whileLoop.condition.position))
|
||||
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
||||
super.visit(whileLoop)
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
|
||||
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
|
||||
if(assignment.value is FunctionCall) {
|
||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if (stmt is Subroutine && stmt.isAsmSubroutine) {
|
||||
if(stmt.returntypes.size>1)
|
||||
checkResult.add(ExpressionError("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position))
|
||||
errors.err("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position)
|
||||
else {
|
||||
val idt = assignment.target.inferType(program, assignment)
|
||||
if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
|
||||
checkResult.add(ExpressionError("return type mismatch", assignment.value.position))
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,7 +363,7 @@ internal class AstChecker(private val program: Program,
|
||||
val targetVar = targetIdent.targetVarDecl(program.namespace)
|
||||
if(sourceVar?.struct!=null && targetVar?.struct!=null) {
|
||||
if(sourceVar.struct!==targetVar.struct)
|
||||
checkResult.add(ExpressionError("assignment of different struct types", assignment.position))
|
||||
errors.err("assignment of different struct types", assignment.position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +376,7 @@ internal class AstChecker(private val program: Program,
|
||||
val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
|
||||
if (memAddr != null) {
|
||||
if (memAddr < 0 || memAddr >= 65536)
|
||||
checkResult.add(ExpressionError("address out of range", assignTarget.position))
|
||||
errors.err("address out of range", assignTarget.position)
|
||||
}
|
||||
|
||||
val assignment = assignTarget.parent as Statement
|
||||
@@ -392,16 +386,16 @@ internal class AstChecker(private val program: Program,
|
||||
val targetSymbol = program.namespace.lookup(targetName, assignment)
|
||||
when (targetSymbol) {
|
||||
null -> {
|
||||
checkResult.add(UndefinedSymbolError(targetIdentifier))
|
||||
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
|
||||
return
|
||||
}
|
||||
!is VarDecl -> {
|
||||
checkResult.add(SyntaxError("assignment LHS must be register or variable", assignment.position))
|
||||
errors.err("assignment LHS must be register or variable", assignment.position)
|
||||
return
|
||||
}
|
||||
else -> {
|
||||
if (targetSymbol.type == VarDeclType.CONST) {
|
||||
checkResult.add(ExpressionError("cannot assign new value to a constant", assignment.position))
|
||||
errors.err("cannot assign new value to a constant", assignment.position)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -409,7 +403,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR)
|
||||
if(targetDt in IterableDatatypes)
|
||||
checkResult.add(SyntaxError("cannot assign to a string or array type", assignTarget.position))
|
||||
errors.err("cannot assign to a string or array type", assignTarget.position)
|
||||
|
||||
if (assignment is Assignment) {
|
||||
|
||||
@@ -427,9 +421,9 @@ internal class AstChecker(private val program: Program,
|
||||
if (assignment.value is FunctionCall) {
|
||||
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if (targetStmt != null)
|
||||
checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position))
|
||||
errors.err("function call doesn't return a suitable value to use in assignment", assignment.value.position)
|
||||
} else
|
||||
checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position))
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
|
||||
sourceDatatype.typeOrElse(DataType.BYTE), assignment.value, assignment.position)
|
||||
@@ -442,17 +436,17 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(addressOf: AddressOf) {
|
||||
val variable=addressOf.identifier.targetVarDecl(program.namespace)
|
||||
if(variable==null)
|
||||
checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position))
|
||||
errors.err("pointer-of operand must be the name of a heap variable", addressOf.position)
|
||||
else {
|
||||
if(variable.datatype !in ArrayDatatypes && variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
|
||||
checkResult.add(ExpressionError("invalid pointer-of operand type", addressOf.position))
|
||||
errors.err("invalid pointer-of operand type", addressOf.position)
|
||||
}
|
||||
super.visit(addressOf)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
fun err(msg: String, position: Position?=null) {
|
||||
checkResult.add(SyntaxError(msg, position ?: decl.position))
|
||||
err(msg, position ?: decl.position)
|
||||
}
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
@@ -468,19 +462,19 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
// FLOATS
|
||||
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))
|
||||
err("floating point used, but that is not enabled via options")
|
||||
}
|
||||
|
||||
// ARRAY without size specifier MUST have an iterable initializer value
|
||||
if(decl.isArray && decl.arraysize==null) {
|
||||
if(decl.type== VarDeclType.MEMORY)
|
||||
checkResult.add(SyntaxError("memory mapped array must have a size specification", decl.position))
|
||||
err("memory mapped array must have a size specification")
|
||||
if(decl.value==null) {
|
||||
checkResult.add(SyntaxError("array variable is missing a size specification or an initialization value", decl.position))
|
||||
err("array variable is missing a size specification or an initialization value")
|
||||
return
|
||||
}
|
||||
if(decl.value is NumericLiteralValue) {
|
||||
checkResult.add(SyntaxError("unsized array declaration cannot use a single literal initialization value", decl.position))
|
||||
err("unsized array declaration cannot use a single literal initialization value")
|
||||
return
|
||||
}
|
||||
if(decl.value is RangeExpr)
|
||||
@@ -546,19 +540,19 @@ internal class AstChecker(private val program: Program,
|
||||
val struct = decl.struct!!
|
||||
val structLv = decl.value as StructLiteralValue
|
||||
if(struct.numberOfElements != structLv.values.size) {
|
||||
checkResult.add(ExpressionError("struct value has incorrect number of elements", structLv.position))
|
||||
errors.err("struct value has incorrect number of elements", structLv.position)
|
||||
return
|
||||
}
|
||||
for(value in structLv.values.zip(struct.statements)) {
|
||||
val memberdecl = value.second as VarDecl
|
||||
val constValue = value.first.constValue(program)
|
||||
if(constValue==null) {
|
||||
checkResult.add(ExpressionError("struct literal value for field '${memberdecl.name}' should consist of compile-time constants", value.first.position))
|
||||
errors.err("struct literal value for field '${memberdecl.name}' should consist of compile-time constants", value.first.position)
|
||||
return
|
||||
}
|
||||
val memberDt = memberdecl.datatype
|
||||
if(!checkValueTypeAndRange(memberDt, constValue)) {
|
||||
checkResult.add(ExpressionError("struct member value's type is not compatible with member field '${memberdecl.name}'", value.first.position))
|
||||
errors.err("struct member value's type is not compatible with member field '${memberdecl.name}'", value.first.position)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -593,7 +587,7 @@ internal class AstChecker(private val program: Program,
|
||||
} else {
|
||||
val value = decl.value as NumericLiteralValue
|
||||
if (value.type !in IntegerDatatypes || value.number.toInt() < 0 || value.number.toInt() > 65535) {
|
||||
err("memory address must be valid integer 0..\$ffff")
|
||||
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -604,21 +598,24 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(directive: Directive) {
|
||||
fun err(msg: String) {
|
||||
checkResult.add(SyntaxError(msg, directive.position))
|
||||
errors.err(msg, directive.position)
|
||||
}
|
||||
when(directive.directive) {
|
||||
"%output" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
if(directive.parent !is Module)
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=1 || directive.args[0].name != "raw" && directive.args[0].name != "prg")
|
||||
err("invalid output directive type, expected raw or prg")
|
||||
}
|
||||
"%launcher" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
if(directive.parent !is Module)
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=1 || directive.args[0].name != "basic" && directive.args[0].name != "none")
|
||||
err("invalid launcher directive type, expected basic or none")
|
||||
}
|
||||
"%zeropage" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
if(directive.parent !is Module)
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=1 ||
|
||||
directive.args[0].name != "basicsafe" &&
|
||||
directive.args[0].name != "floatsafe" &&
|
||||
@@ -628,35 +625,41 @@ internal class AstChecker(private val program: Program,
|
||||
err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, dontuse, or full")
|
||||
}
|
||||
"%zpreserved" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
if(directive.parent !is Module)
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=2 || directive.args[0].int==null || directive.args[1].int==null)
|
||||
err("requires two addresses (start, end)")
|
||||
}
|
||||
"%address" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
if(directive.parent !is Module)
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=1 || directive.args[0].int == null)
|
||||
err("invalid address directive, expected numeric address argument")
|
||||
}
|
||||
"%import" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
if(directive.parent !is Module)
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=1 || directive.args[0].name==null)
|
||||
err("invalid import directive, expected module name argument")
|
||||
if(directive.args[0].name == (directive.parent as? Module)?.name)
|
||||
err("invalid import directive, cannot import itself")
|
||||
}
|
||||
"%breakpoint" -> {
|
||||
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
|
||||
if(directive.parent !is INameScope || directive.parent is Module)
|
||||
err("this directive may only occur in a block")
|
||||
if(directive.args.isNotEmpty())
|
||||
err("invalid breakpoint directive, expected no arguments")
|
||||
}
|
||||
"%asminclude" -> {
|
||||
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
|
||||
if(directive.parent !is INameScope || directive.parent is Module)
|
||||
err("this directive may only occur in a block")
|
||||
if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null)
|
||||
err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"")
|
||||
checkFileExists(directive, directive.args[0].str!!)
|
||||
}
|
||||
"%asmbinary" -> {
|
||||
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
|
||||
if(directive.parent !is INameScope || directive.parent is Module)
|
||||
err("this directive may only occur in a block")
|
||||
val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]"
|
||||
if(directive.args.isEmpty()) err(errormsg)
|
||||
else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
|
||||
@@ -666,7 +669,8 @@ internal class AstChecker(private val program: Program,
|
||||
else checkFileExists(directive, directive.args[0].str!!)
|
||||
}
|
||||
"%option" -> {
|
||||
if(directive.parent !is Block && directive.parent !is Module) err("this directive may only occur in a block or at module level")
|
||||
if(directive.parent !is Block && directive.parent !is Module)
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.name in setOf("enable_floats", "force_output")}.any { !it })
|
||||
@@ -682,13 +686,13 @@ internal class AstChecker(private val program: Program,
|
||||
while (definingModule !is Module)
|
||||
definingModule = definingModule.parent
|
||||
if (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile))
|
||||
checkResult.add(NameError("included file not found: $filename", directive.position))
|
||||
errors.err("included file not found: $filename", directive.position)
|
||||
}
|
||||
|
||||
override fun visit(array: ArrayLiteralValue) {
|
||||
if(array.type.isKnown) {
|
||||
if (!compilerOptions.floats && array.type.typeOrElse(DataType.STRUCT) in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", array.position))
|
||||
errors.err("floating point used, but that is not enabled via options", array.position)
|
||||
}
|
||||
val arrayspec = ArrayIndex.forArray(array)
|
||||
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array)
|
||||
@@ -706,7 +710,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(expr.operator=="-") {
|
||||
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
||||
checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position))
|
||||
errors.err("can only take negative of a signed number type", expr.position)
|
||||
}
|
||||
}
|
||||
super.visit(expr)
|
||||
@@ -726,56 +730,56 @@ internal class AstChecker(private val program: Program,
|
||||
val constvalRight = expr.right.constValue(program)
|
||||
val divisor = constvalRight?.number?.toDouble()
|
||||
if(divisor==0.0)
|
||||
checkResult.add(ExpressionError("division by zero", expr.right.position))
|
||||
errors.err("division by zero", expr.right.position)
|
||||
if(expr.operator=="%") {
|
||||
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))
|
||||
errors.err("remainder can only be used on unsigned integer operands", expr.right.position)
|
||||
}
|
||||
}
|
||||
"**" -> {
|
||||
if(leftDt in IntegerDatatypes)
|
||||
checkResult.add(ExpressionError("power operator requires floating point", expr.position))
|
||||
errors.err("power operator requires floating point", expr.position)
|
||||
}
|
||||
"and", "or", "xor" -> {
|
||||
// only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1)
|
||||
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
|
||||
checkResult.add(ExpressionError("logical operator can only be used on boolean operands", expr.right.position))
|
||||
errors.err("logical operator can only be used on boolean operands", expr.right.position)
|
||||
val constLeft = expr.left.constValue(program)
|
||||
val constRight = expr.right.constValue(program)
|
||||
if(constLeft!=null && constLeft.number.toInt() !in 0..1 || constRight!=null && constRight.number.toInt() !in 0..1)
|
||||
checkResult.add(ExpressionError("const literal argument of logical operator must be boolean (0 or 1)", expr.position))
|
||||
errors.err("const literal argument of logical operator must be boolean (0 or 1)", expr.position)
|
||||
}
|
||||
"&", "|", "^" -> {
|
||||
// only integer numeric operands accepted
|
||||
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
|
||||
checkResult.add(ExpressionError("bitwise operator can only be used on integer operands", expr.right.position))
|
||||
errors.err("bitwise operator can only be used on integer operands", expr.right.position)
|
||||
}
|
||||
"<<", ">>" -> {
|
||||
// for now, bit-shifts can only shift by a constant number
|
||||
val constRight = expr.right.constValue(program)
|
||||
if(constRight==null)
|
||||
checkResult.add(ExpressionError("bit-shift can only be done by a constant number (for now)", expr.right.position))
|
||||
errors.err("bit-shift can only be done by a constant number (for now)", expr.right.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt !in NumericDatatypes)
|
||||
checkResult.add(ExpressionError("left operand is not numeric", expr.left.position))
|
||||
errors.err("left operand is not numeric", expr.left.position)
|
||||
if(rightDt!in NumericDatatypes)
|
||||
checkResult.add(ExpressionError("right operand is not numeric", expr.right.position))
|
||||
errors.err("right operand is not numeric", expr.right.position)
|
||||
if(leftDt!=rightDt)
|
||||
checkResult.add(ExpressionError("left and right operands aren't the same type", expr.left.position))
|
||||
errors.err("left and right operands aren't the same type", expr.left.position)
|
||||
super.visit(expr)
|
||||
}
|
||||
|
||||
override fun visit(typecast: TypecastExpression) {
|
||||
if(typecast.type in IterableDatatypes)
|
||||
checkResult.add(ExpressionError("cannot type cast to string or array type", typecast.position))
|
||||
errors.err("cannot type cast to string or array type", typecast.position)
|
||||
super.visit(typecast)
|
||||
}
|
||||
|
||||
override fun visit(range: RangeExpr) {
|
||||
fun err(msg: String) {
|
||||
checkResult.add(SyntaxError(msg, range.position))
|
||||
errors.err(msg, range.position)
|
||||
}
|
||||
super.visit(range)
|
||||
val from = range.from.constValue(program)
|
||||
@@ -795,7 +799,7 @@ internal class AstChecker(private val program: Program,
|
||||
val fromValue = from.number.toInt()
|
||||
val toValue = to.number.toInt()
|
||||
if(fromValue== toValue)
|
||||
printWarning("range is just a single value, don't use a loop here", range.position)
|
||||
errors.warn("range is just a single value, don't use a loop here", range.position)
|
||||
else if(fromValue < toValue && step<=0)
|
||||
err("ascending range requires step > 0")
|
||||
else if(fromValue > toValue && step>=0)
|
||||
@@ -814,6 +818,14 @@ internal class AstChecker(private val program: Program,
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression)
|
||||
if(targetStatement!=null)
|
||||
checkFunctionCall(targetStatement, functionCall.args, functionCall.position)
|
||||
|
||||
// warn about sgn(unsigned) this is likely a mistake
|
||||
if(functionCall.target.nameInSource.last()=="sgn") {
|
||||
val sgnArgType = functionCall.args.first().inferType(program)
|
||||
if(sgnArgType.istype(DataType.UBYTE) || sgnArgType.istype(DataType.UWORD))
|
||||
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
|
||||
}
|
||||
|
||||
super.visit(functionCall)
|
||||
}
|
||||
|
||||
@@ -823,23 +835,23 @@ internal class AstChecker(private val program: Program,
|
||||
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
||||
if(!functionCallStatement.void && targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
|
||||
if(targetStatement.returntypes.size==1)
|
||||
printWarning("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
|
||||
errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
|
||||
else
|
||||
printWarning("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
|
||||
errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() == "sort") {
|
||||
// sort is not supported on float arrays
|
||||
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
|
||||
if(idref!=null && idref.inferType(program).istype(DataType.ARRAY_F)) {
|
||||
checkResult.add(ExpressionError("sorting a floating point array is not supported", functionCallStatement.args.first().position))
|
||||
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
checkResult.add(ExpressionError("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position))
|
||||
errors.err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
@@ -847,13 +859,13 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
private fun checkFunctionCall(target: Statement, args: List<Expression>, position: Position) {
|
||||
if(target is Label && args.isNotEmpty())
|
||||
checkResult.add(SyntaxError("cannot use arguments when calling a label", position))
|
||||
errors.err("cannot use arguments when calling a label", position)
|
||||
|
||||
if(target is BuiltinFunctionStatementPlaceholder) {
|
||||
// it's a call to a builtin function.
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
if(args.size!=func.parameters.size)
|
||||
checkResult.add(SyntaxError("invalid number of arguments", position))
|
||||
errors.err("invalid number of arguments", position)
|
||||
else {
|
||||
val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
||||
for (arg in args.withIndex().zip(func.parameters)) {
|
||||
@@ -861,7 +873,7 @@ internal class AstChecker(private val program: Program,
|
||||
if (argDt.isKnown
|
||||
&& !(argDt.typeOrElse(DataType.STRUCT) isAssignableTo arg.second.possibleDatatypes)
|
||||
&& (argDt.typeOrElse(DataType.STRUCT) != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) {
|
||||
checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
|
||||
errors.err("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)
|
||||
}
|
||||
}
|
||||
if(target.name=="swap") {
|
||||
@@ -869,26 +881,26 @@ internal class AstChecker(private val program: Program,
|
||||
val dt1 = args[0].inferType(program)
|
||||
val dt2 = args[1].inferType(program)
|
||||
if (dt1 != dt2)
|
||||
checkResult.add(ExpressionError("swap requires 2 args of identical type", position))
|
||||
errors.err("swap requires 2 args of identical type", position)
|
||||
else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
|
||||
checkResult.add(ExpressionError("swap requires 2 variables, not constant value(s)", position))
|
||||
errors.err("swap requires 2 variables, not constant value(s)", position)
|
||||
else if(args[0] isSameAs args[1])
|
||||
checkResult.add(ExpressionError("swap should have 2 different args", position))
|
||||
errors.err("swap should have 2 different args", position)
|
||||
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
|
||||
checkResult.add(ExpressionError("swap requires args of numerical type", position))
|
||||
errors.err("swap requires args of numerical type", position)
|
||||
}
|
||||
else if(target.name=="all" || target.name=="any") {
|
||||
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
|
||||
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
|
||||
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(target is Subroutine) {
|
||||
if(args.size!=target.parameters.size)
|
||||
checkResult.add(SyntaxError("invalid number of arguments", position))
|
||||
errors.err("invalid number of arguments", position)
|
||||
else {
|
||||
for (arg in args.withIndex().zip(target.parameters)) {
|
||||
val argIDt = arg.first.value.inferType(program)
|
||||
@@ -899,26 +911,26 @@ internal class AstChecker(private val program: Program,
|
||||
if(!(argDt isAssignableTo arg.second.type)) {
|
||||
// for asm subroutines having STR param it's okay to provide a UWORD (address value)
|
||||
if(!(target.isAsmSubroutine && arg.second.type == DataType.STR && argDt == DataType.UWORD))
|
||||
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position))
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)
|
||||
}
|
||||
|
||||
if(target.isAsmSubroutine) {
|
||||
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
|
||||
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
|
||||
printWarning("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position)
|
||||
errors.warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position)
|
||||
}
|
||||
|
||||
// check if the argument types match the register(pairs)
|
||||
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))
|
||||
errors.err("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))
|
||||
errors.err("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))
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -931,23 +943,24 @@ internal class AstChecker(private val program: Program,
|
||||
val targetName = postIncrDecr.target.identifier!!.nameInSource
|
||||
val target = program.namespace.lookup(targetName, postIncrDecr)
|
||||
if(target==null) {
|
||||
checkResult.add(UndefinedSymbolError(postIncrDecr.target.identifier!!))
|
||||
val symbol = postIncrDecr.target.identifier!!
|
||||
errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||
} else {
|
||||
if(target !is VarDecl || target.type== VarDeclType.CONST) {
|
||||
checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position))
|
||||
errors.err("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))
|
||||
errors.err("can only increment or decrement a byte/float/word variable", postIncrDecr.position)
|
||||
}
|
||||
}
|
||||
} else if(postIncrDecr.target.arrayindexed != null) {
|
||||
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
|
||||
if(target==null) {
|
||||
checkResult.add(NameError("undefined symbol", postIncrDecr.position))
|
||||
errors.err("undefined symbol", postIncrDecr.position)
|
||||
}
|
||||
else {
|
||||
val dt = (target as VarDecl).datatype
|
||||
if(dt !in NumericDatatypes && dt !in ArrayDatatypes)
|
||||
checkResult.add(SyntaxError("can only increment or decrement a byte/float/word", postIncrDecr.position))
|
||||
errors.err("can only increment or decrement a byte/float/word", postIncrDecr.position)
|
||||
}
|
||||
}
|
||||
// else if(postIncrDecr.target.memoryAddress != null) { } // a memory location can always be ++/--
|
||||
@@ -958,29 +971,29 @@ internal class AstChecker(private val program: Program,
|
||||
val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
|
||||
if(target is VarDecl) {
|
||||
if(target.datatype !in IterableDatatypes)
|
||||
checkResult.add(SyntaxError("indexing requires an iterable variable", arrayIndexedExpression.position))
|
||||
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
|
||||
val arraysize = target.arraysize?.size()
|
||||
if(arraysize!=null) {
|
||||
// check out of bounds
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
|
||||
if(index!=null && (index<0 || index>=arraysize))
|
||||
checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position))
|
||||
errors.err("array index out of bounds", arrayIndexedExpression.arrayspec.position)
|
||||
} else if(target.datatype == DataType.STR) {
|
||||
if(target.value is StringLiteralValue) {
|
||||
// check string lengths for non-memory mapped strings
|
||||
val stringLen = (target.value as StringLiteralValue).value.length
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
|
||||
if (index != null && (index < 0 || index >= stringLen))
|
||||
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
|
||||
errors.err("index out of bounds", arrayIndexedExpression.arrayspec.position)
|
||||
}
|
||||
}
|
||||
} else
|
||||
checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position))
|
||||
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
|
||||
|
||||
// check index value 0..255
|
||||
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
|
||||
checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position))
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
|
||||
super.visit(arrayIndexedExpression)
|
||||
}
|
||||
@@ -988,15 +1001,15 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(whenStatement: WhenStatement) {
|
||||
val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(conditionType !in IntegerDatatypes)
|
||||
checkResult.add(SyntaxError("when condition must be an integer value", whenStatement.position))
|
||||
errors.err("when condition must be an integer value", whenStatement.position)
|
||||
val choiceValues = whenStatement.choiceValues(program)
|
||||
val occurringValues = choiceValues.map {it.first}
|
||||
val tally = choiceValues.associate { it.second to occurringValues.count { ov->it.first==ov} }
|
||||
tally.filter { it.value>1 }.forEach {
|
||||
checkResult.add(SyntaxError("choice value occurs multiple times", it.key.position))
|
||||
errors.err("choice value occurs multiple times", it.key.position)
|
||||
}
|
||||
if(whenStatement.choices.isEmpty())
|
||||
checkResult.add(SyntaxError("empty when statement", whenStatement.position))
|
||||
errors.err("empty when statement", whenStatement.position)
|
||||
|
||||
super.visit(whenStatement)
|
||||
}
|
||||
@@ -1010,14 +1023,14 @@ internal class AstChecker(private val program: Program,
|
||||
val constvalues = whenChoice.values!!.map { it.constValue(program) }
|
||||
for(constvalue in constvalues) {
|
||||
when {
|
||||
constvalue == null -> checkResult.add(SyntaxError("choice value must be a constant", whenChoice.position))
|
||||
constvalue.type !in IntegerDatatypes -> checkResult.add(SyntaxError("choice value must be a byte or word", whenChoice.position))
|
||||
constvalue.type != conditionType.typeOrElse(DataType.STRUCT) -> checkResult.add(SyntaxError("choice value datatype differs from condition value", whenChoice.position))
|
||||
constvalue == null -> errors.err("choice value must be a constant", whenChoice.position)
|
||||
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position)
|
||||
constvalue.type != conditionType.typeOrElse(DataType.STRUCT) -> errors.err("choice value datatype differs from condition value", whenChoice.position)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(whenChoice !== whenStmt.choices.last())
|
||||
checkResult.add(SyntaxError("else choice must be the last one", whenChoice.position))
|
||||
errors.err("else choice must be the last one", whenChoice.position)
|
||||
}
|
||||
super.visit(whenChoice)
|
||||
}
|
||||
@@ -1025,33 +1038,51 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(structDecl: StructDecl) {
|
||||
// a struct can only contain 1 or more vardecls and can not be nested
|
||||
if(structDecl.statements.isEmpty())
|
||||
checkResult.add(SyntaxError("struct must contain at least one member", structDecl.position))
|
||||
errors.err("struct must contain at least one member", structDecl.position)
|
||||
|
||||
for(member in structDecl.statements){
|
||||
val decl = member as? VarDecl
|
||||
if(decl==null)
|
||||
checkResult.add(SyntaxError("struct can only contain variable declarations", structDecl.position))
|
||||
errors.err("struct can only contain variable declarations", structDecl.position)
|
||||
else {
|
||||
if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
||||
checkResult.add(SyntaxError("struct can not contain zeropage members", decl.position))
|
||||
errors.err("struct can not contain zeropage members", decl.position)
|
||||
if(decl.datatype !in NumericDatatypes)
|
||||
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
|
||||
errors.err("structs can only contain numerical types", decl.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope) {
|
||||
visitStatements(scope.statements)
|
||||
}
|
||||
|
||||
private fun visitStatements(statements: List<Statement>) {
|
||||
for((index, stmt) in statements.withIndex()) {
|
||||
if(stmt is FunctionCallStatement
|
||||
&& stmt.target.nameInSource.last()=="exit"
|
||||
&& index < statements.lastIndex)
|
||||
errors.warn("unreachable code, exit call above never returns", statements[index+1].position)
|
||||
|
||||
if(stmt is Return && index < statements.lastIndex)
|
||||
errors.warn("unreachable code, return statement above", statements[index+1].position)
|
||||
|
||||
stmt.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
|
||||
val targetStatement = target.targetStatement(program.namespace)
|
||||
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
return targetStatement
|
||||
checkResult.add(NameError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
|
||||
errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
return null
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteralValue) : Boolean {
|
||||
return if (targetDt == DataType.STR) {
|
||||
if (value.value.length > 255) {
|
||||
checkResult.add(ExpressionError("string length must be 0-255", value.position))
|
||||
errors.err("string length must be 0-255", value.position)
|
||||
false
|
||||
}
|
||||
else
|
||||
@@ -1063,7 +1094,7 @@ internal class AstChecker(private val program: Program,
|
||||
private fun checkValueTypeAndRangeArray(targetDt: DataType, struct: StructDecl?,
|
||||
arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean {
|
||||
fun err(msg: String) : Boolean {
|
||||
checkResult.add(ExpressionError(msg, value.position))
|
||||
errors.err(msg, value.position)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1151,7 +1182,7 @@ internal class AstChecker(private val program: Program,
|
||||
val vardecl = elt.second as VarDecl
|
||||
val valuetype = elt.first.inferType(program)
|
||||
if (!valuetype.isKnown || !(valuetype.typeOrElse(DataType.STRUCT) isAssignableTo vardecl.datatype)) {
|
||||
checkResult.add(ExpressionError("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position))
|
||||
errors.err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1165,7 +1196,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteralValue) : Boolean {
|
||||
fun err(msg: String) : Boolean {
|
||||
checkResult.add(ExpressionError(msg, value.position))
|
||||
errors.err(msg, value.position)
|
||||
return false
|
||||
}
|
||||
when (targetDt) {
|
||||
@@ -1237,7 +1268,7 @@ internal class AstChecker(private val program: Program,
|
||||
else -> throw AstException("invalid array type $type")
|
||||
}
|
||||
if (!correct)
|
||||
checkResult.add(ExpressionError("array value out of range for type $type", value.position))
|
||||
errors.err("array value out of range for type $type", value.position)
|
||||
return correct
|
||||
}
|
||||
|
||||
@@ -1248,7 +1279,7 @@ internal class AstChecker(private val program: Program,
|
||||
position: Position) : Boolean {
|
||||
|
||||
if(sourceValue is RangeExpr)
|
||||
checkResult.add(SyntaxError("can't assign a range value to something else", position))
|
||||
errors.err("can't assign a range value to something else", position)
|
||||
|
||||
val result = when(targetDatatype) {
|
||||
DataType.BYTE -> sourceDatatype== DataType.BYTE
|
||||
@@ -1266,22 +1297,22 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
false
|
||||
}
|
||||
else -> checkResult.add(SyntaxError("cannot assign new value to variable of type $targetDatatype", position))
|
||||
else -> errors.err("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)) {
|
||||
checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position))
|
||||
errors.err("cannot assign word to byte, use msb() or lsb()?", position)
|
||||
}
|
||||
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))
|
||||
errors.err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
|
||||
else {
|
||||
if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes)
|
||||
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position))
|
||||
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position)
|
||||
else
|
||||
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position))
|
||||
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,19 +11,13 @@ import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
|
||||
|
||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||
|
||||
internal class AstIdentifiersChecker(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
internal fun result(): List<AstException> {
|
||||
return checkResult
|
||||
}
|
||||
|
||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
|
||||
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
|
||||
}
|
||||
|
||||
override fun visit(module: Module) {
|
||||
@@ -59,15 +53,15 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// first, check if there are datatype errors on the vardecl
|
||||
decl.datatypeErrors.forEach { checkResult.add(it) }
|
||||
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||
|
||||
// now check the identifier
|
||||
if(decl.name in BuiltinFunctions)
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
|
||||
errors.err("builtin function cannot be redefined", decl.position)
|
||||
|
||||
if(decl.name in CompilationTarget.machine.opcodeNames)
|
||||
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position))
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||
|
||||
// is it a struct variable? then define all its struct members as mangled names,
|
||||
// and include the original decl as well.
|
||||
@@ -76,7 +70,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
return super.visit(decl) // don't do this multiple times
|
||||
|
||||
if(decl.struct==null) {
|
||||
checkResult.add(NameError("undefined struct type", decl.position))
|
||||
errors.err("undefined struct type", decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
@@ -84,7 +78,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
|
||||
|
||||
if(decl.value is NumericLiteralValue) {
|
||||
checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position))
|
||||
errors.err("you cannot initialize a struct using a single value", decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
@@ -104,10 +98,10 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
|
||||
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position))
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
||||
} else if(subroutine.name in BuiltinFunctions) {
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
|
||||
errors.err("builtin function cannot be redefined", subroutine.position)
|
||||
} else {
|
||||
// already reported elsewhere:
|
||||
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||
@@ -157,7 +151,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
}
|
||||
|
||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
|
||||
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||
}
|
||||
}
|
||||
return super.visit(subroutine)
|
||||
@@ -165,15 +159,21 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
|
||||
override fun visit(label: Label): Statement {
|
||||
if(label.name in CompilationTarget.machine.opcodeNames)
|
||||
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${label.name}'", label.position))
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
||||
|
||||
if(label.name in BuiltinFunctions) {
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
||||
errors.err("builtin function cannot be redefined", label.position)
|
||||
} else {
|
||||
val existing = program.namespace.lookup(listOf(label.name), label)
|
||||
if (existing != null && existing !== label)
|
||||
nameError(label.name, label.position, existing)
|
||||
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
|
||||
for(el in existing) {
|
||||
if(el === label || el.name != label.name)
|
||||
continue
|
||||
else {
|
||||
nameError(label.name, label.position, el)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(label)
|
||||
}
|
||||
@@ -185,7 +185,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
// additional interation count variable in their scope.
|
||||
if(forLoop.loopRegister!=null) {
|
||||
if(forLoop.loopRegister == Register.X)
|
||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
||||
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
||||
} else {
|
||||
val loopVar = forLoop.loopVar
|
||||
if (loopVar != null) {
|
||||
@@ -209,7 +209,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
|
||||
override fun visit(assignTarget: AssignTarget): AssignTarget {
|
||||
if(assignTarget.register== Register.X)
|
||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
|
||||
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
|
||||
return super.visit(assignTarget)
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
val vardecl = string.parent as? VarDecl
|
||||
// intern the string; move it into the heap
|
||||
if (string.value.length !in 1..255)
|
||||
checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
|
||||
errors.err("string literal length must be between 1 and 255", string.position)
|
||||
return if (vardecl != null)
|
||||
string
|
||||
else
|
||||
@@ -303,7 +303,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
||||
for(member in structDecl.statements){
|
||||
val decl = member as? VarDecl
|
||||
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
|
||||
errors.err("structs can only contain numerical types", decl.position)
|
||||
}
|
||||
|
||||
return super.visit(structDecl)
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
|
||||
|
||||
internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisitor {
|
||||
internal class AstRecursionChecker(private val namespace: INameScope,
|
||||
private val errors: ErrorReporter) : IAstVisitor {
|
||||
private val callGraph = DirectedGraph<INameScope>()
|
||||
|
||||
internal fun result(): List<AstException> {
|
||||
fun processMessages(modulename: String) {
|
||||
val cycle = callGraph.checkForCycle()
|
||||
if(cycle.isEmpty())
|
||||
return emptyList()
|
||||
return
|
||||
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"))
|
||||
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", null)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
@@ -44,7 +45,6 @@ internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisi
|
||||
super.visit(functionCall)
|
||||
}
|
||||
|
||||
|
||||
private class DirectedGraph<VT> {
|
||||
private val graph = mutableMapOf<VT, MutableSet<VT>>()
|
||||
private var uniqueVertices = mutableSetOf<VT>()
|
||||
|
||||
@@ -6,6 +6,7 @@ import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
interface IAstModifyingVisitor {
|
||||
fun visit(program: Program) {
|
||||
program.modules.forEach { visit(it) }
|
||||
@@ -158,6 +159,11 @@ interface IAstModifyingVisitor {
|
||||
return whileLoop
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop): Statement {
|
||||
foreverLoop.body = foreverLoop.body.accept(this) as AnonymousScope
|
||||
return foreverLoop
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
|
||||
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
|
||||
|
||||
@@ -112,6 +112,10 @@ interface IAstVisitor {
|
||||
whileLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop) {
|
||||
foreverLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop) {
|
||||
repeatLoop.untilCondition.accept(this)
|
||||
repeatLoop.body.accept(this)
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.base.printWarning
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.Statement
|
||||
|
||||
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
|
||||
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
||||
|
||||
internal fun result(): List<SyntaxError> {
|
||||
return checkResult
|
||||
}
|
||||
|
||||
internal class ImportedModuleDirectiveRemover(private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
/**
|
||||
* Most global directives don't apply for imported modules, so remove them
|
||||
*/
|
||||
@@ -25,7 +18,7 @@ internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
|
||||
val stmt = sourceStmt.accept(this)
|
||||
if(stmt is Directive && stmt.parent is Module) {
|
||||
if(stmt.directive in moduleLevelDirectives) {
|
||||
printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive)
|
||||
errors.warn("ignoring module directive because it was imported", stmt.position)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.ForeverLoop
|
||||
import prog8.ast.statements.RepeatLoop
|
||||
import prog8.ast.statements.Statement
|
||||
import prog8.ast.statements.WhileLoop
|
||||
|
||||
internal class MakeForeverLoops : IAstModifyingVisitor {
|
||||
override fun visit(whileLoop: WhileLoop): Statement {
|
||||
val numeric = whileLoop.condition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() != 0) {
|
||||
return ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
}
|
||||
return super.visit(whileLoop)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
val numeric = repeatLoop.untilCondition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() == 0)
|
||||
return ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
return super.visit(repeatLoop)
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,6 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
module.statements.addAll(0, directives)
|
||||
|
||||
for(pos in addReturns) {
|
||||
println(pos)
|
||||
val returnStmt = Return(null, pos.first.position)
|
||||
returnStmt.linkParents(pos.first as Node)
|
||||
pos.first.statements.add(pos.second, returnStmt)
|
||||
|
||||
@@ -4,14 +4,15 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.printWarning
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor {
|
||||
internal class TypecastsAdder(private val program: Program,
|
||||
private val errors: ErrorReporter): IAstModifyingVisitor {
|
||||
// Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
|
||||
// (this includes function call arguments)
|
||||
|
||||
@@ -124,7 +125,7 @@ internal class TypecastsAdder(private val program: Program): IAstModifyingVisito
|
||||
override fun visit(typecast: TypecastExpression): Expression {
|
||||
// warn about any implicit type casts to Float, because that may not be intended
|
||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
||||
errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
||||
}
|
||||
return super.visit(typecast)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.FunctionSignature
|
||||
import prog8.functions.FSignature
|
||||
|
||||
|
||||
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
|
||||
@@ -129,7 +129,7 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
|
||||
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FSignature, args: MutableList<Expression>, parent: Statement) {
|
||||
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
||||
for(arg in args.withIndex().zip(signature.parameters)) {
|
||||
val argvalue = arg.first.value
|
||||
|
||||
@@ -687,6 +687,19 @@ class WhileLoop(var condition: Expression,
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
}
|
||||
|
||||
class ForeverLoop(var body: AnonymousScope, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
}
|
||||
|
||||
class RepeatLoop(var body: AnonymousScope,
|
||||
var untilCondition: Expression,
|
||||
override val position: Position) : Statement() {
|
||||
|
||||
@@ -8,9 +8,8 @@ import prog8.compiler.target.CompilationTarget
|
||||
import prog8.optimizer.constantFold
|
||||
import prog8.optimizer.optimizeStatements
|
||||
import prog8.optimizer.simplifyExpressions
|
||||
import prog8.parser.ModuleImporter
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.importLibraryModule
|
||||
import prog8.parser.importModule
|
||||
import prog8.parser.moduleName
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.measureTimeMillis
|
||||
@@ -32,12 +31,18 @@ fun compileProgram(filepath: Path,
|
||||
var importedFiles: List<Path> = emptyList()
|
||||
var success=false
|
||||
|
||||
val errors = ErrorReporter()
|
||||
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val importer = ModuleImporter(errors)
|
||||
errors.handle()
|
||||
|
||||
println("Parsing...")
|
||||
programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||
importModule(programAst, filepath)
|
||||
importer.importModule(programAst, filepath)
|
||||
errors.handle()
|
||||
|
||||
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
|
||||
|
||||
@@ -47,61 +52,73 @@ fun compileProgram(filepath: Path,
|
||||
|
||||
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
|
||||
importLibraryModule(programAst, "c64lib")
|
||||
importLibraryModule(programAst, "c64utils")
|
||||
importer.importLibraryModule(programAst, "c64lib")
|
||||
importer.importLibraryModule(programAst, "c64utils")
|
||||
}
|
||||
|
||||
// always import prog8lib and math
|
||||
importLibraryModule(programAst, "math")
|
||||
importLibraryModule(programAst, "prog8lib")
|
||||
|
||||
importer.importLibraryModule(programAst, "math")
|
||||
importer.importLibraryModule(programAst, "prog8lib")
|
||||
errors.handle()
|
||||
|
||||
// perform initial syntax checks and constant folding
|
||||
println("Syntax check...")
|
||||
val time1 = measureTimeMillis {
|
||||
programAst.checkIdentifiers()
|
||||
programAst.checkIdentifiers(errors)
|
||||
errors.handle()
|
||||
programAst.makeForeverLoops()
|
||||
}
|
||||
|
||||
//println(" time1: $time1")
|
||||
val time2 = measureTimeMillis {
|
||||
programAst.constantFold()
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
}
|
||||
//println(" time2: $time2")
|
||||
val time3 = measureTimeMillis {
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.reorderStatements()
|
||||
programAst.addTypecasts()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
}
|
||||
//println(" time3: $time3")
|
||||
val time4 = measureTimeMillis {
|
||||
programAst.checkValid(compilerOptions) // check if tree is valid
|
||||
programAst.checkValid(compilerOptions, errors) // check if tree is valid
|
||||
errors.handle()
|
||||
}
|
||||
//println(" time4: $time4")
|
||||
|
||||
programAst.checkIdentifiers()
|
||||
programAst.checkIdentifiers(errors)
|
||||
errors.handle()
|
||||
|
||||
if (optimize) {
|
||||
// optimize the parse tree
|
||||
println("Optimizing...")
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.optimizeStatements()
|
||||
val optsDone2 = programAst.optimizeStatements(errors)
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
programAst.addTypecasts()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.checkValid(compilerOptions) // check if final tree is valid
|
||||
programAst.checkRecursion() // check if there are recursive subroutine calls
|
||||
programAst.checkValid(compilerOptions, errors) // check if final tree is valid
|
||||
errors.handle()
|
||||
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
|
||||
errors.handle()
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
if(writeAssembly) {
|
||||
// asm generation directly from the Ast, no need for intermediate code
|
||||
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
|
||||
programAst.anonscopeVarsCleanup()
|
||||
programAst.anonscopeVarsCleanup(errors)
|
||||
errors.handle()
|
||||
val assembly = CompilationTarget.asmGenerator(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize)
|
||||
assembly.assemble(compilerOptions)
|
||||
programName = assembly.name
|
||||
|
||||
@@ -15,7 +15,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||
|
||||
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
|
||||
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
|
||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
|
||||
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
@@ -28,9 +28,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
DataType.FLOAT -> {
|
||||
if (options.floats) {
|
||||
if(position!=null)
|
||||
printWarning("allocated a large value (float) in zeropage", position)
|
||||
errors.warn("allocated a large value (float) in zeropage", position)
|
||||
else
|
||||
printWarning("$scopedname: allocated a large value (float) in zeropage")
|
||||
errors.warn("$scopedname: allocated a large value (float) in zeropage", null)
|
||||
5
|
||||
} else throw CompilerException("floating point option not enabled")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ internal interface IAssemblyGenerator {
|
||||
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
|
||||
}
|
||||
|
||||
internal const val generatedLabelPrefix = "_prog8_label_"
|
||||
|
||||
internal interface IAssemblyProgram {
|
||||
val name: String
|
||||
fun assemble(options: CompilationOptions)
|
||||
|
||||
@@ -3,10 +3,11 @@ package prog8.compiler.target.c64
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.OutputType
|
||||
import prog8.compiler.target.IAssemblyProgram
|
||||
import prog8.compiler.target.generatedLabelPrefix
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProgram {
|
||||
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
|
||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||
private val prgFile = outputDir.resolve("$name.prg")
|
||||
private val binFile = outputDir.resolve("$name.bin")
|
||||
@@ -18,7 +19,7 @@ class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProg
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
|
||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||
|
||||
val outFile = when(options.output) {
|
||||
val outFile = when (options.output) {
|
||||
OutputType.PRG -> {
|
||||
command.add("--cbm-prg")
|
||||
println("\nCreating C-64 prg.")
|
||||
@@ -34,27 +35,39 @@ class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProg
|
||||
|
||||
val proc = ProcessBuilder(command).inheritIO().start()
|
||||
val result = proc.waitFor()
|
||||
if(result!=0) {
|
||||
if (result != 0) {
|
||||
System.err.println("assembler failed with returncode $result")
|
||||
exitProcess(result)
|
||||
}
|
||||
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
generateBreakpointList()
|
||||
}
|
||||
|
||||
private fun removeGeneratedLabelsFromMonlist() {
|
||||
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
|
||||
val lines = viceMonListFile.toFile().readLines()
|
||||
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||
for (line in lines) {
|
||||
if(pattern.matchEntire(line)==null)
|
||||
it.write(line+"\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateBreakpointList() {
|
||||
// builds list of breakpoints, appends to monitor list file
|
||||
val breakpoints = mutableListOf<String>()
|
||||
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
|
||||
for(line in viceMonListFile.toFile().readLines()) {
|
||||
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
|
||||
for (line in viceMonListFile.toFile().readLines()) {
|
||||
val match = pattern.matchEntire(line)
|
||||
if(match!=null)
|
||||
breakpoints.add("break \$" + match.groupValues[1])
|
||||
if (match != null)
|
||||
breakpoints.add("break \$" + match.groupValues[1])
|
||||
}
|
||||
val num = breakpoints.size
|
||||
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
||||
breakpoints.add(1, "; $num breakpoints have been defined")
|
||||
breakpoints.add(2, "del")
|
||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n")+"\n")
|
||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,17 @@ import prog8.compiler.target.IAssemblyGenerator
|
||||
import prog8.compiler.target.IAssemblyProgram
|
||||
import prog8.compiler.target.c64.AssemblyProgram
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.generatedLabelPrefix
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.FunctionSignature
|
||||
import prog8.functions.FSignature
|
||||
import java.math.RoundingMode
|
||||
import java.nio.file.Path
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.util.ArrayDeque
|
||||
import java.util.*
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
@@ -96,11 +97,15 @@ internal class AsmGen(private val program: Program,
|
||||
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
|
||||
out("+\t.word 0")
|
||||
out("_prog8_entrypoint\t; assembly code starts here\n")
|
||||
out(" tsx")
|
||||
out(" stx prog8_lib.orig_stackpointer")
|
||||
out(" jsr prog8_lib.init_system")
|
||||
}
|
||||
options.output == OutputType.PRG -> {
|
||||
out("; ---- program without basic sys call ----")
|
||||
out("* = ${program.actualLoadAddress.toHex()}\n")
|
||||
out(" tsx")
|
||||
out(" stx prog8_lib.orig_stackpointer")
|
||||
out(" jsr prog8_lib.init_system")
|
||||
}
|
||||
options.output == OutputType.RAW -> {
|
||||
@@ -137,7 +142,6 @@ internal class AsmGen(private val program: Program,
|
||||
out(" jmp (c64.RESET_VEC)\t; cold reset")
|
||||
}
|
||||
}
|
||||
out("")
|
||||
}
|
||||
|
||||
private fun footer() {
|
||||
@@ -178,7 +182,7 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
internal fun makeLabel(postfix: String): String {
|
||||
generatedLabelSequenceNumber++
|
||||
return "_prog8_label_${generatedLabelSequenceNumber}_$postfix"
|
||||
return "${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
|
||||
}
|
||||
|
||||
private fun outputSourceLine(node: Node) {
|
||||
@@ -225,7 +229,9 @@ internal class AsmGen(private val program: Program,
|
||||
&& variable.datatype != DataType.FLOAT
|
||||
&& options.zeropage != ZeropageType.DONTUSE) {
|
||||
try {
|
||||
val address = zeropage.allocate(fullName, variable.datatype, null)
|
||||
val errors = ErrorReporter()
|
||||
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
|
||||
errors.handle()
|
||||
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
|
||||
// make sure we add the var to the set of zpvars for this block
|
||||
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
|
||||
@@ -598,11 +604,13 @@ internal class AsmGen(private val program: Program,
|
||||
is Continue -> out(" jmp ${loopContinueLabels.peek()}")
|
||||
is Break -> out(" jmp ${loopEndLabels.peek()}")
|
||||
is WhileLoop -> translate(stmt)
|
||||
is ForeverLoop -> translate(stmt)
|
||||
is RepeatLoop -> translate(stmt)
|
||||
is WhenStatement -> translate(stmt)
|
||||
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
|
||||
is AnonymousScope -> translate(stmt)
|
||||
is Block -> throw AssemblyError("block should have been handled elsewhere")
|
||||
else -> TODO("no translation for $stmt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +636,19 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: ForeverLoop) {
|
||||
val foreverLabel = makeLabel("forever")
|
||||
val endLabel = makeLabel("foreverend")
|
||||
loopEndLabels.push(endLabel)
|
||||
loopContinueLabels.push(foreverLabel)
|
||||
out(foreverLabel)
|
||||
translate(stmt.body)
|
||||
out(" jmp $foreverLabel")
|
||||
out(endLabel)
|
||||
loopEndLabels.pop()
|
||||
loopContinueLabels.pop()
|
||||
}
|
||||
|
||||
private fun translate(stmt: WhileLoop) {
|
||||
val whileLabel = makeLabel("while")
|
||||
val endLabel = makeLabel("whileend")
|
||||
@@ -729,7 +750,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun translate(stmt: Label) {
|
||||
out(stmt.name)
|
||||
out("_${stmt.name}") // underscore prefix to make sure it's a local label
|
||||
}
|
||||
|
||||
private fun translate(scope: AnonymousScope) {
|
||||
@@ -798,7 +819,14 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
private fun getJumpTarget(jmp: Jump): String {
|
||||
return when {
|
||||
jmp.identifier!=null -> asmIdentifierName(jmp.identifier)
|
||||
jmp.identifier!=null -> {
|
||||
val target = jmp.identifier.targetStatement(program.namespace)
|
||||
val asmName = asmIdentifierName(jmp.identifier)
|
||||
if(target is Label)
|
||||
"_$asmName" // prefix with underscore to jump to local label
|
||||
else
|
||||
asmName
|
||||
}
|
||||
jmp.generatedLabel!=null -> jmp.generatedLabel
|
||||
jmp.address!=null -> jmp.address.toHex()
|
||||
else -> "????"
|
||||
@@ -839,7 +867,7 @@ internal class AsmGen(private val program: Program,
|
||||
internal fun translateExpression(expression: Expression) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FunctionSignature) =
|
||||
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
|
||||
|
||||
internal fun translateFunctionCall(functionCall: FunctionCall) =
|
||||
|
||||
@@ -7,12 +7,12 @@ import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@@ -313,7 +313,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta (+) +1
|
||||
sty (+) +2
|
||||
lda $sourceName
|
||||
+ sta ${65535.toHex()} ; modified
|
||||
+ sta ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
@@ -469,7 +469,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta (+) +1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) +2
|
||||
+ sty ${65535.toHex()} ; modified
|
||||
+ sty ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,26 +9,26 @@ import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||
import prog8.compiler.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.functions.FunctionSignature
|
||||
import prog8.functions.FSignature
|
||||
|
||||
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) {
|
||||
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature) {
|
||||
translateFunctioncall(fcall, func, false)
|
||||
}
|
||||
|
||||
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FunctionSignature) {
|
||||
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
|
||||
translateFunctioncall(fcall, func, true)
|
||||
}
|
||||
|
||||
private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) {
|
||||
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean) {
|
||||
val functionName = fcall.target.nameInSource.last()
|
||||
if (discardResult) {
|
||||
if (func.pure)
|
||||
@@ -149,7 +149,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta (+) + 1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) + 2
|
||||
+ asl 0 ; modified
|
||||
+ asl ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta (+) + 1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) + 2
|
||||
+ lsr 0 ; modified
|
||||
+ lsr ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
@@ -286,7 +286,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta (+) + 1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) + 2
|
||||
+ rol 0 ; modified
|
||||
+ rol ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
@@ -397,7 +397,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta (+) + 1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) + 2
|
||||
+ ror 0 ; modified
|
||||
+ ror ${'$'}ffff ; modified
|
||||
""") }
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
@@ -603,7 +603,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
""")
|
||||
}
|
||||
|
||||
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FunctionSignature) {
|
||||
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FSignature) {
|
||||
args.forEach {
|
||||
asmgen.translateExpression(it)
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ package prog8.compiler.target.c64.codegen
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.compiler.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX
|
||||
import prog8.compiler.toHex
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@@ -158,7 +158,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
private fun translateExpression(expr: RegisterExpr) {
|
||||
when(expr.register) {
|
||||
Register.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
|
||||
Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register")
|
||||
Register.X -> asmgen.out(" txa | sta $ESTACK_LO_HEX,x | dex")
|
||||
Register.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
||||
}
|
||||
}
|
||||
@@ -224,8 +224,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
if(value!=null) {
|
||||
if(rightDt in IntegerDatatypes) {
|
||||
val amount = value.number.toInt()
|
||||
if(amount in powersOfTwo)
|
||||
printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount")
|
||||
when(rightDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(amount in optimizedByteMultiplications) {
|
||||
|
||||
@@ -8,11 +8,11 @@ import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||
import prog8.compiler.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
// todo choose more efficient comparisons to avoid needless lda's
|
||||
|
||||
@@ -7,10 +7,10 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.compiler.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
@@ -6,9 +6,9 @@ import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.RegisterExpr
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
import prog8.compiler.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
@@ -7,88 +7,87 @@ import prog8.compiler.CompilerException
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
|
||||
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
|
||||
|
||||
|
||||
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
|
||||
|
||||
|
||||
class FunctionSignature(val pure: Boolean, // does it have side effects?
|
||||
val parameters: List<BuiltinFunctionParam>,
|
||||
val returntype: DataType?,
|
||||
val constExpressionFunc: ConstExpressionCaller? = null)
|
||||
class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val parameters: List<FParam>,
|
||||
val returntype: DataType?,
|
||||
val constExpressionFunc: ConstExpressionCaller? = null)
|
||||
|
||||
|
||||
val BuiltinFunctions = mapOf(
|
||||
// this set of function have no return value and operate in-place:
|
||||
"rol" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
||||
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
||||
"sort" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
|
||||
"reverse" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
|
||||
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"lsl" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
||||
"lsr" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
||||
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
// these few have a return value depending on the argument(s):
|
||||
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||
"min" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||
// normal functions follow:
|
||||
"sgn" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
"sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||
"sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||
"cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||
"cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
"cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||
"cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
||||
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
|
||||
"mkword" to FunctionSignature(true, listOf(
|
||||
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
|
||||
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
"rnd" to FunctionSignature(true, emptyList(), DataType.UBYTE),
|
||||
"rndw" to FunctionSignature(true, emptyList(), DataType.UWORD),
|
||||
"rndf" to FunctionSignature(true, emptyList(), DataType.FLOAT),
|
||||
"rsave" to FunctionSignature(false, emptyList(), null),
|
||||
"rrestore" to FunctionSignature(false, emptyList(), null),
|
||||
"set_carry" to FunctionSignature(false, emptyList(), null),
|
||||
"clear_carry" to FunctionSignature(false, emptyList(), null),
|
||||
"set_irqd" to FunctionSignature(false, emptyList(), null),
|
||||
"clear_irqd" to FunctionSignature(false, emptyList(), null),
|
||||
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
|
||||
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
|
||||
"memcopy" to FunctionSignature(false, listOf(
|
||||
BuiltinFunctionParam("from", IterableDatatypes + DataType.UWORD),
|
||||
BuiltinFunctionParam("to", IterableDatatypes + DataType.UWORD),
|
||||
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
|
||||
"memset" to FunctionSignature(false, listOf(
|
||||
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
|
||||
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
|
||||
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
|
||||
"memsetw" to FunctionSignature(false, listOf(
|
||||
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
|
||||
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
|
||||
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
|
||||
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
||||
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
|
||||
"mkword" to FSignature(true, listOf(FParam("lsb", setOf(DataType.UBYTE)), FParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
|
||||
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
|
||||
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
|
||||
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
|
||||
"rsave" to FSignature(false, emptyList(), null),
|
||||
"rrestore" to FSignature(false, emptyList(), null),
|
||||
"set_carry" to FSignature(false, emptyList(), null),
|
||||
"clear_carry" to FSignature(false, emptyList(), null),
|
||||
"set_irqd" to FSignature(false, emptyList(), null),
|
||||
"clear_irqd" to FSignature(false, emptyList(), null),
|
||||
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
|
||||
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||
"memcopy" to FSignature(false, listOf(
|
||||
FParam("from", IterableDatatypes + DataType.UWORD),
|
||||
FParam("to", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UBYTE))), null),
|
||||
"memset" to FSignature(false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UWORD)),
|
||||
FParam("bytevalue", ByteDatatypes)), null),
|
||||
"memsetw" to FSignature(false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numwords", setOf(DataType.UWORD)),
|
||||
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
|
||||
)
|
||||
|
||||
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
|
||||
|
||||
@@ -11,24 +11,14 @@ import prog8.functions.BuiltinFunctions
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
class ConstantFolding(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
var errors : MutableList<AstException> = mutableListOf()
|
||||
private val reportedErrorMessages = mutableSetOf<String>()
|
||||
|
||||
fun addError(x: AstException) {
|
||||
// check that we don't add the isSameAs error more than once
|
||||
if(x.toString() !in reportedErrorMessages) {
|
||||
reportedErrorMessages.add(x.toString())
|
||||
errors.add(x)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call tree for this?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
errors.add(ExpressionError("recursive var declaration", decl.position))
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return decl
|
||||
}
|
||||
|
||||
@@ -69,7 +59,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.size()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
@@ -88,7 +78,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
}
|
||||
}
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
@@ -96,19 +86,19 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.add(ExpressionError("ubyte value overflow", numericLv.position))
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.add(ExpressionError("byte value overflow", numericLv.position))
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.add(ExpressionError("uword value overflow", numericLv.position))
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.add(ExpressionError("word value overflow", numericLv.position))
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@@ -131,7 +121,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = litval.number.toDouble()
|
||||
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.add(ExpressionError("float value overflow", litval.position))
|
||||
errors.err("float value overflow", litval.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
|
||||
@@ -178,7 +168,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
else -> identifier
|
||||
}
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
errors.err("unhandled AST error: $ax", identifier.position)
|
||||
identifier
|
||||
}
|
||||
}
|
||||
@@ -189,7 +179,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
return try {
|
||||
functionCall.constValue(program) ?: functionCall
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
errors.err("unhandled AST error: $ax", functionCall.position)
|
||||
functionCall
|
||||
}
|
||||
}
|
||||
@@ -284,7 +274,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
}
|
||||
return prefixExpr
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
errors.err("unhandled AST error: $ax", expr.position)
|
||||
expr
|
||||
}
|
||||
}
|
||||
@@ -344,7 +334,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
else -> expr
|
||||
}
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
errors.err("unhandled AST error: $ax", expr.position)
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,25 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.ast.base.ErrorReporter
|
||||
|
||||
|
||||
internal fun Program.constantFold() {
|
||||
val optimizer = ConstantFolding(this)
|
||||
try {
|
||||
optimizer.visit(this)
|
||||
} catch (ax: AstException) {
|
||||
optimizer.addError(ax)
|
||||
}
|
||||
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
val optimizer = ConstantFolding(this, errors)
|
||||
optimizer.visit(this)
|
||||
|
||||
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
while(errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
optimizer.optimizationsDone = 0
|
||||
optimizer.visit(this)
|
||||
}
|
||||
|
||||
if(optimizer.errors.isNotEmpty()) {
|
||||
optimizer.errors.forEach { System.err.println(it) }
|
||||
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
|
||||
} else {
|
||||
if(errors.isEmpty())
|
||||
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.optimizeStatements(): Int {
|
||||
val optimizer = StatementOptimizer(this)
|
||||
internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
|
||||
val optimizer = StatementOptimizer(this, errors)
|
||||
optimizer.visit(this)
|
||||
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
||||
|
||||
|
||||
@@ -15,12 +15,13 @@ import kotlin.math.floor
|
||||
|
||||
|
||||
/*
|
||||
TODO: remove unreachable code?
|
||||
TODO: remove unreachable code after return and exit()
|
||||
TODO: proper inlining of tiny subroutines (at first, restrict to subs without parameters and variables in them, and build it up from there: correctly renaming/relocating all variables in them and refs to those as well)
|
||||
*/
|
||||
|
||||
|
||||
internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor {
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
private set
|
||||
|
||||
@@ -80,13 +81,13 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
optimizationsDone++
|
||||
printWarning("removing empty block '${block.name}'", block.position)
|
||||
errors.warn("removing empty block '${block.name}'", block.position)
|
||||
return NopStatement.insteadOf(block)
|
||||
}
|
||||
|
||||
if (block !in callgraph.usedSymbols) {
|
||||
optimizationsDone++
|
||||
printWarning("removing unused block '${block.name}'", block.position)
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return NopStatement.insteadOf(block) // remove unused block
|
||||
}
|
||||
}
|
||||
@@ -99,7 +100,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||
if(subroutine.asmAddress==null && !forceOutput) {
|
||||
if(subroutine.containsNoCodeNorVars()) {
|
||||
printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(subroutine)
|
||||
}
|
||||
@@ -111,11 +112,12 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
}
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(subroutine)
|
||||
}
|
||||
|
||||
visitStatements(subroutine.statements)
|
||||
return subroutine
|
||||
}
|
||||
|
||||
@@ -123,7 +125,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||
if(decl.type == VarDeclType.VAR)
|
||||
printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position)
|
||||
errors.warn("removing unused variable ${decl.type} '${decl.name}'", decl.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(decl)
|
||||
}
|
||||
@@ -160,7 +162,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(functionCallStatement)
|
||||
}
|
||||
@@ -263,12 +265,12 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
printWarning("condition is always true", ifStatement.position)
|
||||
errors.warn("condition is always true", ifStatement.position)
|
||||
optimizationsDone++
|
||||
ifStatement.truepart
|
||||
} else {
|
||||
// always false -> keep only else-part
|
||||
printWarning("condition is always false", ifStatement.position)
|
||||
errors.warn("condition is always false", ifStatement.position)
|
||||
optimizationsDone++
|
||||
ifStatement.elsepart
|
||||
}
|
||||
@@ -311,21 +313,13 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
val constvalue = whileLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> print a warning, and optimize into body + jump (if there are no continue and break statements)
|
||||
printWarning("condition is always true", whileLoop.condition.position)
|
||||
if(hasContinueOrBreak(whileLoop.body))
|
||||
return whileLoop
|
||||
val backLabelName = "_prog8_back${whileLoop.position.line}"
|
||||
val label = Label(backLabelName, whileLoop.condition.position)
|
||||
whileLoop.body.statements.add(0, label)
|
||||
whileLoop.body.statements.add(Jump(null,
|
||||
IdentifierReference(listOf(backLabelName), whileLoop.condition.position),
|
||||
null, whileLoop.condition.position))
|
||||
// always true -> print a warning, and optimize into a forever-loop
|
||||
errors.warn("condition is always true", whileLoop.condition.position)
|
||||
optimizationsDone++
|
||||
return whileLoop.body
|
||||
ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
} else {
|
||||
// always false -> ditch whole statement
|
||||
printWarning("condition is always false", whileLoop.condition.position)
|
||||
// always false -> remove the while statement altogether
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
optimizationsDone++
|
||||
NopStatement.insteadOf(whileLoop)
|
||||
}
|
||||
@@ -339,7 +333,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only the statement block (if there are no continue and break statements)
|
||||
printWarning("condition is always true", repeatLoop.untilCondition.position)
|
||||
errors.warn("condition is always true", repeatLoop.untilCondition.position)
|
||||
if(hasContinueOrBreak(repeatLoop.body))
|
||||
repeatLoop
|
||||
else {
|
||||
@@ -347,18 +341,10 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
repeatLoop.body
|
||||
}
|
||||
} else {
|
||||
// always false -> print a warning, and optimize into body + jump (if there are no continue and break statements)
|
||||
printWarning("condition is always false", repeatLoop.untilCondition.position)
|
||||
if(hasContinueOrBreak(repeatLoop.body))
|
||||
return repeatLoop
|
||||
val backLabelName = "_prog8_back${repeatLoop.position.line}"
|
||||
val label = Label(backLabelName, repeatLoop.untilCondition.position)
|
||||
repeatLoop.body.statements.add(0, label)
|
||||
repeatLoop.body.statements.add(Jump(null,
|
||||
IdentifierReference(listOf(backLabelName), repeatLoop.untilCondition.position),
|
||||
null, repeatLoop.untilCondition.position))
|
||||
// always false -> print a warning, and optimize into a forever loop
|
||||
errors.warn("condition is always false", repeatLoop.untilCondition.position)
|
||||
optimizationsDone++
|
||||
return repeatLoop.body
|
||||
ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
}
|
||||
}
|
||||
return repeatLoop
|
||||
@@ -564,6 +550,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
|
||||
}
|
||||
visitStatements(scope.statements)
|
||||
return super.visit(scope)
|
||||
}
|
||||
|
||||
@@ -571,11 +558,26 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
||||
// remove duplicate labels
|
||||
val stmts = label.definingScope().statements
|
||||
val startIdx = stmts.indexOf(label)
|
||||
if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
|
||||
if(startIdx< stmts.lastIndex && stmts[startIdx+1] == label)
|
||||
return NopStatement.insteadOf(label)
|
||||
|
||||
return super.visit(label)
|
||||
}
|
||||
|
||||
|
||||
private fun visitStatements(statements: MutableList<Statement>) {
|
||||
// TODO remove all unreachable code statements after call to exit() or a return
|
||||
// this is not yet correct because we still have nested subroutines
|
||||
// val exitCallIndex = statements.indexOfFirst { it is FunctionCallStatement && it.target.nameInSource.last()=="exit" }
|
||||
// if(exitCallIndex>=0) {
|
||||
// while(exitCallIndex < statements.lastIndex) {
|
||||
// val stmt = statements[exitCallIndex+1]
|
||||
// println("after exit() removing: $stmt")
|
||||
// statements.removeAt(exitCallIndex+1)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.antlr.v4.runtime.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.toAst
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.base.checkImportedValid
|
||||
@@ -33,114 +34,117 @@ internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexe
|
||||
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
|
||||
|
||||
|
||||
internal fun importModule(program: Program, filePath: Path): Module {
|
||||
print("importing '${moduleName(filePath.fileName)}'")
|
||||
if(filePath.parent!=null) {
|
||||
var importloc = filePath.toString()
|
||||
val curdir = Paths.get("").toAbsolutePath().toString()
|
||||
if(importloc.startsWith(curdir))
|
||||
importloc = "." + importloc.substring(curdir.length)
|
||||
println(" (from '$importloc')")
|
||||
}
|
||||
else
|
||||
println("")
|
||||
if(!Files.isReadable(filePath))
|
||||
throw ParsingFailedError("No such file: $filePath")
|
||||
internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
|
||||
val input = CharStreams.fromPath(filePath)
|
||||
return importModule(program, input, filePath, false)
|
||||
}
|
||||
internal fun importModule(program: Program, filePath: Path): Module {
|
||||
print("importing '${moduleName(filePath.fileName)}'")
|
||||
if(filePath.parent!=null) {
|
||||
var importloc = filePath.toString()
|
||||
val curdir = Paths.get("").toAbsolutePath().toString()
|
||||
if(importloc.startsWith(curdir))
|
||||
importloc = "." + importloc.substring(curdir.length)
|
||||
println(" (from '$importloc')")
|
||||
}
|
||||
else
|
||||
println("")
|
||||
if(!Files.isReadable(filePath))
|
||||
throw ParsingFailedError("No such file: $filePath")
|
||||
|
||||
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))
|
||||
return executeImportDirective(program, import, Paths.get(""))
|
||||
}
|
||||
|
||||
internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
|
||||
val moduleName = moduleName(modulePath.fileName)
|
||||
val lexer = CustomLexer(modulePath, stream)
|
||||
val lexerErrors = LexerErrorListener()
|
||||
lexer.addErrorListener(lexerErrors)
|
||||
val tokens = CommentHandlingTokenStream(lexer)
|
||||
val parser = prog8Parser(tokens)
|
||||
val parseTree = parser.module()
|
||||
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
|
||||
if(numberOfErrors > 0)
|
||||
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
|
||||
|
||||
// You can do something with the parsed comments:
|
||||
// tokens.commentTokens().forEach { println(it) }
|
||||
|
||||
// convert to Ast
|
||||
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
|
||||
moduleAst.program = program
|
||||
moduleAst.linkParents(program.namespace)
|
||||
program.modules.add(moduleAst)
|
||||
|
||||
// accept additional imports
|
||||
val lines = moduleAst.statements.toMutableList()
|
||||
lines.asSequence()
|
||||
.mapIndexed { i, it -> Pair(i, it) }
|
||||
.filter { (it.second as? Directive)?.directive == "%import" }
|
||||
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
|
||||
|
||||
moduleAst.statements = lines
|
||||
return moduleAst
|
||||
}
|
||||
|
||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||
val fileName = "$name.p8"
|
||||
val locations = mutableListOf(source.parent)
|
||||
|
||||
val propPath = System.getProperty("prog8.libdir")
|
||||
if(propPath!=null)
|
||||
locations.add(pathFrom(propPath))
|
||||
val envPath = System.getenv("PROG8_LIBDIR")
|
||||
if(envPath!=null)
|
||||
locations.add(pathFrom(envPath))
|
||||
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
|
||||
|
||||
locations.forEach {
|
||||
val file = pathFrom(it.toString(), fileName)
|
||||
if (Files.isReadable(file)) return file
|
||||
val input = CharStreams.fromPath(filePath)
|
||||
return importModule(program, input, filePath, false)
|
||||
}
|
||||
|
||||
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
|
||||
}
|
||||
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))
|
||||
return executeImportDirective(program, import, Paths.get(""))
|
||||
}
|
||||
|
||||
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
|
||||
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
|
||||
throw SyntaxError("invalid import directive", import.position)
|
||||
val moduleName = import.args[0].name!!
|
||||
if("$moduleName.p8" == import.position.file)
|
||||
throw SyntaxError("cannot import self", import.position)
|
||||
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
|
||||
val moduleName = moduleName(modulePath.fileName)
|
||||
val lexer = CustomLexer(modulePath, stream)
|
||||
val lexerErrors = LexerErrorListener()
|
||||
lexer.addErrorListener(lexerErrors)
|
||||
val tokens = CommentHandlingTokenStream(lexer)
|
||||
val parser = prog8Parser(tokens)
|
||||
val parseTree = parser.module()
|
||||
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
|
||||
if(numberOfErrors > 0)
|
||||
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
|
||||
|
||||
val existing = program.modules.singleOrNull { it.name == moduleName }
|
||||
if(existing!=null)
|
||||
return null
|
||||
// You can do something with the parsed comments:
|
||||
// tokens.commentTokens().forEach { println(it) }
|
||||
|
||||
val resource = tryGetEmbeddedResource("$moduleName.p8")
|
||||
val importedModule =
|
||||
if(resource!=null) {
|
||||
// load the module from the embedded resource
|
||||
resource.use {
|
||||
if(import.args[0].int==42)
|
||||
println("importing '$moduleName' (library, auto)")
|
||||
else
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
|
||||
}
|
||||
} else {
|
||||
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
|
||||
importModule(program, modulePath)
|
||||
// convert to Ast
|
||||
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
|
||||
moduleAst.program = program
|
||||
moduleAst.linkParents(program.namespace)
|
||||
program.modules.add(moduleAst)
|
||||
|
||||
// accept additional imports
|
||||
val lines = moduleAst.statements.toMutableList()
|
||||
lines.asSequence()
|
||||
.mapIndexed { i, it -> Pair(i, it) }
|
||||
.filter { (it.second as? Directive)?.directive == "%import" }
|
||||
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
|
||||
|
||||
moduleAst.statements = lines
|
||||
return moduleAst
|
||||
}
|
||||
|
||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||
val fileName = "$name.p8"
|
||||
val locations = mutableListOf(source.parent)
|
||||
|
||||
val propPath = System.getProperty("prog8.libdir")
|
||||
if(propPath!=null)
|
||||
locations.add(pathFrom(propPath))
|
||||
val envPath = System.getenv("PROG8_LIBDIR")
|
||||
if(envPath!=null)
|
||||
locations.add(pathFrom(envPath))
|
||||
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
|
||||
|
||||
locations.forEach {
|
||||
val file = pathFrom(it.toString(), fileName)
|
||||
if (Files.isReadable(file)) return file
|
||||
}
|
||||
|
||||
importedModule.checkImportedValid()
|
||||
return importedModule
|
||||
}
|
||||
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
|
||||
}
|
||||
|
||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
|
||||
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
|
||||
throw SyntaxError("invalid import directive", import.position)
|
||||
val moduleName = import.args[0].name!!
|
||||
if("$moduleName.p8" == import.position.file)
|
||||
throw SyntaxError("cannot import self", import.position)
|
||||
|
||||
val existing = program.modules.singleOrNull { it.name == moduleName }
|
||||
if(existing!=null)
|
||||
return null
|
||||
|
||||
val resource = tryGetEmbeddedResource("$moduleName.p8")
|
||||
val importedModule =
|
||||
if(resource!=null) {
|
||||
// load the module from the embedded resource
|
||||
resource.use {
|
||||
if(import.args[0].int==42)
|
||||
println("importing '$moduleName' (library, auto)")
|
||||
else
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
|
||||
}
|
||||
} else {
|
||||
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
|
||||
importModule(program, modulePath)
|
||||
}
|
||||
|
||||
importedModule.checkImportedValid(errors)
|
||||
return importedModule
|
||||
}
|
||||
|
||||
private fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package prog8.server.dbus
|
||||
|
||||
//import org.freedesktop.dbus.interfaces.DBusInterface
|
||||
//
|
||||
//
|
||||
//interface IrmenDbusTest: DBusInterface
|
||||
//{
|
||||
// fun Status(address: String): Map<Int, String>
|
||||
//}
|
||||
//
|
||||
//
|
||||
//internal class TestService: IrmenDbusTest {
|
||||
// override fun Status(address: String): Map<Int, String> {
|
||||
// return mapOf(
|
||||
// 5 to "hello",
|
||||
// 42 to address
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// override fun isRemote() = true
|
||||
// override fun getObjectPath() = "/razorvine/TestService"
|
||||
//}
|
||||
@@ -0,0 +1,17 @@
|
||||
package prog8.server.dbus
|
||||
|
||||
|
||||
//import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||
//
|
||||
//
|
||||
//fun main() {
|
||||
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
|
||||
// println(it.names.toList())
|
||||
// println(it.uniqueName)
|
||||
// println(it.address)
|
||||
// println(it.machineId)
|
||||
// val obj = it.getRemoteObject("local.net.razorvine.dbus.test", "/razorvine/TestService", IrmenDbusTest::class.java)
|
||||
// println(obj.Status("irmen"))
|
||||
// }
|
||||
//}
|
||||
//
|
||||
@@ -0,0 +1,18 @@
|
||||
package prog8.server.dbus
|
||||
|
||||
//import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||
//
|
||||
//
|
||||
//fun main() {
|
||||
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
|
||||
// it.requestBusName("local.net.razorvine.dbus.test")
|
||||
// println(it.names.toList())
|
||||
// println(it.uniqueName)
|
||||
// println(it.address)
|
||||
// println(it.machineId)
|
||||
// val service = TestService()
|
||||
// it.exportObject(service.objectPath, service)
|
||||
//
|
||||
// Thread.sleep(100000)
|
||||
// }
|
||||
//}
|
||||
@@ -7,7 +7,7 @@ import prog8.ast.expressions.ArrayLiteralValue
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.vm.astvm.VmExecutionException
|
||||
import java.util.Objects
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import prog8.ast.statements.*
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.vm.*
|
||||
import java.awt.EventQueue
|
||||
import java.util.ArrayDeque
|
||||
import java.util.*
|
||||
import kotlin.NoSuchElementException
|
||||
import kotlin.concurrent.fixedRateTimer
|
||||
import kotlin.math.*
|
||||
@@ -996,6 +996,11 @@ class AstVm(val program: Program, compilationTarget: String) {
|
||||
else -> RuntimeValueNumeric(DataType.BYTE, 1)
|
||||
}
|
||||
}
|
||||
"exit" -> {
|
||||
val rv = args.single().numericValue()
|
||||
dialog.canvas.printAsciiText("\n<program ended with exit($rv)>")
|
||||
throw VmTerminationException("program ended with exit($rv)")
|
||||
}
|
||||
else -> TODO("astvm implement builtin function $name")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.awt.*
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.KeyListener
|
||||
import java.awt.image.BufferedImage
|
||||
import java.util.ArrayDeque
|
||||
import java.util.*
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.Timer
|
||||
|
||||
+32
-28
@@ -6,6 +6,7 @@ import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
@@ -124,31 +125,34 @@ class TestCompiler {
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestZeropage {
|
||||
|
||||
private val errors = ErrorReporter()
|
||||
|
||||
@Test
|
||||
fun testNames() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
|
||||
|
||||
zp.allocate("", DataType.UBYTE, null)
|
||||
zp.allocate("", DataType.UBYTE, null)
|
||||
zp.allocate("varname", DataType.UBYTE, null)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||
assertFailsWith<AssertionError> {
|
||||
zp.allocate("varname", DataType.UBYTE, null)
|
||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||
}
|
||||
zp.allocate("varname2", DataType.UBYTE, null)
|
||||
zp.allocate("varname2", DataType.UBYTE, null, errors)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpFloatEnable() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
assertFailsWith<CompilerException> {
|
||||
zp.allocate("", DataType.FLOAT, null)
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
|
||||
assertFailsWith<CompilerException> {
|
||||
zp2.allocate("", DataType.FLOAT, null)
|
||||
zp2.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||
zp3.allocate("", DataType.FLOAT, null)
|
||||
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -173,7 +177,7 @@ class TestZeropage {
|
||||
println(zp.free)
|
||||
assertEquals(0, zp.available())
|
||||
assertFailsWith<CompilerException> {
|
||||
zp.allocate("", DataType.BYTE, null)
|
||||
zp.allocate("", DataType.BYTE, null, errors)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,19 +222,19 @@ class TestZeropage {
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// in regular zp there aren't 5 sequential bytes free
|
||||
zp.allocate("", DataType.FLOAT, null)
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
|
||||
for (i in 0 until zp.available()) {
|
||||
val loc = zp.allocate("", DataType.UBYTE, null)
|
||||
val loc = zp.allocate("", DataType.UBYTE, null, errors)
|
||||
assertTrue(loc > 0)
|
||||
}
|
||||
assertEquals(0, zp.available())
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
zp.allocate("", DataType.UBYTE, null)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
zp.allocate("", DataType.UWORD, null)
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,29 +242,29 @@ class TestZeropage {
|
||||
fun testFullAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
assertEquals(238, zp.available())
|
||||
val loc = zp.allocate("", DataType.UWORD, null)
|
||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||
assertTrue(loc > 3)
|
||||
assertFalse(loc in zp.free)
|
||||
val num = zp.available() / 2
|
||||
|
||||
for(i in 0..num-4) {
|
||||
zp.allocate("", DataType.UWORD, null)
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
assertEquals(6,zp.available())
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// can't allocate because no more sequential bytes, only fragmented
|
||||
zp.allocate("", DataType.UWORD, null)
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
|
||||
for(i in 0..5) {
|
||||
zp.allocate("", DataType.UBYTE, null)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
|
||||
assertEquals(0, zp.available())
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// no more space
|
||||
zp.allocate("", DataType.UBYTE, null)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,16 +272,16 @@ class TestZeropage {
|
||||
fun testEfficientAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(16, zp.available())
|
||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0x94, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0, zp.available())
|
||||
}
|
||||
}
|
||||
|
||||
+14
-14
@@ -143,6 +143,7 @@ Design principles and features
|
||||
still able to directly use memory addresses, CPU registers and ROM subroutines,
|
||||
and inline assembly to have full control when every cycle or byte matters
|
||||
- Arbitrary number of subroutine parameters (constrained only by available memory)
|
||||
- Nested subroutines can access variables from outer scopes, this avoids the need and overhead to pass everything via parameters
|
||||
- Complex nested expressions are possible
|
||||
- Values are typed. Types supported include signed and unsigned bytes and words, arrays, strings and floats.
|
||||
- No dynamic memory allocation or sizing! All variables stay fixed size as determined at compile time.
|
||||
@@ -158,7 +159,7 @@ Design principles and features
|
||||
- The compiler tries to optimize the program and generated code, but hand-tuning of the
|
||||
performance or space-critical parts will likely still be required. This is supported by
|
||||
the ability to easily write embedded assembly code directly in the program source code.
|
||||
- There are many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
|
||||
|
||||
.. _requirements:
|
||||
@@ -167,26 +168,25 @@ Required tools
|
||||
--------------
|
||||
|
||||
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
|
||||
A recent .exe version of this tool for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
|
||||
For other platforms it is very easy to compile it yourself (make ; make install).
|
||||
It's very easy to compile yourself.
|
||||
A recent precompiled .exe for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
|
||||
|
||||
A **Java runtime (jre or jdk), version 8 or newer** is required to run the packaged compiler.
|
||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead
|
||||
and for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ for
|
||||
downloads.
|
||||
A **Java runtime (jre or jdk), version 8 or newer** is required to run the prog8 compiler itself.
|
||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead.
|
||||
Fnd for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
|
||||
|
||||
Finally: a **C-64 emulator** (or a real C-64 ofcourse) to run the programs on. The compiler assumes the presence
|
||||
of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
Finally: a **C-64 emulator** (or a real C-64 ofcourse) can be nice to test and run your programs on.
|
||||
The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
|
||||
.. important::
|
||||
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
|
||||
|
||||
(re)building the compiler itself requires a recent Kotlin SDK.
|
||||
The compiler is developed using the `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_
|
||||
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
|
||||
But a bare Kotlin SDK installation should work just as well.
|
||||
(Re)building the compiler itself requires a recent Kotlin SDK.
|
||||
The compiler is developed using `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_ ,
|
||||
but only a Kotlin SDK installation should work as well, because the gradle tool is
|
||||
used to compile everything from the commandline.
|
||||
|
||||
Instructions on how to obtain a working compiler are in :ref:`building_compiler`.
|
||||
Instructions on how to obtain a prebuilt compiler are in :ref:`building_compiler`.
|
||||
|
||||
|
||||
.. toctree::
|
||||
|
||||
+62
-53
@@ -12,57 +12,56 @@ Elements of a program
|
||||
---------------------
|
||||
|
||||
Program
|
||||
Consists of one or more *modules*.
|
||||
Consists of one or more *modules*.
|
||||
|
||||
Module
|
||||
A file on disk with the ``.p8`` suffix. It contains *directives* and *code blocks*.
|
||||
Whitespace and indentation in the source code are arbitrary and can be tabs or spaces or both.
|
||||
You can also add *comments* to the source code.
|
||||
One moudule file can *import* others, and also import *library modules*.
|
||||
A file on disk with the ``.p8`` suffix. It can contain *directives* and *code blocks*.
|
||||
Whitespace and indentation in the source code are arbitrary and can be mixed tabs or spaces.
|
||||
A module file can *import* other modules, including *library modules*.
|
||||
|
||||
Comments
|
||||
Everything after a semicolon ``;`` is a comment and is ignored by the compiler.
|
||||
If the whole line is just a comment, it will be copied into the resulting assembly source code.
|
||||
This makes it easier to understand and relate the generated code. Examples::
|
||||
|
||||
A = 42 ; set the initial value to 42
|
||||
; next is the code that...
|
||||
Everything after a semicolon ``;`` is a comment and is ignored by the compiler.
|
||||
If the whole line is just a comment, this line will be copied into the resulting assembly source code for reference.
|
||||
|
||||
Directive
|
||||
These are special instructions for the compiler, to change how it processes the code
|
||||
and what kind of program it creates. A directive is on its own line in the file, and
|
||||
starts with ``%``, optionally followed by some arguments.
|
||||
These are special instructions for the compiler, to change how it processes the code
|
||||
and what kind of program it creates. A directive is on its own line in the file, and
|
||||
starts with ``%``, optionally followed by some arguments.
|
||||
|
||||
Code block
|
||||
A block of actual program code. It defines a *scope* (also known as 'namespace') and
|
||||
can contain Prog8 *code*, *variable declarations* and *subroutines*.
|
||||
More details about this below: :ref:`blocks`.
|
||||
A block of actual program code. It has a starting address in memory,
|
||||
and defines a *scope* (also known as 'namespace').
|
||||
It contains variables and subroutines.
|
||||
More details about this below: :ref:`blocks`.
|
||||
|
||||
Variable declarations
|
||||
The data that the code works on is stored in variables ('named values that can change').
|
||||
The compiler allocates the required memory for them.
|
||||
There is *no dynamic memory allocation*. The storage size of all variables
|
||||
is fixed and is determined at compile time.
|
||||
Variable declarations tend to appear at the top of the code block that uses them.
|
||||
They define the name and type of the variable, and its initial value.
|
||||
Prog8 supports a small list of data types, including special 'memory mapped' types
|
||||
that don't allocate storage but instead point to a fixed location in the address space.
|
||||
The data that the code works on is stored in variables ('named values that can change').
|
||||
The compiler allocates the required memory for them.
|
||||
There is *no dynamic memory allocation*. The storage size of all variables
|
||||
is fixed and is determined at compile time.
|
||||
Variable declarations tend to appear at the top of the code block that uses them.
|
||||
They define the name and type of the variable, and its initial value.
|
||||
Prog8 supports a small list of data types, including special 'memory mapped' types
|
||||
that don't allocate storage but instead point to a fixed location in the address space.
|
||||
|
||||
Code
|
||||
These are the instructions that make up the program's logic. There are different kinds of instructions
|
||||
('statements' is a better name):
|
||||
These are the instructions that make up the program's logic.
|
||||
Code can only occur inside a subroutine.
|
||||
There are different kinds of instructions ('statements' is a better name) such as:
|
||||
|
||||
- value assignment
|
||||
- looping (for, while, repeat, unconditional jumps)
|
||||
- conditional execution (if - then - else, when, and conditional jumps)
|
||||
- subroutine calls
|
||||
- label definition
|
||||
- value assignment
|
||||
- looping (for, while, repeat, unconditional jumps)
|
||||
- conditional execution (if - then - else, when, and conditional jumps)
|
||||
- subroutine calls
|
||||
- label definition
|
||||
|
||||
Subroutine
|
||||
Defines a piece of code that can be called by its name from different locations in your code.
|
||||
It accepts parameters and can return a value (optional).
|
||||
It can define its own variables, and it is even possible to define subroutines nested inside other subroutines.
|
||||
Their contents is scoped accordingly.
|
||||
Nested subroutines can access the variables from outer scopes.
|
||||
This removes the need and overhead to pass everything via parameters.
|
||||
|
||||
Label
|
||||
This is a named position in your code where you can jump to from another place.
|
||||
@@ -90,16 +89,20 @@ Scope
|
||||
Blocks, Scopes, and accessing Symbols
|
||||
-------------------------------------
|
||||
|
||||
**Blocks** are the top level separate pieces of code and data of your program. They are combined
|
||||
into a single output program. No code or data can occur outside a block. Here's an example::
|
||||
**Blocks** are the top level separate pieces of code and data of your program. They have a
|
||||
starting address in memory and will be combined together into a single output program.
|
||||
They can only contain *directives*, *variable declarations*, *subroutines* and *inline assembly code*.
|
||||
Your actual program code can only exist inside these subroutines.
|
||||
(except the occasional inline assembly)
|
||||
|
||||
main $c000 {
|
||||
; this is code inside the block...
|
||||
}
|
||||
Here's an example::
|
||||
|
||||
main $c000 {
|
||||
; this is code inside the block...
|
||||
}
|
||||
|
||||
The name of a block must be unique in your entire program.
|
||||
Also be careful when importing other modules; blocks in your own code cannot have
|
||||
Be careful when importing other modules; blocks in your own code cannot have
|
||||
the same name as a block defined in an imported module or library.
|
||||
|
||||
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
|
||||
@@ -109,7 +112,7 @@ want to work on later, because the contents of the ignored block are not fully p
|
||||
The address can be used to place a block at a specific location in memory.
|
||||
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
|
||||
the previous block in memory).
|
||||
The address must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$200`` is the cpu stack).
|
||||
It must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$1ff`` is the cpu stack).
|
||||
|
||||
|
||||
.. _scopes:
|
||||
@@ -133,14 +136,13 @@ Scopes are created using either of these two statements:
|
||||
- subroutines (nested named scope)
|
||||
|
||||
.. note::
|
||||
In contrast to many other programming languages, a new scope is *not* created inside
|
||||
Unlike many other programming languages, a new scope is *not* created inside
|
||||
for, while and repeat statements, nor for the if statement and branching conditionals.
|
||||
This is a bit restrictive because you have to think harder about what variables you
|
||||
want to use inside a subroutine. But it is done precisely for this reason; memory in the
|
||||
target system is very limited and it would be a waste to allocate a lot of variables.
|
||||
|
||||
Right now the prog8 compiler is not advanced enough to be able to 'share' or 'overlap'
|
||||
variables intelligently by itself. So for now, it's something the programmer has to think about.
|
||||
This can be a bit restrictive because as a programmer you have to think harder about what variables you
|
||||
want to use inside a subroutine. But it is done precisely for this reason: memory in prog8's
|
||||
target systems is usually very limited and it would be a waste to allocate a lot of variables.
|
||||
The prog8 compiler is not yet advanced enough to be able to share or overlap
|
||||
variables intelligently. So for now that is something the programmer has to think about.
|
||||
|
||||
|
||||
Program Start and Entry Point
|
||||
@@ -159,12 +161,12 @@ taking no parameters and having no return value.
|
||||
|
||||
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call)::
|
||||
|
||||
main {
|
||||
sub start () {
|
||||
; program entrypoint code here
|
||||
return
|
||||
}
|
||||
}
|
||||
main {
|
||||
sub start () {
|
||||
; program entrypoint code here
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
The ``main`` module is always relocated to the start of your programs
|
||||
@@ -175,7 +177,6 @@ calls with the SYS statement.
|
||||
|
||||
|
||||
|
||||
|
||||
Variables and values
|
||||
--------------------
|
||||
|
||||
@@ -406,6 +407,10 @@ Iterating with a floating point variable is not supported. If you want to loop o
|
||||
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
|
||||
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true.
|
||||
|
||||
The *forever*-loop is used to simply run a piece of code in a loop, forever. You can still
|
||||
break out of this loop if desired. A "while true" or "until false" loop is equivalent to
|
||||
a forever-loop.
|
||||
|
||||
You can also create loops by using the ``goto`` statement, but this should usually be avoided.
|
||||
|
||||
.. attention::
|
||||
@@ -836,6 +841,10 @@ rrestore()
|
||||
read_flags()
|
||||
Returns the current value of the CPU status register.
|
||||
|
||||
exit(returncode)
|
||||
Immediately stops the program and exits it, with the returncode in the A register.
|
||||
Note: custom interrupt handlers remain active unless manually cleared first!
|
||||
|
||||
|
||||
|
||||
Library routines
|
||||
|
||||
@@ -172,13 +172,13 @@ Code blocks
|
||||
-----------
|
||||
|
||||
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
|
||||
can contain Prog8 *code*, *directives*, *variable declarations* and *subroutines*::
|
||||
can only contain *directives*, *variable declarations*, *subroutines* or *inline assembly*::
|
||||
|
||||
<blockname> [<address>] {
|
||||
<directives>
|
||||
<variables>
|
||||
<statements>
|
||||
<subroutines>
|
||||
<inline asm>
|
||||
}
|
||||
|
||||
The <blockname> must be a valid identifier.
|
||||
@@ -191,7 +191,6 @@ Also read :ref:`blocks`. Here is an example of a code block, to be loaded at ``
|
||||
}
|
||||
|
||||
|
||||
|
||||
Labels
|
||||
------
|
||||
|
||||
@@ -598,8 +597,8 @@ You can use a single statement, or a statement block like in the example below::
|
||||
}
|
||||
|
||||
|
||||
repeat--until loop
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
repeat-until loop
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Until the given condition is true (1), repeat the given statement(s).
|
||||
You can use a single statement, or a statement block like in the example below::
|
||||
@@ -611,6 +610,19 @@ You can use a single statement, or a statement block like in the example below::
|
||||
} until <condition>
|
||||
|
||||
|
||||
forever loop
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Simply run the code in a loop, forever. It's the same as a while true or until false loop,
|
||||
or just a jump back to a previous label. You can still break out of this loop as well, if you want::
|
||||
|
||||
forever {
|
||||
; .. do stuff
|
||||
if something
|
||||
break ; you can exit the loop if you want
|
||||
}
|
||||
|
||||
|
||||
Conditional Execution and Jumps
|
||||
-------------------------------
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- remove statements after an exit() or return
|
||||
- fix warnings about that unreachable code?
|
||||
|
||||
- option to load library files from a directory instead of the embedded ones
|
||||
|
||||
|
||||
@@ -10,6 +13,9 @@ Memory Block Operations integrated in language?
|
||||
|
||||
array/string memory block operations?
|
||||
|
||||
- vector inc/dec/add/sub/mul/div...? (on array or string):
|
||||
``arrayvar++ / arrayvar-- / arrayvar += 2 / arrayvar -= 2 / arrayvar *= 3 / arrayvar /= 3``
|
||||
|
||||
- array operations
|
||||
copy (from another array with the same length), shift-N(left,right), rotate-N(left,right)
|
||||
clear (set whole array to the given value, default 0)
|
||||
|
||||
@@ -104,7 +104,18 @@ main {
|
||||
ub = all(farr)
|
||||
if ub==0 c64scr.print("error all10\n")
|
||||
|
||||
check_eval_stack()
|
||||
|
||||
c64scr.print("\nyou should see no errors above.")
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,7 +36,8 @@ main {
|
||||
}
|
||||
|
||||
sub start() {
|
||||
unimplemented()
|
||||
; TODO call this too: unimplemented()
|
||||
|
||||
lsr(A)
|
||||
lsl(A)
|
||||
ror(A)
|
||||
@@ -107,5 +108,17 @@ main {
|
||||
ror(uw)
|
||||
rol2(uw)
|
||||
ror2(uw)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,7 @@ main {
|
||||
div_float(0,1,0)
|
||||
div_float(999.9,111.0,9.008108108108107)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@@ -102,4 +103,12 @@ main {
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ main {
|
||||
minus_float(2.5,1.5,1.0)
|
||||
minus_float(-1.5,3.5,-5.0)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@@ -110,4 +111,13 @@ main {
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@ main {
|
||||
mul_float(2.5,10,25)
|
||||
mul_float(-1.5,10,-15)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@@ -104,4 +105,12 @@ main {
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ main {
|
||||
plus_float(-1.5,3.5,2.0)
|
||||
plus_float(-1.1,3.3,2.2)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@@ -108,4 +109,13 @@ main {
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
main {
|
||||
|
||||
|
||||
sub start() {
|
||||
|
||||
c64scr.plot(0,24)
|
||||
@@ -64,6 +63,7 @@ main {
|
||||
warr[1]--
|
||||
flarr[1] --
|
||||
check_ub(ub, 200)
|
||||
|
||||
Y=100
|
||||
Y--
|
||||
check_ub(Y, 99)
|
||||
@@ -77,7 +77,7 @@ main {
|
||||
check_uw(uwarr[1], 2000)
|
||||
check_w(warr[1], -1000)
|
||||
|
||||
@($0400+400-1) = X
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_ub(ubyte value, ubyte expected) {
|
||||
@@ -139,4 +139,13 @@ main {
|
||||
c64flt.print_f(expected)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ main {
|
||||
remainder_uword(40000,511,142)
|
||||
remainder_uword(40000,500,0)
|
||||
remainder_uword(43211,12,11)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub remainder_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@@ -46,4 +48,12 @@ main {
|
||||
c64scr.print_uw(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
byte b1
|
||||
byte b2
|
||||
ubyte ub1
|
||||
ubyte ub2
|
||||
word w1
|
||||
word w2
|
||||
uword uw1
|
||||
uword uw2
|
||||
float f1
|
||||
float f2
|
||||
|
||||
b1 = 10
|
||||
b2 = 10
|
||||
if sgn(b2-b1) != 0
|
||||
c64scr.print("sgn1 error1\n")
|
||||
|
||||
b1 = -100
|
||||
b2 = -100
|
||||
if sgn(b2-b1) != 0
|
||||
c64scr.print("sgn1 error2\n")
|
||||
|
||||
ub1 = 200
|
||||
ub2 = 200
|
||||
if sgn(ub2-ub1) != 0
|
||||
c64scr.print("sgn1 error3\n")
|
||||
|
||||
w1 = 100
|
||||
w2 = 100
|
||||
if sgn(w2-w1) != 0
|
||||
c64scr.print("sgn1 error4\n")
|
||||
|
||||
w1 = -2000
|
||||
w2 = -2000
|
||||
if sgn(w2-w1) != 0
|
||||
c64scr.print("sgn1 error5\n")
|
||||
|
||||
uw1 = 999
|
||||
uw2 = 999
|
||||
if sgn(uw2-uw1) != 0
|
||||
c64scr.print("sgn1 error6\n")
|
||||
|
||||
f1 = 3.45
|
||||
f2 = 3.45
|
||||
if sgn(f2-f1) != 0
|
||||
c64scr.print("sgn1 error7\n")
|
||||
|
||||
|
||||
; -1
|
||||
b1 = 11
|
||||
b2 = 10
|
||||
if sgn(b2-b1) != -1
|
||||
c64scr.print("sgn2 error1\n")
|
||||
|
||||
b1 = -10
|
||||
b2 = -100
|
||||
if sgn(b2-b1) != -1
|
||||
c64scr.print("sgn2 error2\n")
|
||||
|
||||
ub1 = 202
|
||||
ub2 = 200
|
||||
if sgn(ub2 as byte - ub1 as byte) != -1
|
||||
c64scr.print("sgn2 error3\n")
|
||||
|
||||
w1 = 101
|
||||
w2 = 100
|
||||
if sgn(w2-w1) != -1
|
||||
c64scr.print("sgn2 error4\n")
|
||||
|
||||
w1 = -200
|
||||
w2 = -2000
|
||||
if sgn(w2-w1) != -1
|
||||
c64scr.print("sgn2 error5\n")
|
||||
|
||||
uw1 = 2222
|
||||
uw2 = 999
|
||||
if sgn(uw2 as word - uw1 as word) != -1
|
||||
c64scr.print("sgn2 error6\n")
|
||||
if sgn(uw2 - uw1) != -1
|
||||
c64scr.print("sgn2 error6\n")
|
||||
|
||||
f1 = 3.45
|
||||
f2 = 1.11
|
||||
if sgn(f2-f1) != -1
|
||||
c64scr.print("sgn2 error7\n")
|
||||
|
||||
; +1
|
||||
b1 = 11
|
||||
b2 = 20
|
||||
if sgn(b2-b1) != 1
|
||||
c64scr.print("sgn3 error1\n")
|
||||
|
||||
b1 = -10
|
||||
b2 = -1
|
||||
if sgn(b2-b1) != 1
|
||||
c64scr.print("sgn3 error2\n")
|
||||
|
||||
ub1 = 202
|
||||
ub2 = 205
|
||||
if sgn(ub2-ub1) != 1
|
||||
c64scr.print("sgn3 error3\n")
|
||||
|
||||
w1 = 101
|
||||
w2 = 200
|
||||
if sgn(w2-w1) != 1
|
||||
c64scr.print("sgn3 error4\n")
|
||||
|
||||
w1 = -200
|
||||
w2 = -20
|
||||
if sgn(w2-w1) != 1
|
||||
c64scr.print("sgn3 error5\n")
|
||||
|
||||
uw1 = 2222
|
||||
uw2 = 9999
|
||||
if sgn(uw2-uw1) != 1
|
||||
c64scr.print("sgn3 error6\n")
|
||||
|
||||
f1 = 3.45
|
||||
f2 = 5.11
|
||||
if sgn(f2-f1) != 1
|
||||
c64scr.print("sgn3 error7\n")
|
||||
|
||||
c64scr.print("should see no sgn errors\n")
|
||||
}
|
||||
}
|
||||
+18
-17
@@ -16,7 +16,7 @@ sub start() {
|
||||
void c64.CHRIN()
|
||||
c64.CLEARSCR()
|
||||
|
||||
while(true) {
|
||||
forever {
|
||||
uword note
|
||||
for note in notes {
|
||||
ubyte note1 = lsb(note)
|
||||
@@ -34,27 +34,28 @@ sub start() {
|
||||
delay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub delay() {
|
||||
ubyte d
|
||||
for d in 0 to 12 {
|
||||
while(c64.RASTER!=0) {
|
||||
; tempo delay synced to screen refresh
|
||||
}
|
||||
sub delay() {
|
||||
ubyte d
|
||||
for d in 0 to 12 {
|
||||
while c64.RASTER!=0 {
|
||||
; tempo delay synced to screen refresh
|
||||
}
|
||||
}
|
||||
|
||||
sub print_notes(ubyte n1, ubyte n2) {
|
||||
c64.CHROUT('\n')
|
||||
c64scr.plot(n1/2, 24)
|
||||
c64.COLOR=7
|
||||
c64.CHROUT('Q')
|
||||
c64scr.plot(n2/2, 24)
|
||||
c64.COLOR=4
|
||||
c64.CHROUT('Q')
|
||||
}
|
||||
}
|
||||
|
||||
sub print_notes(ubyte n1, ubyte n2) {
|
||||
c64.CHROUT('\n')
|
||||
c64scr.plot(n1/2, 24)
|
||||
c64.COLOR=7
|
||||
c64.CHROUT('Q')
|
||||
c64scr.plot(n2/2, 24)
|
||||
c64.COLOR=4
|
||||
c64.CHROUT('Q')
|
||||
}
|
||||
|
||||
|
||||
; details about the boulderdash music can be found here:
|
||||
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune
|
||||
|
||||
|
||||
@@ -106,12 +106,16 @@ main {
|
||||
else
|
||||
c64scr.print("error in 22>=22!\n")
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("stack x ok!\n")
|
||||
else
|
||||
c64scr.print("error: stack x != 255 !\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
@@ -106,11 +106,16 @@ main {
|
||||
else
|
||||
c64scr.print("error in -22.2>=-22.2!\n")
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("stack x ok!\n")
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print("error: stack x != 255 !\n")
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -106,12 +106,16 @@ main {
|
||||
else
|
||||
c64scr.print("error in 22>=22!\n")
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("stack x ok!\n")
|
||||
else
|
||||
c64scr.print("error: stack x != 255 !\n")
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -106,13 +106,16 @@ main {
|
||||
else
|
||||
c64scr.print("error in 322>=322!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("stack x ok!\n")
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print("error: stack x != 255 !\n")
|
||||
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -138,13 +138,16 @@ main {
|
||||
else
|
||||
c64scr.print("error in 1000>=1000!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("stack x ok!\n")
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print("error: stack x != 255 !\n")
|
||||
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,16 +52,10 @@ main {
|
||||
c64scr.print("v1=20, v2=-111\n")
|
||||
compare()
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("\nstack x ok!\n")
|
||||
else
|
||||
c64scr.print("\nerror: stack x != 255 !\n")
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
|
||||
sub compare() {
|
||||
sub compare() {
|
||||
c64scr.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
@@ -98,4 +92,13 @@ main {
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,16 +68,10 @@ main {
|
||||
c64scr.print("v1 = v2 = 0\n")
|
||||
compare()
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("\nstack x ok!\n")
|
||||
else
|
||||
c64scr.print("\nerror: stack x != 255 !\n")
|
||||
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
sub compare() {
|
||||
c64scr.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
@@ -112,7 +106,14 @@ main {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
@@ -52,15 +52,10 @@ main {
|
||||
c64scr.print("v1=220, v2=10\n")
|
||||
compare()
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("\nstack x ok!\n")
|
||||
else
|
||||
c64scr.print("\nerror: stack x != 255 !\n")
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
sub compare() {
|
||||
c64scr.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
@@ -97,4 +92,13 @@ main {
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -82,16 +82,10 @@ main {
|
||||
c64scr.print("v1 = v2 = aa\n")
|
||||
compare()
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("\nstack x ok!\n")
|
||||
else
|
||||
c64scr.print("\nerror: stack x != 255 !\n")
|
||||
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
sub compare() {
|
||||
c64scr.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
@@ -128,4 +122,13 @@ main {
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -118,15 +118,10 @@ main {
|
||||
c64scr.print("v1 = v2 = aa\n")
|
||||
compare()
|
||||
|
||||
ubyte endX = X
|
||||
if endX == 255
|
||||
c64scr.print("\nstack x ok!\n")
|
||||
else
|
||||
c64scr.print("\nerror: stack x != 255 !\n")
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
sub compare() {
|
||||
c64scr.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
@@ -163,4 +158,13 @@ main {
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -19,7 +19,7 @@ main {
|
||||
|
||||
sub start() {
|
||||
float time=0.0
|
||||
while(true) {
|
||||
forever {
|
||||
rotate_vertices(time)
|
||||
c64scr.clear_screenchars(32)
|
||||
draw_edges()
|
||||
@@ -27,7 +27,9 @@ main {
|
||||
c64scr.plot(0,0)
|
||||
c64scr.print("3d cube! (float) ")
|
||||
c64scr.print_ub(c64.TIME_LO)
|
||||
c64scr.print(" jiffies/frame")
|
||||
c64scr.print(" jiffies/fr = ")
|
||||
c64scr.print_ub(60/c64.TIME_LO)
|
||||
c64scr.print(" fps")
|
||||
c64.TIME_LO=0
|
||||
}
|
||||
}
|
||||
@@ -76,7 +78,7 @@ main {
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
rz = rotatedz[i]
|
||||
if rz >= 0.1 {
|
||||
persp = (5.0+rz)/height
|
||||
persp = (5.0+rz)/(height as float)
|
||||
sx = rotatedx[i] / persp + width/2.0 as ubyte
|
||||
sy = rotatedy[i] / persp + height/2.0 as ubyte
|
||||
c64scr.setcc(sx, sy, 46, i+2)
|
||||
@@ -86,7 +88,7 @@ main {
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
rz = rotatedz[i]
|
||||
if rz < 0.1 {
|
||||
persp = (5.0+rz)/height
|
||||
persp = (5.0+rz)/(height as float)
|
||||
sx = rotatedx[i] / persp + width/2.0 as ubyte
|
||||
sy = rotatedy[i] / persp + height/2.0 as ubyte
|
||||
c64scr.setcc(sx, sy, 81, i+2)
|
||||
|
||||
@@ -82,7 +82,7 @@ main {
|
||||
uword angley
|
||||
uword anglez
|
||||
word rz=33
|
||||
while(true) {
|
||||
forever {
|
||||
c64.TIME_LO=0
|
||||
rotate_vertices(msb(anglex), msb(angley), msb(anglez))
|
||||
position_sprites()
|
||||
@@ -92,7 +92,9 @@ main {
|
||||
c64scr.plot(0,0)
|
||||
c64scr.print("3d cube! (sprites) ")
|
||||
c64scr.print_ub(c64.TIME_LO)
|
||||
c64scr.print(" jiffies/frame ")
|
||||
c64scr.print(" jiffies/fr = ")
|
||||
c64scr.print_ub(60/c64.TIME_LO)
|
||||
c64scr.print(" fps")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -22,7 +22,7 @@ main {
|
||||
uword angley
|
||||
uword anglez
|
||||
word rz=33
|
||||
while(true) {
|
||||
forever {
|
||||
rotate_vertices(msb(anglex), msb(angley), msb(anglez))
|
||||
c64scr.clear_screenchars(32)
|
||||
draw_edges()
|
||||
@@ -30,9 +30,11 @@ main {
|
||||
angley+=433
|
||||
anglez+=907
|
||||
c64scr.plot(0,0)
|
||||
c64scr.print("3d cube! (integer) ")
|
||||
c64scr.print("3d cube! ")
|
||||
c64scr.print_ub(c64.TIME_LO)
|
||||
c64scr.print(" jiffies/frame")
|
||||
c64scr.print(" jiffies/fr = ")
|
||||
c64scr.print_ub(60/c64.TIME_LO)
|
||||
c64scr.print(" fps")
|
||||
c64.TIME_LO=0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ main {
|
||||
c64scr.print_uw(fib_next())
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub fib_setup() {
|
||||
@@ -31,4 +33,13 @@ main {
|
||||
current = new
|
||||
return prev
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,17 @@ main {
|
||||
c64.CHROUT('\n')
|
||||
|
||||
c64scr.print("bye!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
c64scr.print("mid-point\ncircle\n and\nbresenham\nline\nalgorithms.\n")
|
||||
|
||||
const ubyte xcenter = 20
|
||||
const ubyte ycenter = 12
|
||||
ubyte r
|
||||
for r in 3 to 12 step 3 {
|
||||
circle(r)
|
||||
}
|
||||
;line(5,3, 30,3)
|
||||
;line(5,3, 5, 24)
|
||||
;line(30,3,30,24)
|
||||
;line(5,24,30,24)
|
||||
|
||||
line(1, 10, 38, 24)
|
||||
line(1, 20, 38, 2)
|
||||
line(20, 4, 10, 24)
|
||||
line(39, 16, 12, 0)
|
||||
|
||||
sub line(ubyte x1, ubyte y1, ubyte x2, ubyte y2) {
|
||||
byte d = 0
|
||||
ubyte dx = abs(x2 - x1)
|
||||
ubyte dy = abs(y2 - y1)
|
||||
ubyte dx2 = 2 * dx
|
||||
ubyte dy2 = 2 * dy
|
||||
byte ix = sgn(x2 as byte - x1 as byte)
|
||||
byte iy = sgn(y2 as byte - y1 as byte)
|
||||
ubyte x = x1
|
||||
ubyte y = y1
|
||||
|
||||
if dx >= dy {
|
||||
forever {
|
||||
c64scr.setcc(x, y, 42, 5)
|
||||
if x==x2
|
||||
return
|
||||
x += ix as ubyte
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y += iy as ubyte
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
c64scr.setcc(x, y, 42, 5)
|
||||
if y == y2
|
||||
return
|
||||
y += iy as ubyte
|
||||
d += dx2
|
||||
if d > dy {
|
||||
x += ix as ubyte
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(ubyte radius) {
|
||||
byte x = radius as byte
|
||||
byte y = 0
|
||||
byte decisionOver2 = 1-x
|
||||
|
||||
while x>=y {
|
||||
c64scr.setcc(xcenter + x as ubyte, ycenter + y as ubyte, 81, 1)
|
||||
c64scr.setcc(xcenter - x as ubyte, ycenter + y as ubyte, 81, 1)
|
||||
c64scr.setcc(xcenter + x as ubyte, ycenter - y as ubyte, 81, 1)
|
||||
c64scr.setcc(xcenter - x as ubyte, ycenter - y as ubyte, 81, 1)
|
||||
c64scr.setcc(xcenter + y as ubyte, ycenter + x as ubyte, 81, 1)
|
||||
c64scr.setcc(xcenter - y as ubyte, ycenter + x as ubyte, 81, 1)
|
||||
c64scr.setcc(xcenter + y as ubyte, ycenter - x as ubyte, 81, 1)
|
||||
c64scr.setcc(xcenter - y as ubyte, ycenter - x as ubyte, 81, 1)
|
||||
y++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*y+1
|
||||
else {
|
||||
x--
|
||||
decisionOver2 += 2*(y-x)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,5 +49,15 @@ main {
|
||||
c64scr.print("finished in ")
|
||||
c64flt.print_f(duration)
|
||||
c64scr.print(" seconds!\n")
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -57,6 +57,18 @@ main {
|
||||
c64scr.print("Thanks for playing, ")
|
||||
c64scr.print(name)
|
||||
c64scr.print(".\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 115 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user