Merge branch 'master' into next_compositetypes

# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
#	compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt
#	compiler/test/TestSubroutines.kt
This commit is contained in:
Irmen de Jong 2024-10-12 13:22:51 +02:00
commit ba6db8f905
29 changed files with 213 additions and 490 deletions

View File

@ -54,27 +54,23 @@ What does Prog8 provide?
------------------------
- all advantages of a higher level language over having to write assembly code manually
- programs run very fast because compilation to native machine code
- programs run very fast because it's compiled to native machine code
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
- modularity, symbol scoping, subroutines
- modularity, symbol scoping, subroutines. No need for forward declarations.
- various data types other than just bytes (16-bit words, floats, strings)
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
- floating point math is supported on certain targets
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- subroutines with input parameters and result values
- high-level program optimizations
- no need for forward declarations
- small program boilerplate/compilersupport overhead
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``in`` expression for concise and efficient multi-value/containment check
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
- encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16)
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
- encode strings and characters into petscii or screencodes or even other encodings
*Rapid edit-compile-run-debug cycle:*

View File

@ -103,7 +103,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
is PtIdentifier -> parent.children[index] = node.prefix(parent, st)
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
is PtJump -> parent.children[index] = node.prefix(parent, st)
is PtVariable -> parent.children[index] = node.prefix(st)
is PtVariable -> parent.children[index] = node.prefix(parent, st)
else -> throw AssemblyError("weird node to prefix $node")
}
}
@ -134,7 +134,7 @@ private fun prefixScopedName(name: String, type: Char): String {
return prefixed.joinToString(".")
}
private fun PtVariable.prefix(st: SymbolTable): PtVariable {
private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
name = prefixScopedName(name, 'v')
if(value==null)
return this
@ -162,7 +162,9 @@ private fun PtVariable.prefix(st: SymbolTable): PtVariable {
else -> throw AssemblyError("weird array value element $elt")
}
}
PtVariable(name, type, zeropage, newValue, arraySize, position)
val result = PtVariable(name, type, zeropage, newValue, arraySize, position)
result.parent = parent
result
}
else this
}

View File

@ -393,11 +393,35 @@ internal class ConstantIdentifierReplacer(
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// convert a range expression that is assigned to an array, to an array literal instead.
val range = assignment.value as? RangeExpression
if(range!=null) {
val targetDatatype = assignment.target.inferType(program)
if(targetDatatype.isArray) {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, targetDatatype.getOrUndef(),
ZeropageWish.DONTCARE, null, "dummy", emptyList(),
assignment.value, false, false, Position.DUMMY)
val replaceValue = createConstArrayInitializerValue(decl)
if(replaceValue!=null) {
return listOf(IAstModification.ReplaceNode(assignment.value, replaceValue, assignment))
}
}
}
return noModifications
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
if(decl.type==VarDeclType.MEMORY)
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
val rangeSize=(decl.value as? RangeExpression)?.size()
if(rangeSize!=null && rangeSize>65535) {
errors.err("range size overflow", decl.value!!.position)
return null
}
// convert the initializer range expression from a range or int, to an actual array.
// this is to allow initialization of arrays with a single value like ubyte[10] array = 42
val dt = decl.datatype

View File

