Merge branch 'v7.1' into testability_steps_1_2_3_again

# Conflicts:
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	compilerAst/src/prog8/parser/ModuleParsing.kt
#	compilerAst/test/TestAntlrParser.kt
#	parser/antlr/Prog8ANTLR.g4
This commit is contained in:
Irmen de Jong 2021-10-10 22:20:08 +02:00
commit f37fb82d53
58 changed files with 338 additions and 333 deletions

2
.idea/compiler.xml generated
View File

@ -4,4 +4,4 @@
<option name="BUILD_PROCESS_HEAP_SIZE" value="1200" />
<bytecodeTargetLevel target="11" />
</component>
</project>
</project>

View File

@ -1,3 +1,3 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.5.20" apply false
id "org.jetbrains.kotlin.jvm" version "1.5.30" apply false
}

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target c64
%option enable_floats
floats {

View File

@ -1,4 +1,3 @@
%target c64
%import textio
; bitmap pixel graphics module for the C64

View File

@ -5,8 +5,6 @@
;
; indent format: TABS, size=8
%target c64
c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
@ -502,11 +500,9 @@ sys {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
%asm {{
- lda c64.RASTER
beq -
- lda c64.RASTER
bne -
bit c64.SCROLY
- bit c64.SCROLY
bpl -
- bit c64.SCROLY
bmi -
rts
}}

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target c64
%import syslib
%import conv

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target cx16
%option enable_floats
floats {

View File

@ -1,5 +1,3 @@
%target cx16
; Bitmap pixel graphics routines for the CommanderX16
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)

View File

@ -1,4 +1,3 @@
%target cx16
%import syslib
%import textio

View File

@ -1,5 +1,3 @@
%target cx16
; Manipulate the Commander X16's display color palette.
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
@ -9,7 +7,7 @@ palette {
ubyte c
sub set_color(ubyte index, uword color) {
vera_palette_ptr = $fa00+index*2
vera_palette_ptr = $fa00+(index as uword * 2)
cx16.vpoke(1, vera_palette_ptr, lsb(color))
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(color))
@ -70,11 +68,11 @@ palette {
}
}
inline sub set_all_black() {
sub set_all_black() {
set_monochrome($000, $000)
}
inline sub set_all_white() {
sub set_all_white() {
set_monochrome($fff, $fff)
}

View File

@ -5,9 +5,6 @@
;
; indent format: TABS, size=8
%target cx16
c64 {
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target cx16
%import syslib
%import conv

View File

@ -301,17 +301,6 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
break
}
val inliner = SubroutineInliner(programAst, errors, options)
inliner.visit(programAst)
errors.report()
if(errors.noErrors()) {
inliner.applyModifications()
inliner.fixCallsToInlinedSubroutines()
val remover2 = UnusedCodeRemover(programAst, errors, compTarget)
remover2.visit(programAst)
remover2.applyModifications()
}
errors.report()
}

View File

@ -270,10 +270,11 @@ internal class AstChecker(private val program: Program,
}
}
// scope check
if(subroutine.parent !is Block && subroutine.parent !is Subroutine) {
if(subroutine.inline && !subroutine.isAsmSubroutine)
err("subroutine inlining is currently only supported on asmsub routines")
if(subroutine.parent !is Block && subroutine.parent !is Subroutine)
err("subroutines can only be defined in the scope of a block or within another subroutine")
}
if(subroutine.isAsmSubroutine) {
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
@ -715,14 +716,6 @@ internal class AstChecker(private val program: Program,
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
err("invalid option directive argument(s)")
}
"%target" -> {
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.size != 1)
err("directive requires one argument")
if(directive.args.single().name !in setOf(C64Target.name, Cx16Target.name))
err("invalid compilation target")
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
}
super.visit(directive)

View File

@ -33,16 +33,6 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
super.visit(block)
}
override fun visit(directive: Directive) {
if(directive.directive=="%target") {
val compatibleTarget = directive.args.single().name
if (compatibleTarget != compTarget.name)
errors.err("module's compilation target ($compatibleTarget) differs from active target (${compTarget.name})", directive.position)
}
super.visit(directive)
}
override fun visit(decl: VarDecl) {
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }

View File

@ -864,7 +864,7 @@ internal class AsmGen(private val program: Program,
if(sub.inline) {
if(options.optimize) {
if(sub.isAsmSubroutine ||callGraph.unused(sub))
if(sub.isAsmSubroutine || callGraph.unused(sub))
return
// from an inlined subroutine only the local variables are generated,
@ -873,7 +873,7 @@ internal class AsmGen(private val program: Program,
onlyVariables = true
}
else if(sub.amountOfRtsInAsm()==0) {
// make sure the NOT INLINED subroutine actually does an rts at the end
// make sure the NOT INLINED subroutine actually does a rts at the end
sub.statements.add(Return(null, Position.DUMMY))
}
}

View File

@ -750,7 +750,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// optimized simple case: swap two memory locations
if(first is DirectMemoryRead && second is DirectMemoryRead) {
// TODO optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+i1), @(ptr+i2))
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
@ -773,6 +772,50 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2")
return
}
addr1==null && addr2==null && name1==null && name2==null -> {
val firstExpr = first.addressExpression as? BinaryExpression
val secondExpr = second.addressExpression as? BinaryExpression
if(firstExpr!=null && secondExpr!=null) {
val pointerVariable = firstExpr.left as? IdentifierReference
val firstOffset = firstExpr.right
val secondOffset = secondExpr.right
if(pointerVariable != null
&& pointerVariable isSameAs secondExpr.left
&& firstExpr.operator == "+" && secondExpr.operator == "+"
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
) {
val pointerVar = firstExpr.left as IdentifierReference
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
if(firstOffset!=secondOffset) {
swapArrayValues(
DataType.UBYTE,
asmgen.asmVariableName(pointerVariable), firstOffset,
asmgen.asmVariableName(pointerVariable), secondOffset
)
return
}
} else if(firstOffset is TypecastExpression && secondOffset is TypecastExpression) {
if(firstOffset.type in WordDatatypes && secondOffset.type in WordDatatypes) {
val firstOffsetVar = firstOffset.expression as? IdentifierReference
val secondOffsetVar = secondOffset.expression as? IdentifierReference
if(firstOffsetVar!=null && secondOffsetVar!=null) {
if(firstOffsetVar!=secondOffsetVar) {
swapArrayValues(
DataType.UBYTE,
asmgen.asmVariableName(pointerVariable), firstOffsetVar,
asmgen.asmVariableName(pointerVariable), secondOffsetVar
)
return
}
}
}
} else if(firstOffset is IdentifierReference || secondOffset is IdentifierReference) {
throw AssemblyError("expected a typecast-to-word for index variable at ${firstOffset.position} and/or ${secondOffset.position}")
}
}
}
}
}
}
@ -888,7 +931,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
sta P8ZP_SCRATCH_W2
lda #>(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
jsr floats.func_swap_f
""")
}
else -> throw AssemblyError("invalid aray elt type")
@ -961,7 +1004,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
sta P8ZP_SCRATCH_W2
bcc +
inc P8ZP_SCRATCH_W2+1
+ jsr floats.swap_floats
+ jsr floats.func_swap_f
""")
}
else -> throw AssemblyError("invalid aray elt type")
@ -1019,7 +1062,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ jsr floats.swap_floats
+ jsr floats.func_swap_f
""")
}
else -> throw AssemblyError("invalid aray elt type")
@ -1077,7 +1120,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
sta P8ZP_SCRATCH_W2
lda #>(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
jsr floats.func_swap_f
""")
}
else -> throw AssemblyError("invalid aray elt type")

View File

@ -118,16 +118,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// we do this by copying the subroutine's statements at the call site.
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
// note: for now, this is only reliably supported for asmsubs.
if(!sub.isAsmSubroutine)
throw AssemblyError("can only reliably inline asmsub routines at this time")
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
statements.forEach {
if(it is Return) {
asmgen.translate(it, false) // don't use RTS for the inlined return statement
} else {
if(!sub.inline || it !is VarDecl)
asmgen.translate(it)
}
}
val assembly = sub.statements.single() as InlineAssembly
asmgen.translate(assembly)
asmgen.out(" \t; inlined routine end: ${sub.name}")
}

View File

@ -1359,8 +1359,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
// we make an exception in the type check for assigning something to a cx16 virtual register
if(target.register !in Cx16VirtualRegisters) {
// we make an exception in the type check for assigning something to a cx16 virtual register, or a register pair
// these will be correctly typecasted from a byte to a word value
if(target.register !in Cx16VirtualRegisters &&
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
if(target.kind==TargetStorageKind.VARIABLE) {
val parts = target.asmVarname.split('.')
if (parts.size != 2 || parts[0] != "cx16")

View File

@ -14,6 +14,7 @@ import prog8.compiler.astprocessing.toConstantIntegerRange
import prog8.compiler.target.ICompilationTarget
// Fix up the literal value's type to match that of the vardecl
// (also check range literal operands types before they get expanded to arrays for instance)
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@ -54,6 +55,35 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
}
return noModifications
}
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
val from = range.from.constValue(program)?.number?.toDouble()
val to = range.to.constValue(program)?.number?.toDouble()
val step = range.step.constValue(program)?.number?.toDouble()
if(from==null) {
if(!range.from.inferType(program).isInteger())
errors.err("range expression from value must be integer", range.from.position)
} else if(from-from.toInt()>0) {
errors.err("range expression from value must be integer", range.from.position)
}
if(to==null) {
if(!range.to.inferType(program).isInteger())
errors.err("range expression to value must be integer", range.to.position)
} else if(to-to.toInt()>0) {
errors.err("range expression to value must be integer", range.to.position)
}
if(step==null) {
if(!range.step.inferType(program).isInteger())
errors.err("range expression step value must be integer", range.step.position)
} else if(step-step.toInt()>0) {
errors.err("range expression step value must be integer", range.step.position)
}
return noModifications
}
}