@ -757,6 +757,11 @@ internal_vload:
}}
}
; note: There is no vsave_raw() routine because the Kernal doesn't have a VSAVE routine.
; You'll have to write your own loop that reads vram data and use
; cbm.CHROUT or cx16.MCIOUT to write it to an open output file.
sub chdir(str path) {
; -- change current directory.
list_filename[0] = 'c'

View File

@ -16,7 +16,7 @@ import kotlin.io.path.Path
import kotlin.math.floor
/**
* Semantic analysis.
* Semantic analysis and error reporting.
*/
internal class AstChecker(private val program: Program,
private val errors: IErrorReporter,
@ -666,7 +666,7 @@ internal class AstChecker(private val program: Program,
if (assignment.value !is BinaryExpression && assignment.value !is PrefixExpression && assignment.value !is ContainmentCheck)
errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.getOrUndef(),
checkAssignmentCompatible(assignTarget, targetDatatype.getOrUndef(),
sourceDatatype.getOrUndef(), assignment.value)
}
}
@ -697,6 +697,7 @@ internal class AstChecker(private val program: Program,
}
fun err(msg: String) = errors.err(msg, decl.position)
fun valueerr(msg: String) = errors.err(msg, decl.value?.position ?: decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true)
@ -717,11 +718,11 @@ internal class AstChecker(private val program: Program,
if(decl.type== VarDeclType.MEMORY)
err("memory mapped array must have a size specification")
if(decl.value==null) {
err("array variable is missing a size specification or an initialization value")
valueerr("array variable is missing a size specification or an initialization value")
return
}
if(decl.value is NumericLiteral) {
err("unsized array declaration cannot use a single literal initialization value")
valueerr("unsized array declaration cannot use a single literal initialization value")
return
}
if(decl.value is RangeExpression)
@ -747,7 +748,7 @@ internal class AstChecker(private val program: Program,
}
else -> {
if(decl.type==VarDeclType.CONST) {
err("const declaration needs a compile-time constant initializer value")
valueerr("const declaration needs a compile-time constant initializer value")
super.visit(decl)
return
}
@ -778,10 +779,10 @@ internal class AstChecker(private val program: Program,
val numvalue = decl.value as? NumericLiteral
if(numvalue!=null) {
if (!numvalue.type.isInteger || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
err("memory address must be valid integer 0..\$ffff")
valueerr("memory address must be valid integer 0..\$ffff")
}
} else {
err("value of memory mapped variable can only be a constant, maybe use an address pointer type instead?")
valueerr("value of memory mapped variable can only be a constant, maybe use an address pointer type instead?")
}
}
}
@ -793,10 +794,10 @@ internal class AstChecker(private val program: Program,
if(decl.isArray) {
val eltDt = decl.datatype.elementType()
if(!(iDt istype eltDt))
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
valueerr("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
} else {
if(!(iDt.isBool && decl.datatype.isUnsignedByte || iDt issimpletype BaseDataType.UBYTE && decl.datatype.isBool))
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
valueerr("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
}
}
}
@ -845,7 +846,7 @@ internal class AstChecker(private val program: Program,
if(decl.type==VarDeclType.MEMORY)
err("strings can't be memory mapped")
else
err("string var must be initialized with a string literal")
valueerr("string var must be initialized with a string literal")
}
}
@ -1008,9 +1009,11 @@ internal class AstChecker(private val program: Program,
}
if(array.parent is Assignment) {
val arraydt = array.inferType(program)
val assignTarget = (array.parent as Assignment).target
if(!assignTarget.inferType(program).isArray)
errors.err("cannot assign array to a non-array variable", assignTarget.position)
val targetDt = assignTarget.inferType(program)
if(arraydt!=targetDt)
errors.err("value has incompatible type ($arraydt) for the variable ($targetDt)", array.position)
}
super.visit(array)
}
@ -1624,7 +1627,7 @@ internal class AstChecker(private val program: Program,
return err("boolean array length must be 1-256")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
}
return err("invalid boolean array size, must be 1-256")
@ -1643,7 +1646,7 @@ internal class AstChecker(private val program: Program,
return err("byte array length must be 1-256")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
}
return err("invalid byte array size, must be 1-256")
@ -1663,7 +1666,7 @@ internal class AstChecker(private val program: Program,
return err("array length must be 1-$maxLength")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
}
return err("invalid array size, must be 1-$maxLength")
@ -1682,7 +1685,7 @@ internal class AstChecker(private val program: Program,
return err("float array length must be 1-51")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
} else
return err("invalid float array size, must be 1-51")
@ -1751,7 +1754,7 @@ internal class AstChecker(private val program: Program,
return true
}
private fun checkArrayValues(value: ArrayLiteral, type: DataType): Boolean {
private fun checkArrayValues(value: ArrayLiteral, targetDt: DataType): Boolean {
val array = value.value.map {
when (it) {
is NumericLiteral -> it.number.toInt()
@ -1763,41 +1766,52 @@ internal class AstChecker(private val program: Program,
if(cast==null || !cast.isValid)
-9999999
else
cast.valueOrZero().number.toInt()
cast.valueOrZero().number
}
else -> -9999999
}
}
val correct: Boolean
when {
type.isUnsignedByteArray -> {
targetDt.isUnsignedByteArray -> {
correct = array.all { it in 0..255 }
}
type.isSignedByteArray -> {
targetDt.isSignedByteArray -> {
correct = array.all { it in -128..127 }
}
type.isUnsignedWordArray || type.isSplitUnsignedWordArray -> {
targetDt.isUnsignedWordArray || targetDt.isSplitUnsignedWordArray -> {
correct = array.all { (it in 0..65535) }
}
type.isSignedWordArray || type.isSplitSignedWordArray -> {
targetDt.isSignedWordArray || targetDt.isSplitSignedWordArray -> {
correct = array.all { it in -32768..32767 }
}
type.isBoolArray -> {
targetDt.isBoolArray -> {
correct = array.all { it==0 || it==1 }
}
type.isFloatArray -> correct = true
else -> throw FatalAstException("invalid array type $type")
targetDt.isFloatArray -> correct = true
else -> throw FatalAstException("invalid type $targetDt")
}
if (!correct)
errors.err("array value out of range for type $type", value.position)
errors.err("array element out of range for type $targetDt", value.position)
return correct
}
private fun checkAssignmentCompatible(targetDatatype: DataType,
private fun checkAssignmentCompatible(target: AssignTarget,
targetDatatype: DataType,
sourceDatatype: DataType,
sourceValue: Expression) : Boolean {
val position = sourceValue.position
if(sourceValue is ArrayLiteral && targetDatatype.isArray) {
val vardecl=target.identifier?.targetVarDecl(program)
val targetSize = vardecl?.arraysize?.constIndex()
if(targetSize!=null) {
if(sourceValue.value.size != targetSize) {
errors.err("array size mismatch (expecting $targetSize, got ${sourceValue.value.size})", sourceValue.position)
}
}
}
if(sourceValue is RangeExpression) {
errors.err("can't assign a range value to something else", position)
return false
@ -1817,8 +1831,12 @@ internal class AstChecker(private val program: Program,
targetDatatype.isFloat -> sourceDatatype.isNumeric
targetDatatype.isString -> sourceDatatype.isString
else -> {
errors.err("cannot assign new value to variable of type $targetDatatype", position)
false
if(targetDatatype.isArray && sourceValue is ArrayLiteral)
true // assigning array literal to an array variable is allowed, size and type are checked elsewhere
else {
errors.err("cannot assign this value to variable of type $targetDatatype", position)
false
}
}
}
@ -1839,6 +1857,9 @@ internal class AstChecker(private val program: Program,
else if(targetDatatype.isUnsignedWord && sourceDatatype.isPassByRef) {
// this is allowed: a pass-by-reference datatype into a uword (pointer value).
}
else if(sourceDatatype.isArray && targetDatatype.isArray) {
// this is allowed (assigning array to array)
}
else if(sourceDatatype.isBool && !targetDatatype.isBool) {
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}

View File

@ -258,18 +258,17 @@ internal class StatementReorderer(
if(assign.value is ArrayLiteral) {
return // invalid assignment of literals will be reported elsewhere
}
if(assign.value !is IdentifierReference) {
errors.err("invalid array value to assign to other array", assign.value.position)
return
return // invalid assignment value will be reported elsewhere
}
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program)!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
} else {
if (sourceVar.arraysize!!.constIndex() != targetVar.arraysize!!.constIndex())
errors.err("element count mismatch", assign.position)
errors.err("array size mismatch (expecting ${targetVar.arraysize!!.constIndex()}, got ${sourceVar.arraysize!!.constIndex()})", assign.value.position)
val sourceEltDt = sourceVar.datatype.elementType()
val targetEltDt = targetVar.datatype.elementType()
if (!sourceEltDt.equalsSize(targetEltDt)) {

View File

@ -135,8 +135,9 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
if(isMultiComparisonRecurse(leftBinExpr1)) {
val elementType = needle.inferType(program).getOrElse { throw FatalAstException("invalid needle dt") }
require(elementType.isNumericOrBool)
val elementIType = needle.inferType(program)
if(elementIType.isUnknown) return noModifications
val elementType = elementIType.getOrUndef()
if(values.size==2 || values.size==3 && (elementType.isUnsignedByte || elementType.isUnsignedWord)) {
val numbers = values.map{it.number}.toSet()
if(numbers == setOf(0.0, 1.0)) {

View File

@ -127,7 +127,6 @@ class TestCompilerOnExamplesCx16: FunSpec({
"cxlogo",
"diskspeed",
"fileseek",
"highresbitmap",
"kefrenbars",
"keyboardhandler",
"life",
@ -170,8 +169,6 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"dirlist",
"fibonacci",
"fractal-tree",
"line-circle-gfx",
"line-circle-txt",
"maze",
"mandelbrot",
"mandelbrot-gfx",
@ -179,7 +176,6 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"primes",
"queens",
"screencodes",
"sincos",
"swirl",
"swirl-float",
"tehtriz",

View File

@ -34,7 +34,7 @@ class TestSubroutines: FunSpec({
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "type mismatch, was: str expected: ubyte"
errors.errors[1] shouldContain "initialisation value has incompatible type"
errors.errors[1] shouldContain "value has incompatible type"
}
test("stringParameter") {

View File

@ -4,6 +4,7 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.code.ast.PtBuiltinFunctionCall
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
@ -327,5 +328,80 @@ main {
errors.errors[1] shouldContain "out of bounds"
errors.errors[2] shouldContain "out of bounds"
}
test("array assignments should check for number of elements and element type correctness") {
val src="""
%option enable_floats
main {
sub start() {
ubyte[] array = 1 to 4
ubyte[] array2 = [1,2,3,4]
str[] names = ["apple", "banana", "tomato"]
array = [10,11,12,13] ; ok!
array = 20 to 23 ; ok!
names = ["x1", "x2", "x3"] ; ok!
ubyte[] array3 = [1,2,3,4000] ; error: element type
array = 10 to 15 ; error: array size
array = 1000 to 1003 ; error: element type
names = ["x1", "x2", "x3", "x4"] ; error: array size
names = [1.1, 2.2, 3.3, 4.4] ; error: array size AND element type
names = [1.1, 2.2, 999999.9] ; error: element type
names = [1.1, 2.2, 9.9] ; error: element type
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = true, errors = errors) shouldBe null
errors.errors.size shouldBe 8
errors.errors[0] shouldContain "incompatible type"
errors.errors[1] shouldContain "array size mismatch"
errors.errors[2] shouldContain "array element out of range"
errors.errors[3] shouldContain "array size mismatch"
errors.errors[4] shouldContain "array size mismatch"
errors.errors[5] shouldContain "value has incompatible type"
errors.errors[6] shouldContain "value has incompatible type"
errors.errors[7] shouldContain "value has incompatible type"
}
test("array assignments should work via array copy call") {
val src="""
%option enable_floats
main {
sub start() {
ubyte[] array = [1,2,3]
ubyte[3] array2
float[] flarray = [1.1, 2.2, 3.3]
float[3] flarray2
word[] warray = [-2222,42,3333]
word[3] warray2
str[] names = ["apple", "banana", "tomato"]
str[3] names2
; 8 array assignments -> 8 arraycopies:
array = [8,7,6]
array = array2
flarray = [99.9, 88.8, 77.7]
flarray = flarray2
warray = [4444,5555,6666]
warray = warray2
names = ["x1", "x2", "x3"]
names = names2
}
}"""
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
val x = result.codegenAst!!.entrypoint()!!
(x.children[12] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[13] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[14] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[15] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[16] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[17] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[18] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[19] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
}
})

View File

@ -110,8 +110,8 @@ class TestVariables: FunSpec({
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "initialisation value has incompatible type"
errors.errors[1] shouldContain "initialisation value has incompatible type"
errors.errors[0] shouldContain "value has incompatible type"
errors.errors[1] shouldContain "value has incompatible type"
}
test("initialization of boolean array with single value") {

View File

@ -34,6 +34,8 @@ Data types
You'll have to add explicit casts to increase the size of the value if required.
For example when adding two byte variables having values 100 and 200, the result won't be 300, because that doesn't fit in a byte. It will be 44.
You'll have to cast one or both of the *operands* to a word type first if you want to accomodate the actual result value of 300.
- Arrays and strings have a limited size and the allocated size never changes
- Arrays and strings are mutable
Variables
@ -85,10 +87,10 @@ Foreign function interface (external/ROM calls)
Optimizations
-------------
- Prog8 contains many compiler optimizations to generate efficent code, but also lacks many optimizations that modern compilers do have.
- Prog8 contains many compiler optimizations to generate efficient code, but also lacks many optimizations that modern compilers do have.
While empirical evidence shows that Prog8 generates more efficent code than some C compilers that also target the same 8 bit systems,
it still is limited in how sophisticated the optimizations are that it performs on your code.
- For time critical code, it may be worth it to inspect the generated assembly code to see if you could write things differently
the optimizations it makes on your code aren't super sophisticated.
- For time critical code, it may be worth it to inspect the generated assembly code to see if you can write things differently
to help the compiler generate more efficient code (or even replace it with hand written inline assembly altogether).
For example, if you repeat an expression multiple times it will be evaluated every time, so maybe you should store it
in a variable instead and reuse that variable::

View File

@ -88,30 +88,28 @@ Features
- provides a convenient and fast edit/compile/run cycle by being able to directly launch
the compiled program in an emulator and provide debugging information to this emulator.
- the language looks like a mix of Python and C so should be quite easy to learn
- Modular programming, scoping via modules, code blocks, and subroutines. No need for forward declarations.
- Provide high level programming constructs but at the same time stay close to the metal;
- Modular programming, scoping via module source files, code blocks, and subroutines. No need for forward declarations.
- Provides high level programming constructs but at the same time stay close to the metal;
still able to directly use memory addresses and ROM subroutines,
and inline assembly to have full control when every register, cycle or byte matters
- Subroutines with parameters and return values of various types
- Complex nested expressions are possible
- Variables are all allocated statically, no memory allocator overhead
- Variables are all allocated statically, no memory allocation overhead
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
- ``when`` statement to avoid if-else chains
- ``in`` expression for concise and efficient multi-value/containment test
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Various powerful built-in libraries to do I/O, number conversions, graphics and more
- Floating point math is supported on select compiler targets.
- Floating point math is supported on certain compiler targets.
- Easy and highly efficient integration with external subroutines and ROM routines on the target systems.
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
- Encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16)
- Identifiers can contain Unicode Letters, so ``knäckebröd``, ``приблизительно``, ``見せしめ`` and ``π`` are all valid identifiers.
- Advanced code optimizations to make the resulting program smaller and faster
- Programs can be restarted after exiting (i.e. run them multiple times without having to reload everything), due to automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 to R15 as defined on the Commander X16. These are also available on the other compilation targets!
- Supports the sixteen 'virtual' 16-bit registers R0 to R15 as defined on the Commander X16. You can look at them as general purpose global variables. These are also available on the other compilation targets!
- On the Commander X16: Support for low level system features such as Vera Fx, which includes 16x16 bits multiplication in hardware and fast memory copy and fill.
- Many library routines are available across compiler targets. This means that as long as you only use standard Kernal
and core prog8 library routines, it is sometimes possible to compile the *exact same program* for different machines (just change the compilation target flag).
and core prog8 library routines, it is sometimes possible to compile the *exact same program* for different machines by just changing the compilation target flag.
Code example

View File

@ -312,9 +312,9 @@ Note that the various keywords for the data type and variable type (``byte``, ``
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance.
It's possible to assign an array to another array; this will overwrite all elements in the target
array with those in the source array. The number and types of elements have to match for this to work!
For large arrays this is a slow operation because every element is copied over. It should probably be avoided.
It is possible to assign an array (variable or array literal) to another array; this will overwrite all elements in the target
array with those in the source array. The number of elements in the arrays and the data types have to match.
For large arrays this is a slow operation because all values are copied over.
Using the ``in`` operator you can easily check if a value is present in an array,
example: ``if choice in [1,2,3,4] {....}``

View File

@ -10,6 +10,11 @@ Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEva
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- remove support for array variable initialization with a single value, just require explicitly creating the value array [42] * 10 (which is what the compiler now does for you implicitly)
- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does.
- should we add a cleararray builtin function that can efficiently set every element in the array to the given value
- improve detection that a variable is not read before being written so that initializing it to zero can be omitted (only happens now if a vardecl is immediately followed by a for loop for instance)
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
- Can we support signed % (remainder) somehow?
- Don't add "random" rts to %asm blocks but instead give a warning about it? (but this breaks existing behavior that others already depend on... command line switch? block directive?)

View File

@ -1,7 +1,6 @@
%import syslib
%import textio
%import math
%import test_stack
spritedata $2000 {
@ -97,8 +96,6 @@ main {
txt.print(" jiffies/fr = ")
txt.print_ub(60/cbm.TIME_LO)
txt.print(" fps")
; test_stack.test()
}
}

View File

@ -1,5 +1,4 @@
%import syslib
%import test_stack
%import textio
%import math
@ -35,8 +34,6 @@ main {
txt.print_ub(60/cbm.TIME_LO)
txt.print(" fps")
cbm.TIME_LO=0
; test_stack.test()
}
}

View File

@ -11,7 +11,6 @@
%import syslib
%import textio
%import math
%import test_stack
main {
@ -68,9 +67,6 @@ waitkey:
}
drawScore()
; txt.plot(0,0)
; test_stack.test()
}
ubyte key

View File

@ -1,6 +1,5 @@
%import floats
%import textio
%import test_stack
%zeropage basicsafe
; Note: this program can be compiled for multiple target systems.
@ -37,8 +36,6 @@ main {
txt.print(" jiffies/fr = ")
txt.print_ub(60/jiffies)
txt.print(" fps")
;test_stack.test()
}
}

View File

@ -34,8 +34,6 @@ main {
anglex += 317
angley -= 505
anglez += 452
; test_stack.test()
}
}

View File

@ -1,85 +0,0 @@
%import monogfx
%import floats
%import textio
%import math
%zeropage dontuse
main {
sub start () {
monogfx.text_charset(3)
test_monochrome()
monogfx.textmode()
txt.print("done!\n")
}
sub test_monochrome() {
monogfx.hires()
uword yy
uword xx
word ww
yy = 20
xx = 20
monogfx.drawmode(monogfx.MODE_NORMAL)
monogfx.rect(xx, yy, 250, 80, true)
monogfx.drawmode(monogfx.MODE_STIPPLE)
monogfx.fillrect(xx+2, yy+2, 250-4, 80-4, true)
monogfx.drawmode(monogfx.MODE_NORMAL)
monogfx.fillrect(xx+20, yy+20, 200, 30, true)
monogfx.rect(xx+21, yy+21, 200-2, 30-2, false)
monogfx.text(xx+30, yy+32, false, sc:"High Res Bitmap Example")
; monogfx.drawmode(monogfx.MODE_STIPPLE)
monogfx.horizontal_line(10, 240, 620, true)
monogfx.vertical_line(320, 10, 460, true)
monogfx.text(320, 242, true, sc:"0,0")
monogfx.text(322, 10, true, sc:"Y-axis")
monogfx.text(590, 242, true, sc:"X-axis")
for ww in -10 to 10 {
xx = (ww*30) + 320 as uword
monogfx.vertical_line(xx, 239, 3, true)
}
for ww in -7 to 7 {
yy = (ww*30) + 240 as uword
monogfx.horizontal_line(319, yy, 3, true)
}
monogfx.drawmode(monogfx.MODE_NORMAL)
float y_f
for ww in -600 to 600 {
y_f = floats.sin(ww as float / 60.0)*150
monogfx.plot(ww/2 + 320 as uword, (y_f + 240) as uword, true)
}
monogfx.text(480, 100, true, sc:"sin(x)")
for ww in -300 to 300 {
y_f = floats.cos(ww as float/30.0)*60 - (ww as float)/1.7
monogfx.plot(ww + 320 as uword, (y_f + 240) as uword, true)
}
monogfx.text(80, 420, true, sc:"cos(x)+x")
sys.wait(3*60)
monogfx.circle(320, 240, 220, true)
monogfx.circle(320, 240, 210, true)
monogfx.circle(320, 240, 200, true)
monogfx.circle(320, 240, 190, true)
monogfx.drawmode(monogfx.MODE_STIPPLE)
monogfx.disc(320, 240, 140, true)
monogfx.drawmode(monogfx.MODE_NORMAL)
monogfx.disc(320, 240, 90, true)
monogfx.disc(320, 240, 40, false)
sys.wait(2*60)
monogfx.drawmode(monogfx.MODE_INVERT)
repeat 255
monogfx.line(math.rndw() % 640, math.rndw() % 480, math.rndw() % 640, math.rndw() % 480, true)
sys.wait(1*60)
}
}

View File

@ -10,7 +10,6 @@
%import syslib
%import textio
%import math
%import test_stack
%import psg
main {
@ -90,9 +89,6 @@ waitkey:
}
drawScore()
; txt.plot(0,0)
; test_stack.test()
}
ubyte key

View File

@ -1,7 +1,6 @@
%import textio
%import diskio
%zeropage basicsafe
%import test_stack
%option no_sysinit
; Note: this program can be compiled for multiple target systems.
@ -53,7 +52,5 @@ main {
} else {
txt.print("error\n")
}
; test_stack.test()
}
}

View File

@ -1,49 +0,0 @@
%import graphics
%import test_stack
%import math
; Note: this program can be compiled for multiple target systems.
main {
sub start() {
graphics.enable_bitmap_mode()
draw_lines()
draw_circles()
draw_rects()
; graphics.disable_bitmap_mode()
; test_stack.test()
repeat {
}
}
sub draw_rects() {
graphics.rect(220,10,80,10)
graphics.rect(20,180,80,10)
graphics.fillrect(220,30,80,10)
graphics.fillrect(20,160,80,10)
}
sub draw_circles() {
ubyte xx
for xx in 3 to 7 {
graphics.circle(xx*50-100, 10+xx*16, (xx+6)*4)
graphics.disc(xx*50-100, 10+xx*16, (xx+6)*2)
}
}
sub draw_lines() {
ubyte i
for i in 0 to 255 step 4 {
uword x1 = ((graphics.WIDTH-256)/2 as uword) + math.sin8u(i)
uword y1 = (graphics.HEIGHT-128)/2 + math.cos8u(i)/2
uword x2 = ((graphics.WIDTH-64)/2 as uword) + math.sin8u(i)/4
uword y2 = (graphics.HEIGHT-64)/2 + math.cos8u(i)/4
graphics.line(x1, lsb(y1), x2, lsb(y2))
}
}
}

View File

@ -1,114 +0,0 @@
%import textio
%import syslib
%zeropage basicsafe
; Note: this program can be compiled for multiple target systems.
main {
sub start() {
txt.print("rectangles\nand circle\ndrawing.\n")
ubyte r
for r in 3 to 12 step 3 {
circle(20, 12, r)
}
txt.print("enter for disc:")
void cbm.CHRIN()
txt.nl()
txt.clear_screen()
disc(20, 12, 12)
txt.print("enter for rectangles:")
void cbm.CHRIN()
txt.nl()
txt.clear_screen()
rect(4, 8, 37, 23, false)
rect(20, 12, 30, 20, true)
rect(10, 10, 10, 10, false)
rect(6, 0, 16, 20, true)
sub rect(ubyte x1, ubyte y1, ubyte x2, ubyte y2, bool fill) {
ubyte x
ubyte y
if fill {
for y in y1 to y2 {
for x in x1 to x2 {
txt.setcc(x, y, 42, x+y)
}
}
} else {
for x in x1 to x2 {
txt.setcc(x, y1, 42, 8)
txt.setcc(x, y2, 42, 8)
}
if y2>y1 {
for y in y1+1 to y2-1 {
txt.setcc(x1, y, 42, 7)
txt.setcc(x2, y, 42, 7)
}
}
}
}
sub circle(ubyte xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm
ubyte x = radius
ubyte y = 0
byte decisionOver2 = 1-x as byte
while x>=y {
txt.setcc(xcenter + x, ycenter + y, 81, 1)
txt.setcc(xcenter - x, ycenter + y, 81, 2)
txt.setcc(xcenter + x, ycenter - y, 81, 3)
txt.setcc(xcenter - x, ycenter - y, 81, 4)
txt.setcc(xcenter + y, ycenter + x, 81, 5)
txt.setcc(xcenter - y, ycenter + x, 81, 6)
txt.setcc(xcenter + y, ycenter - x, 81, 7)
txt.setcc(xcenter - y, ycenter - x, 81, 8)
y++
if decisionOver2>=0 {
x--
decisionOver2 -= 2*x
}
decisionOver2 += 2*y
decisionOver2++
}
}
sub disc(ubyte cx, ubyte cy, ubyte radius) {
; Midpoint algorithm, filled
; NOTE: because of the symmetry drawing, some horizontal spans will be drawn multiple times.
; because this is only a very low res (text tiles) disc, it's not worth optimizing that.
ubyte x = radius
ubyte y = 0
byte decisionOver2 = 1-x as byte
ubyte xx
while x>=y {
xx = cx-x
repeat 2*x+1 {
txt.setcc(xx, cy + y, 81, 11)
txt.setcc(xx, cy - y, 81, 12)
xx++
}
xx = cx-y
repeat 2*y+1 {
txt.setcc(xx, cy + x, 81, 13)
txt.setcc(xx, cy - x, 81, 14)
xx++
}
y++
if decisionOver2>=0 {
x--
decisionOver2 -= 2*x
}
decisionOver2 += 2*y
decisionOver2++
}
}
}
}

View File

@ -1,6 +1,5 @@
%import graphics
%import floats
%import test_stack
%zeropage floatsafe
; Draw a mandelbrot in graphics mode (the image will be 256 x 200 pixels).
@ -45,9 +44,6 @@ main {
}
}
; graphics.disable_bitmap_mode()
; test_stack.test()
repeat {
}
}

View File

@ -1,6 +1,5 @@
%import textio
%import floats
%import test_stack
%zeropage basicsafe
; Note: this program can be compiled for multiple target systems.
@ -45,7 +44,5 @@ main {
txt.print("finished in ")
floats.print(duration)
txt.print(" seconds!\n")
; test_stack.test()
}
}

View File

@ -1,113 +0,0 @@
%import graphics
%import math
%import textio
%zeropage basicsafe
; Draw sine and cosine graphs. The sine and cosine functions are table lookups
; where the tables are generated by 64tass list functions.
; This uses the graphics library which works on multiple targets (C64, CX16)
main {
const uword width = 320
const ubyte height = 200
sub start() {
graphics.enable_bitmap_mode()
sincos255()
sys.wait(60)
graphics.clear_screen(1, 0)
sincos180()
sys.wait(60)
graphics.clear_screen(1, 0)
circles()
sys.wait(60)
graphics.disable_bitmap_mode()
txt.print("done\n")
}
sub sincos255() {
graphics.line(256,0,256,height-1)
ubyte pixelyb
ubyte pixelxb
byte pixelys
; unsigned
for pixelxb in 0 to 255 {
pixelyb = math.cos8u(pixelxb) / 2
graphics.plot(pixelxb, pixelyb+20)
pixelyb = math.sin8u(pixelxb) / 2
graphics.plot(pixelxb, pixelyb+20)
}
; signed
for pixelxb in 0 to 255 {
pixelys = math.cos8(pixelxb) / 2
graphics.plot(pixelxb, pixelys as ubyte + 90)
pixelys = math.sin8(pixelxb) / 2
graphics.plot(pixelxb, pixelys as ubyte + 90)
}
}
sub sincos180() {
graphics.line(180,0,180,height-1)
ubyte pixelyb
ubyte pixelxb
byte pixelys
; signed
for pixelxb in 0 to 179 {
pixelyb = math.cosr8u(pixelxb) / 2
graphics.plot(pixelxb, pixelyb+20)
pixelyb = math.sinr8u(pixelxb) / 2
graphics.plot(pixelxb, pixelyb+20)
}
; unsigned
for pixelxb in 0 to 179 {
pixelys = math.cosr8(pixelxb) / 2
graphics.plot(pixelxb, pixelys as ubyte + 90)
pixelys = math.sinr8(pixelxb) / 2
graphics.plot(pixelxb, pixelys as ubyte + 90)
}
}
sub circles() {
ubyte pixelyb
uword pixelxw
ubyte r
; circle with "degrees" from 0 to 255
for r in 0 to 255 {
pixelxw = (math.sin8(r)/2 as word) + 80 as uword
pixelyb = (math.cos8(r)/2 as uword + height/2) as ubyte
graphics.plot(pixelxw, pixelyb)
}
for r in 0 to 255 {
pixelxw = math.sin8u(r)/2
pixelyb = math.cos8u(r)/2
graphics.plot(pixelxw + 16, pixelyb+50)
}
; circle with half-degrees from 0 to 179 (=full degrees 0..358 with steps of 2 degrees)
for r in 0 to 179 {
pixelxw = (math.sinr8(r) as word /2 + 220) as uword
pixelyb = (math.cosr8(r)/2 + height/2) as ubyte
graphics.plot(pixelxw, pixelyb)
}
for r in 0 to 179 {
pixelxw = math.sinr8u(r)/2
pixelyb = math.cosr8u(r)/2
graphics.plot(pixelxw + 156, pixelyb+50)
}
}
}

View File

@ -1,37 +1,25 @@
%import palette
%import textio
%option no_sysinit
%option no_sysinit, enable_floats
%zeropage basicsafe
main {
sub start() {
repeat 4 {
for cx16.r0L in 0 to 15 {
txt.color2(cx16.r0L, cx16.r0L)
txt.spc()
txt.spc()
txt.spc()
txt.spc()
}
txt.nl()
}
bool changed
uword[] colors = [
$f00, $800, $200, $000,
$f0f, $80f, $20f, $00f
]
do {
sys.waitvsync()
sys.waitvsync()
changed = palette.fade_step_colors(0, 8, colors)
} until not changed
ubyte[] array = [1,2,3]
ubyte[3] array2
float[] flarray = [1.1, 2.2, 3.3]
float[3] flarray2
word[] warray = [-2222,42,3333]
word[3] warray2
str[] names = ["apple", "banana", "tomato"]
str[3] names2
sys.wait(60)
changed = false
do {
sys.waitvsync()
sys.waitvsync()
changed = palette.fade_step_multi(0, 8, $fff)
} until not changed
sys.wait(60)
; 8 array assignments -> 8 arraycopies:
array = [8,7,6]
array = array2
flarray = [99.9, 88.8, 77.7]
flarray = flarray2
warray = [4444,5555,6666]
warray = warray2
names = ["x1", "x2", "x3"]
names = names2
}
}