View File

@ -40,6 +40,41 @@ internal class StatementOptimizer(private val program: Program,
return noModifications
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with a simple value,
// remove the jump altogeter and inline the returnvalue directly.
val subroutine = functionCall.target.targetSubroutine(program)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value?.isSimple==true) {
val orig = first.value!!
val copy = when(orig) {
is AddressOf -> {
val scoped = scopePrefix(orig.identifier, subroutine)
AddressOf(scoped, orig.position)
}
is DirectMemoryRead -> {
when(val expr = orig.addressExpression) {
is NumericLiteralValue -> DirectMemoryRead(expr.copy(), orig.position)
else -> return noModifications
}
}
is IdentifierReference -> scopePrefix(orig, subroutine)
is NumericLiteralValue -> orig.copy()
is StringLiteralValue -> orig.copy()
else -> return noModifications
}
return listOf(IAstModification.ReplaceNode(functionCall, copy, parent))
}
}
return noModifications
}
private fun scopePrefix(variable: IdentifierReference, subroutine: Subroutine): IdentifierReference {
val scoped = subroutine.makeScopedName(variable.nameInSource.last())
return IdentifierReference(scoped.split('.'), variable.position)
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
val functionName = functionCallStatement.target.nameInSource[0]
@ -102,19 +137,19 @@ internal class StatementOptimizer(private val program: Program,
return noModifications
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
if(constval!=null)
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
}
}
return noModifications
}
// override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// // if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
// val subroutine = functionCall.target.targetSubroutine(program)
// if(subroutine!=null) {
// val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
// if(first is Return && first.value!=null) {
// val constval = first.value?.constValue(program)
// if(constval!=null)
// return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
// }
// }
// return noModifications
// }
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements

View File

@ -1,96 +0,0 @@
package prog8.optimizer
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
internal class SubroutineInliner(private val program: Program, val errors: IErrorReporter, private val compilerOptions: CompilationOptions): AstWalker() {
private var callsToInlinedSubroutines = mutableListOf<Pair<IFunctionCall, Node>>()
fun fixCallsToInlinedSubroutines() {
for((call, parent) in callsToInlinedSubroutines) {
val sub = call.target.targetSubroutine(program)!!
val intermediateReturnValueVar = sub.statements.filterIsInstance<VarDecl>().singleOrNull { it.name.endsWith(retvarName) }
if(intermediateReturnValueVar!=null) {
val scope = parent.definingScope()
if(!scope.statements.filterIsInstance<VarDecl>().any { it.name==intermediateReturnValueVar.name}) {
val decl = intermediateReturnValueVar.copy()
scope.statements.add(0, decl)
decl.linkParents(scope as Node)
}
}
}
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
return if(compilerOptions.optimize && subroutine.inline && !subroutine.isAsmSubroutine)
annotateInlinedSubroutineIdentifiers(subroutine)
else
noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return after(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return after(functionCall as IFunctionCall, parent, functionCall.position)
}
private fun after(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
val sub = functionCall.target.targetSubroutine(program)
if(sub != null && compilerOptions.optimize && sub.inline && !sub.isAsmSubroutine)
callsToInlinedSubroutines.add(Pair(functionCall, parent))
return noModifications
}
private fun annotateInlinedSubroutineIdentifiers(sub: Subroutine): List<IAstModification> {
// this adds name prefixes to the identifiers used in the subroutine,
// so that the statements can be inlined (=copied) in the call site and still reference
// the correct symbols as seen from the scope of the subroutine.
class Annotator: AstWalker() {
var numReturns=0
override fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
val stmt = identifier.targetStatement(program)!!
if(stmt is BuiltinFunctionStatementPlaceholder)
return noModifications
val prefixed = stmt.makeScopedName(identifier.nameInSource.last()).split('.')
val withPrefix = IdentifierReference(prefixed, identifier.position)
return listOf(IAstModification.ReplaceNode(identifier, withPrefix, parent))
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
numReturns++
if(parent !== sub || sub.indexOfChild(returnStmt)<sub.statements.size-1)
errors.err("return statement must be the very last statement in the inlined subroutine", sub.position)
return noModifications
}
fun theModifications(): List<IAstModification> {
return this.modifications.map { it.first }.toList()
}
}
val annotator = Annotator()
sub.accept(annotator, sub.parent)
if(annotator.numReturns>1) {
errors.err("inlined subroutine can only have one return statement", sub.position)
return noModifications
}
return annotator.theModifications()
}
}

View File

@ -12,6 +12,14 @@ import prog8.parser.Prog8ANTLRParser
private data class NumericLiteral(val number: Number, val datatype: DataType)
// TODO [merge conflict]: not sure if this should be kept?? Is it double??
internal fun Prog8ANTLRParser.ModuleContext.toAst(name: String, source: Path, encoding: IStringEncoding) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
val directives = this.directive().map { it.toAst() }
val blocks = this.block().map { it.toAst(Module.isLibrary(source), encoding) }
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), source)
}
private fun ParserRuleContext.toPosition() : Position {
/*
val customTokensource = this.start.tokenSource as? CustomLexer

View File

@ -347,7 +347,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
this.addressExpression.linkParents(this)
}
override val isSimple = true
override val isSimple = addressExpression is NumericLiteralValue || addressExpression is IdentifierReference
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
@ -365,8 +365,6 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun toString(): String {
return "DirectMemoryRead($addressExpression)"
}
fun copy() = DirectMemoryRead(addressExpression, position)
}
class NumericLiteralValue(val type: DataType, // only numerical types allowed
@ -375,6 +373,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override lateinit var parent: Node
override val isSimple = true
fun copy() = NumericLiteralValue(type, number, position)
companion object {
fun fromBoolean(bool: Boolean, position: Position) =
@ -539,6 +538,7 @@ class StringLiteralValue(val value: String,
}
override val isSimple = true
fun copy() = StringLiteralValue(value, altEncoding, position)
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")

View File

@ -641,9 +641,13 @@ Subroutines can be defined in a Block, but also nested inside another subroutine
With ``asmsub`` you can define a low-level subroutine that is implemented in inline assembly and takes any parameters
in registers directly.
Trivial subroutines can be tagged as ``inline`` to tell the compiler to copy their code
Trivial ``asmsub`` routines can be tagged as ``inline`` to tell the compiler to copy their code
in-place to the locations where the subroutine is called, rather than inserting an actual call and return to the
subroutine. This may increase code size significantly and can only be used in limited scenarios, so YMMV.
Note that the routine's code is copied verbatim into the place of the subroutine call in this case,
so pay attention to any jumps and rts instructions in the inlined code!
At this time it is not yet possible to inline regular Prog8 subroutines, this may be added in the future.
Calling a subroutine

View File

@ -33,13 +33,6 @@ This makes it easier to understand and relate the generated code. Examples::
Directives
-----------
.. data:: %target <target>
Level: module.
Global setting, specifies that this module can only work for the given compiler target.
If compiled with a different target, compilation is aborted with an error message.
.. data:: %output <type>
Level: module.
@ -403,7 +396,7 @@ The following names are reserved, they have a special meaning::
Range expression
^^^^^^^^^^^^^^^^
A special value is the *range expression* which represents a range of numbers or characters,
A special value is the *range expression* which represents a range of integer numbers or characters,
from the starting value to (and including) the ending value::
<start> to <end> [ step <step> ]
@ -413,7 +406,7 @@ You an provide a step value if you need something else than the default incremen
in case of downto, a decrement of one). Because a step of minus one is so common you can just use
the downto variant to avoid having to specify the step as well.
If used in the place of a literal value, it expands into the actual array of values::
If used in the place of a literal value, it expands into the actual array of integer values::
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
@ -559,7 +552,7 @@ Subroutine definitions
The syntax is::
[inline] sub <identifier> ( [parameters] ) [ -> returntype ] {
sub <identifier> ( [parameters] ) [ -> returntype ] {
... statements ...
}
@ -572,9 +565,6 @@ The open curly brace must immediately follow the subroutine result specification
and can have nothing following it. The close curly brace must be on its own line as well.
The parameters is a (possibly empty) comma separated list of "<datatype> <parametername>" pairs specifying the input parameters.
The return type has to be specified if the subroutine returns a value.
The ``inline`` keyword makes their code copied in-place to the locations where the subroutine is called,
rather than having an actual call and return to the subroutine. This is meant for very small subroutines only
as it can increase code size significantly.
Assembly / ROM subroutines

View File

@ -1,4 +1,3 @@
%target c64
%import syslib
%import textio
%import test_stack

View File

@ -1,4 +1,3 @@
%target c64
%import syslib
%import textio
%zeropage basicsafe

View File

@ -1,4 +1,3 @@
%target c64
%import textio
%import syslib

View File

@ -1,4 +1,3 @@
%target c64
%import syslib
%import textio
%zeropage basicsafe

View File

@ -1,4 +1,3 @@
%target c64
%import syslib
%import textio
%import test_stack

View File

@ -1,4 +1,3 @@
%target c64
%import syslib
%import test_stack
%import textio

View File

@ -1,4 +1,3 @@
%target cx16
%import textio
%import palette
%import string

View File

@ -1,4 +1,3 @@
%target cx16
%import palette
%import conv
%import textio

View File

@ -1,4 +1,3 @@
%target cx16
%import syslib
%import test_stack
%import conv

View File

@ -1,27 +1,24 @@
%import gfx2
%import palette
%import textio
; TODO WORK IN PROGRESS...
; want to make Amiga 'copper' bars color cycling effects
; Amiga 'copper' bars color cycling effect
main {
sub start() {
palette.set_all_black()
gfx2.screen_mode(4)
; make palette color 1 black so we can print black letters over the background color 0
void cx16.screen_set_mode(0)
cx16.vpoke(1, $fa02, $0)
cx16.vpoke(1, $fa03, $0)
txt.color(1)
txt.plot(13,12)
txt.print("amiga-inspired")
txt.plot(10,14)
txt.print("raster blinds effect")
txt.plot(12,16)
txt.print("random gradients")
ubyte yy
for yy in 0 to 239
gfx2.horizontal_line(0, yy, 320, yy & 63)
repeat {
colors.random_bar()
colors.set_palette()
repeat 20
sys.waitvsync()
}
irq.make_new_gradient()
cx16.set_rasterirq(&irq.irqhandler, irq.top_scanline)
repeat {
}
@ -29,99 +26,165 @@ main {
}
irq {
const ubyte top_scanline = 0
ubyte blinds_start_ix = 0
ubyte color_ix = 0
uword next_irq_line = top_scanline
ubyte shift_counter = 0
ubyte[32+32+16] blinds_lines_reds
ubyte[32+32+16] blinds_lines_greens
ubyte[32+32+16] blinds_lines_blues
sub irqhandler() {
set_scanline_color(color_ix)
color_ix++
next_irq_line += 2 ; code needs 2 scanlines per color transition
if next_irq_line == 480 {
; start over at top
next_irq_line = top_scanline
blinds_start_ix = 0
color_ix = 0
shift_counter++
if shift_counter == 32+32+32 {
make_new_gradient()
shift_counter = 0
} else if shift_counter & 1 {
shift_gradient()
}
} else if next_irq_line & 15 == 0 {
; start next blinds
blinds_start_ix++
color_ix = blinds_start_ix
}
cx16.set_rasterline(next_irq_line)
}
sub make_new_gradient() {
colors.random_half_bar()
colors.mirror_bar()
sys.memcopy(colors.reds, &blinds_lines_reds+32+16, len(colors.reds))
sys.memcopy(colors.greens, &blinds_lines_greens+32+16, len(colors.greens))
sys.memcopy(colors.blues, &blinds_lines_blues+32+16, len(colors.blues))
}
sub shift_gradient() {
sys.memcopy(&blinds_lines_reds+1, blinds_lines_reds, len(blinds_lines_reds)-1)
sys.memcopy(&blinds_lines_greens+1, blinds_lines_greens, len(blinds_lines_greens)-1)
sys.memcopy(&blinds_lines_blues+1, blinds_lines_blues, len(blinds_lines_blues)-1)
}
asmsub set_scanline_color(ubyte color_ix @Y) {
; uword color = mkword(reds[ix], (greens[ix] << 4) | blues[ix] )
%asm {{
lda blinds_lines_reds,y
pha
lda blinds_lines_greens,y
asl a
asl a
asl a
asl a
ora blinds_lines_blues,y
tay
stz cx16.VERA_CTRL
lda #%00010001
sta cx16.VERA_ADDR_H
lda #$fa
sta cx16.VERA_ADDR_M
; lda #$02
; sta cx16.VERA_ADDR_L
stz cx16.VERA_ADDR_L
sty cx16.VERA_DATA0 ; gb
pla
sta cx16.VERA_DATA0 ; r
stz cx16.VERA_ADDR_H
rts
}}
}
}
colors {
ubyte cr
ubyte cg
ubyte cb
ubyte[48+16] reds
ubyte[48+16] greens
ubyte[48+16] blues
ubyte bar_size
ubyte target_red
ubyte target_green
ubyte target_blue
ubyte[32] reds
ubyte[32] greens
ubyte[32] blues
sub random_rgb12() {
do {
uword rr = rndw()
cr = msb(rr) & 15
cg = lsb(rr)
cb = cg & 15
cg >>= 4
} until cr+cg+cb >= 12
target_red = msb(rr) & 15
target_green = lsb(rr)
target_blue = target_green & 15
target_green >>= 4
} until target_red+target_green+target_blue >= 12
}
sub random_bar() {
sub mirror_bar() {
; mirror the top half bar into the bottom half
ubyte ix=14
ubyte mix=16
do {
reds[mix] = reds[ix]
greens[mix] = greens[ix]
blues[mix] = blues[ix]
mix++
ix--
} until ix==255
reds[mix] = 0
greens[mix] = 0
blues[mix] = 0
}
sub random_half_bar() {
; fade black -> color then fade color -> white
; gradient calculations in 8.8 bits fixed-point
; could theoretically be 4.12 bits for even more fractional accuracy
random_rgb12()
ubyte r=0
ubyte g=0
ubyte b=0
ubyte different
bar_size = 0
uword r = $000
uword g = $000
uword b = $000
uword dr = target_red
uword dg = target_green
uword db = target_blue
ubyte ix = 1
repeat {
different = false
if r != cr {
different = true
r++
}
if g != cg {
different = true
g++
}
if b != cb {
different = true
b++
}
if not different
break
reds[bar_size] = r
greens[bar_size] = g
blues[bar_size] = b
bar_size++
}
repeat {
different = false
if r != 15 {
different = true
r++
}
if g != 15 {
different = true
g++
}
if b != 15 {
different = true
b++
}
if not different
break
reds[bar_size] = r
greens[bar_size] = g
blues[bar_size] = b
bar_size++
}
; mirror bottom half from top half
ubyte mi = bar_size-1
repeat mi {
reds[bar_size] = reds[mi]
greens[bar_size] = greens[mi]
blues[bar_size] = blues[mi]
bar_size++
mi--
}
; make rest of bar black (bars are not always the same length using the simplistic algorithm above...)
while bar_size != 48+16 {
reds[bar_size] = $0
greens[bar_size] = $0
blues[bar_size] = $0
bar_size++
}
}
; gradient from black to halfway color
reds[0] = 0
greens[0] = 0
blues[0] = 0
dr <<= 5
dg <<= 5
db <<= 5
continue_gradient()
sub set_palette() {
ubyte ix
for ix in 0 to 48+15 {
uword color = mkword(reds[ix], (greens[ix] << 4) | blues[ix] )
palette.set_color(ix, color)
; gradient from halfway color to white
dr = (($f00 - r) >> 3) - 1
dg = (($f00 - g) >> 3) - 1
db = (($f00 - b) >> 3) - 1
continue_gradient()
return
sub continue_gradient() {
repeat 8 {
reds[ix] = msb(r)
greens[ix] = msb(g)
blues[ix] = msb(b)
r += dr
g += dg
b += db
ix++
}
}
}
}

View File

@ -1,4 +1,3 @@
%target cx16
%import textio
main {

View File

@ -1,6 +1,5 @@
; CommanderX16 text datetime example!
%target cx16
%import textio
%zeropage basicsafe

View File

@ -1,4 +1,3 @@
%target cx16
%import gfx2
%import floats
%import textio

View File

@ -1,4 +1,3 @@
%target cx16
%import palette
%option no_sysinit

View File

@ -1,4 +1,3 @@
%target cx16
%import textio
%import floats
%zeropage basicsafe

View File

@ -1,4 +1,3 @@
%target cx16
%import textio
%import floats
%zeropage basicsafe

View File

@ -1,4 +1,3 @@
%target cx16
%import palette
%import gfx2
%option no_sysinit

View File

@ -1,4 +1,3 @@
%target cx16
%import textio
%import palette

View File

@ -8,7 +8,6 @@
; simplistic sound effects (Vera PSG)
%target cx16
%import syslib
%import textio
%import test_stack

View File

@ -1,4 +1,3 @@
%target cx16
%import gfx2
%import textio
%import test_stack

View File

@ -1,4 +1,3 @@
%target cx16
%import textio
%option no_sysinit
%zeropage basicsafe

View File

@ -1,4 +1,3 @@
%target c64
%import syslib
%import test_stack
%import textio

View File

@ -1,4 +1,3 @@
%target c64
%import syslib
main {

View File

@ -1,4 +1,3 @@
%target c64
%import textio
%import syslib
%zeropage basicsafe

View File

@ -8,7 +8,6 @@
; some simple sound effects
%target c64
%import syslib
%import textio
%import test_stack

View File

@ -1,4 +1,3 @@
%target c64
%import floats
%import graphics
%import test_stack

View File

@ -1,4 +1,3 @@
%target c64
%import syslib
%zeropage basicsafe

View File

@ -8,7 +8,7 @@ NOTES:
*/
// -> java classes Prog8ANTLRParser and Prog8ANTLRLexer,
// -> java classes Prog8ANTLRParser and Prog8ANTLRLexer,
// both NOT to be used from Kotlin code, but ONLY through Kotlin class Prog8Parser
grammar Prog8ANTLR;
@ -137,7 +137,7 @@ unconditionaljump : 'goto' (integerliteral | scoped_identifier) ;
directive :
directivename=('%output' | '%launcher' | '%zeropage' | '%zpreserved' | '%address' | '%import' |
'%breakpoint' | '%asminclude' | '%asmbinary' | '%option' | '%target' )
'%breakpoint' | '%asminclude' | '%asmbinary' | '%option' )
(directivearg? | directivearg (',' directivearg)*)
;

View File

@ -12,7 +12,7 @@
<option name="HAS_STRING_ESCAPES" value="true" />
</options>
<keywords keywords="&amp;;-&gt;;@;\$;and;as;asmsub;break;clobbers;do;downto;else;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;or;repeat;return;romsub;step;sub;to;true;until;when;while;xor;~" ignore_case="false" />
<keywords2 keywords="%address;%asm;%asmbinary;%asminclude;%breakpoint;%import;%launcher;%option;%output;%target;%zeropage;%zpreserved" />
<keywords2 keywords="%address;%asm;%asmbinary;%asminclude;%breakpoint;%import;%launcher;%option;%output;%zeropage;%zpreserved" />
<keywords3 keywords="byte;const;float;shared;str;ubyte;uword;void;word;zp" />
<keywords4 keywords="abs;acos;all;any;asin;atan;avg;callfar;callrom;ceil;cmp;cos;cos16;cos16u;cos8;cos8u;deg;floor;len;ln;log2;lsb;lsl;lsr;max;memory;min;mkword;msb;peek;peekw;poke;pokew;rad;reverse;rnd;rndf;rndw;rol;rol2;ror;ror2;round;sgn;sin;sin16;sin16u;sin8;sin8u;sizeof;sort;sqrt;sqrt16;sum;swap;tan" />
</highlighting>
@ -20,4 +20,4 @@
<mapping ext="p8" />
<mapping ext="prog8" />
</extensionMap>
</filetype>
</filetype>

View File

@ -25,7 +25,7 @@
<Keywords name="Folders in comment, middle"></Keywords>
<Keywords name="Folders in comment, close"></Keywords>
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte&#x000D;&#x000A;word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%import&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%target&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%import&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved</Keywords>
<Keywords name="Keywords3">inline sub asmsub romsub&#x000D;&#x000A;clobbers&#x000D;&#x000A;asm&#x000D;&#x000A;if&#x000D;&#x000A;when else&#x000D;&#x000A;if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z&#x000D;&#x000A;for in step do while repeat&#x000D;&#x000A;break return goto</Keywords>
<Keywords name="Keywords4">abs acos all any asin atan avg callfar callrom ceil cmp cos cos16 cos16u cos8 cos8u deg floor len ln log2 lsb lsl lsr max memory min mkword msb peek peekw poke pokew rad reverse rnd rndf rndw rol rol2 ror ror2 round sgn sin sin16 sin16u sin8 sin8u sizeof sort sqrt sqrt16 sum swap tan&#x000D;&#x000A;</Keywords>
<Keywords name="Keywords5">true false&#x000D;&#x000A;not and or xor&#x000D;&#x000A;as to downto</Keywords>

View File

@ -1,4 +1,3 @@
%target cx16
%import textio
%zeropage basicsafe
%option no_sysinit

View File

@ -33,7 +33,7 @@ syn keyword prog8Repeat for while in do until repeat
syn match prog8Label "\<\w\+\>:"
syn keyword prog8Operator and or to downto as void
syn match prog8Directive "\(^\|\s\)%\(target\|output\|launcher\|zeropage\)\>"
syn match prog8Directive "\(^\|\s\)%\(output\|launcher\|zeropage\)\>"
syn match prog8Directive "\(^\|\s\)%\(zpreserved\|address\|import\|option\)\>"
syn match prog8Directive "\(^\|\s\)%\(asmbinary\|asminclude\|breakpoint\)\>"
syn match prog8Directive "\(^\|\s\)%asm\>"