Compare commits

...
63 Commits
Author SHA1 Message Date
Irmen de Jong aa10997171 upgrade to kotlin 1.5.20 2021-06-25 19:38:44 +02:00
Irmen de Jong 7880ac1909 wording and version 2021-06-24 21:34:11 +02:00
Irmen de Jong f53848b4b9 wording and version 2021-06-24 21:25:35 +02:00
Irmen de Jong 82f2f38680 Merge pull request #48 from meisl/issue40(EOF,EOL)
* fix 47, add tests
2021-06-19 01:55:44 +02:00
meisl dcc2549574 * fix 47, add tests 2021-06-18 21:55:03 +02:00
Irmen de Jong cfe4753913 Merge pull request #45 from meisl/issue40(EOF,EOL)
Issue #40, EOF/EOL
2021-06-18 20:22:59 +02:00
meisl fcb1a7e4d4 * #40: fix grammar rules module and block s.t. we don't need a "synthesized double EOF" (behavior remains exactly the same) 2021-06-14 22:17:30 +02:00
meisl ce76a7dfa5 * #40: fix grammar wrt line endings - tests pass 2021-06-14 22:04:22 +02:00
meisl 7c1de81861 * #40: fix mixed line endings test, now intentionally failing (!): also test sole \r AND do not allow any recovery, neither from parser not lexer. 2021-06-14 22:02:26 +02:00
meisl eddad20acc Merge remote-tracking branch 'remotes/origin/master' into issue40(EOF,EOL) 2021-06-13 22:56:24 +02:00
meisl 7daad57862 + #40: test for mixed (Unix/Win/Mac) line endings - *TODO: test doesn't actually fail with old grammar, but a built jar does - WHY?!* 2021-06-13 22:49:54 +02:00
meisl 1468049fe9 + #40: test that (module-level) blocks *before the last* still must have a newline after their closing } 2021-06-13 22:43:27 +02:00
meisl 3b91e59a79 * #40: refactor tests 2021-06-13 20:28:01 +02:00
meisl 3496a30528 * #40: put back in fix for EOL-after-block - tests pass 2021-06-13 20:10:35 +02:00
meisl 32bad5df15 +/* #40: add tests; temporarily undo fix for EOL-after-block so we can see that tests actually fail 2021-06-13 20:08:50 +02:00
Irmen de Jong 3f58eca1be updated gradle scripts (fixed warnings), updated some library dependencies 2021-06-13 18:10:07 +02:00
meisl 2350843d1d Merge remote-tracking branch 'remotes/origin/master' into issue40(EOF,EOL) 2021-06-13 16:06:50 +02:00
Irmen de Jong a2588a178c added some simple unit tests to the ast parser 2021-06-13 14:59:57 +02:00
meisl e5292696c4 - #40 grammar: remove obsolete note about line endings 2021-06-13 14:38:25 +02:00
meisl 34b2a65ccb Merge branch 'irmen:master' into issue40(EOF,EOL) 2021-06-12 20:29:37 +02:00
meisl 3aa3659bc7 * #40 grammar: handle different EOLs (Win, Unix, Mac) purely in grammar 2021-06-12 20:24:15 +02:00
meisl b8117394c0 * #40 grammar: don't require EOL after blocks, so .p8 files need not end with that 2021-06-12 17:52:44 +02:00
Irmen de Jong fd2bbd2b59 no longer allow subroutine name same as its block name due to asm symbol scoping issues 2021-06-12 17:31:09 +02:00
Irmen de Jong 127c470746 add some explanation about Cx16 v38 - v39 issue 2021-06-12 15:48:04 +02:00
Irmen de Jong f2844bdf1a fix crash when using labels in pointerexpression lab+index 2021-06-10 00:44:12 +02:00
Irmen de Jong c5bfef4264 slight improvement on scope doc, added doc example for %asminclude/%asmbinary 2021-06-09 23:46:07 +02:00
Irmen de Jong 73863acc12 version bump 2021-06-06 10:50:05 +02:00
Irmen de Jong 19e99204b9 fix asm symbol name scoping bug and add unit tests for this 2021-06-04 22:42:28 +02:00
Irmen de Jong 13f5b94c3e Clarified instructions of how to obtain the compiler. Fixed sphinx css config issue. 2021-06-03 21:21:44 +02:00
Irmen de Jong 3a2498401d working on unit tests for symbol scope bug 2021-06-03 21:21:38 +02:00
Irmen de Jong 53b20ba625 name 2021-06-01 22:22:58 +02:00
Irmen de Jong e7f6f0950f identified asm symbol name scoping bugs 2021-06-01 22:21:50 +02:00
Irmen de Jong 9fbe1b38a5 fix old block syntax in ast print routine 2021-06-01 22:08:23 +02:00
Irmen de Jong 078485134d split up unittests files 2021-06-01 22:07:39 +02:00
Irmen de Jong 67b1766e32 don't use ./ prefix for %asmbinary paths 2021-06-01 19:30:53 +02:00
Irmen de Jong d4b69ac79c improved repeat counter vars allocation (re-use var if possible) 2021-05-30 15:30:34 +02:00
Irmen de Jong e61a2d7083 slightly optimized repeat loop asmgen 2021-05-30 13:10:05 +02:00
Irmen de Jong c03f6604af added free words counting method to zeropage 2021-05-30 00:55:11 +02:00
Irmen de Jong 572bb38ddb update to kotlin 1.5.10 2021-05-29 15:25:17 +02:00
Irmen de Jong 42c5c0cb9f start of cx16 colorbars example 2021-05-26 22:13:23 +02:00
Irmen de Jong e145d2255e added palette.set_all_black() and set_all_white() 2021-05-26 21:33:18 +02:00
Irmen de Jong 442fa07dd4 relax name conflict rule regarding block names vs subroutine params 2021-05-26 21:32:54 +02:00
Irmen de Jong 31ae9e1243 refactor repeat loop counter var creation into single routine 2021-05-22 13:01:51 +02:00
Irmen de Jong d7f83f8df2 version bump 2021-05-20 23:38:41 +02:00
Irmen de Jong 29e2d4e0c8 give error when passing invalid command line option 2021-05-20 23:34:20 +02:00
Irmen de Jong 2732d2c844 exclude d64 files 2021-05-19 18:49:38 +02:00
Irmen de Jong c4a037b277 added '@shared' to syntax files 2021-05-19 18:48:18 +02:00
Irmen de Jong 0e614ad6fc added @shared flag to vardecl to mark variable as shared with assembly code elsewhere, to not have it optimized away 2021-05-19 01:19:25 +02:00
Irmen de Jong ca1a8cd617 improve doc about string (im)mutability 2021-05-19 00:15:17 +02:00
Irmen de Jong ba96a637be remove strdedup compiler argument again
(string deduplication is the default again but only for known-const strings, i.e. string literals)
2021-05-18 23:52:43 +02:00
Irmen de Jong c2cac772e3 validate string interning 2021-05-18 23:37:52 +02:00
Irmen de Jong 6b7216f4ec todo 2021-05-17 19:00:20 +02:00
Irmen de Jong e4fb5946dd optimize cx16 sys.wait and sys.waitvsync to use WAI instruction 2021-05-17 18:44:42 +02:00
Irmen de Jong ca61248861 printing 2-letter strings is now only optimized into direct CHROUT if it's a const string literal 2021-05-16 15:00:40 +02:00
Irmen de Jong 68d7b4649e label and directive location docs 2021-05-16 12:32:08 +02:00
Irmen de Jong 0416aacbbd fix %asminclude by removing scopelabel argument and improving docs to remove false promise about labels 2021-05-16 00:14:57 +02:00
Irmen de Jong bc731e6f8e fix compiler crash when taking address of label 2021-05-16 00:07:48 +02:00
Irmen de Jong ae5d7705bb allow correct parsing of source files that don't end in a EOL character. Fixes #40 2021-05-14 17:14:44 +02:00
Irmen de Jong b9bd541532 restored optimization of printing short strings into just CHROUT
but added comment about known-constness still to be resolved
2021-05-13 01:46:43 +02:00
Irmen de Jong 83639c2535 code style 2021-05-13 01:00:19 +02:00
Irmen de Jong 25d80f4df1 added compiler option to choose string literal deduplication yes/no -- default changed to NO 2021-05-13 00:35:22 +02:00
Irmen de Jong 74f918d911 fix crashes for string encoding errors: give normal compiler error instead 2021-05-11 21:33:11 +02:00
Irmen de Jong a20efa56eb print unmappable character in escaped form in errormessage 2021-05-11 18:09:09 +02:00
84 changed files with 1933 additions and 1020 deletions

No files matched your search

+1 -1
View File
@@ -30,4 +30,4 @@ parsetab.py
.gradle
/prog8compiler.jar
sd*.img
*.d64
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="libraryTable">
<library name="antlr-4.9-complete">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9-complete.jar!/" />
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9.2-complete.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="libraryTable">
<library name="antlr-runtime-4.9">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.jar!/" />
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="libraryTable">
<library name="kotlinx-cli-jvm">
<CLASSES>
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.1.jar!/" />
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+3
View File
@@ -0,0 +1,3 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.5.20" apply false
}
+17 -10
View File
@@ -1,13 +1,14 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.5.0"
id 'com.github.johnrengelman.shadow' version '6.1.0'
id "org.jetbrains.kotlin.jvm"
id 'com.github.johnrengelman.shadow' version '7.0.0'
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenLocal()
mavenCentral()
@@ -21,8 +22,6 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2'
// implementation 'net.razorvine:ksim65:1.8'
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
@@ -30,6 +29,20 @@ dependencies {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
exclude group: "org.antlr", module: "antlr4"
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
@@ -66,7 +79,6 @@ startScripts.enabled = true
application {
mainClass = 'prog8.CompilerMainKt'
mainClassName = 'prog8.CompilerMainKt' // deprecated
applicationName = 'p8compile'
}
@@ -94,8 +106,3 @@ test {
events "skipped", "failed"
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}
+1 -1
View File
@@ -13,7 +13,7 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="unittest-libs" level="project" />
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -195,7 +195,7 @@ sub print_f (float value) {
}}
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}
+2 -2
View File
@@ -158,7 +158,7 @@ sub print_f (float value) {
}}
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}
+8
View File
@@ -70,6 +70,14 @@ palette {
}
}
inline sub set_all_black() {
set_monochrome($000, $000)
}
inline sub set_all_white() {
set_monochrome($fff, $fff)
}
sub set_grayscale() {
vera_palette_ptr = $fa00
repeat 16 {
+19 -19
View File
@@ -759,28 +759,28 @@ sys {
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: regular system vsync irq handler must be running, and no nother irqs
%asm {{
- wai ; wait for irq (assume it was vsync)
cmp #0
bne +
dey
+ dec a
bne -
cpy #0
bne -
rts
}}
}
asmsub waitvsync() clobbers(A, X, Y) {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
; note: system vsync irq handler has to be active for this routine to work.
; note 2: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
inline asmsub waitvsync() {
; --- suspend execution until the next vsync has occurred, without depending on custom irq handling.
; note: system vsync irq handler has to be active for this routine to work (and no other irqs-- which is the default).
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
%asm {{
jsr c64.RDTIM
sta _mod + 1
inc _mod + 1
_loop jsr c64.RDTIM
_mod cmp #255 ; modified
bne _loop
rts
wai
}}
}
+1 -1
View File
@@ -3,5 +3,5 @@
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
math {
%asminclude "library:math.asm", ""
%asminclude "library:math.asm"
}
+2 -2
View File
@@ -3,8 +3,8 @@
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
prog8_lib {
%asminclude "library:prog8_lib.asm", ""
%asminclude "library:prog8_funcs.asm", ""
%asminclude "library:prog8_lib.asm"
%asminclude "library:prog8_funcs.asm"
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
+1 -1
View File
@@ -1 +1 @@
7.0-BETA
7.0
+7 -1
View File
@@ -40,8 +40,8 @@ private fun compileMain(args: Array<String>) {
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
val libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try {
cli.parse(args)
@@ -56,6 +56,12 @@ private fun compileMain(args: Array<String>) {
exitProcess(1)
}
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
if(faultyOption!=null) {
System.err.println("Unknown command line option given: $faultyOption")
exitProcess(1)
}
val libdirs = libDirs.toMutableList()
if(libdirs.firstOrNull()!=".")
libdirs.add(0, ".")
+6 -3
View File
@@ -166,7 +166,10 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
builtinFunctionReturnType(name, args, program)
}
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget, libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
private fun parseImports(filepath: Path,
errors: IErrorReporter,
compTarget: ICompilationTarget,
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
val compilationTargetName = compTarget.name
println("Compiler target: $compilationTargetName. Parsing...")
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
@@ -248,7 +251,7 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
programAst.checkIdentifiers(errors, compilerOptions)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)
errors.report()
@@ -260,7 +263,7 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti
errors.report()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
programAst.checkIdentifiers(errors, compilerOptions)
errors.report()
}
+23 -1
View File
@@ -19,7 +19,29 @@ abstract class Zeropage(protected val options: CompilationOptions) {
val allowedDatatypes = NumericDatatypes
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun availableBytes() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun hasByteAvailable() = if(options.zeropage==ZeropageType.DONTUSE) false else free.isNotEmpty()
fun availableWords(): Int {
if(options.zeropage==ZeropageType.DONTUSE)
return 0
val words = free.windowed(2).filter { it[0] == it[1]-1 }
var nonOverlappingWordsCount = 0
var prevMsbLoc = -1
for(w in words) {
if(w[0]!=prevMsbLoc) {
nonOverlappingWordsCount++
prevMsbLoc = w[1]
}
}
return nonOverlappingWordsCount
}
fun hasWordAvailable(): Boolean {
if(options.zeropage==ZeropageType.DONTUSE)
return false
return free.windowed(2).any { it[0] == it[1] - 1 }
}
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
@@ -15,6 +15,7 @@ import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import java.io.CharConversionException
import java.io.File
import java.util.*
@@ -683,20 +684,20 @@ internal class AstChecker(private val program: Program,
}
"%breakpoint" -> {
if(directive.parent !is INameScope || directive.parent is Module)
err("this directive may only occur in a block")
err("this directive can't be used here")
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.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null)
err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"")
err("this directive can't be used here")
if(directive.args.size!=1 || directive.args[0].str==null)
err("invalid asminclude directive, expected argument: \"filename\"")
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")
err("this directive can't be used here")
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)
@@ -764,6 +765,13 @@ internal class AstChecker(private val program: Program,
override fun visit(string: StringLiteralValue) {
checkValueTypeAndRangeString(DataType.STR, string)
try {
compTarget.encodeString(string.value, string.altEncoding)
} catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode string", string.position)
}
super.visit(string)
}
@@ -44,9 +44,9 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) {
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
val checker2 = AstIdentifiersChecker(this, errors, compTarget)
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
checker2.visit(this)
if(errors.noErrors()) {
@@ -89,7 +89,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing)
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters. Blocks are okay.
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
val paramNames = subroutine.parameters.map { it.name }.toSet()
@@ -101,14 +101,17 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
if(sub!=null)
nameError(name, subroutine.position, sub)
val block = program.allBlocks().firstOrNull { it.name==name }
if(block!=null)
nameError(name, subroutine.position, block)
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
}
if(subroutine.name == subroutine.definingBlock().name) {
// subroutines cannot have the same name as their enclosing block,
// because this causes symbol scoping issues in the resulting assembly source
nameError(subroutine.name, subroutine.position, subroutine.definingBlock())
}
}
super.visit(subroutine)
@@ -7,7 +7,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.AssignTarget
import prog8.compiler.AssemblyError
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.Zeropage
@@ -75,13 +74,13 @@ internal object C64Target: ICompilationTarget {
try {
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
} catch (x: CharConversionException) {
throw AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw AssemblyError("There was a problem decoding to a string: ${x.message}")
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
@@ -102,13 +101,13 @@ internal object Cx16Target: ICompilationTarget {
try {
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
} catch (x: CharConversionException) {
throw AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw AssemblyError("There was a problem decoding to a string: ${x.message}")
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
@@ -39,7 +39,7 @@ internal object C64MachineDefinition: IMachineDefinition {
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "$programName.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
@@ -1,5 +1,6 @@
package prog8.compiler.target.cbm
import prog8.ast.antlr.escape
import java.io.CharConversionException
object Petscii {
@@ -1073,7 +1074,7 @@ object Petscii {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Petscii character for '$chr' (${chr.code})")
throw CharConversionException("no ${case}Petscii character for '${escape(chr.toString())}' (${chr.code})")
}
}
}
@@ -1110,7 +1111,7 @@ object Petscii {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$chr' (${chr.code})")
throw CharConversionException("no ${case}Screencode character for '${escape(chr.toString())}' (${chr.code})")
}
}
}
@@ -381,18 +381,14 @@ internal class AsmGen(private val program: Program,
out("\n; non-zeropage variables")
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
// special treatment for string types: merge strings that are identical
val encodedstringVars = vars
.filter {it.datatype == DataType.STR }
.map {
val str = it.value as StringLiteralValue
it to compTarget.encodeString(str.value, str.altEncoding).plus(0)
}
.groupBy({it.second}, {it.first})
for((encoded, variables) in encodedstringVars) {
variables.dropLast(1).forEach { out(it.name) }
val lastvar = variables.last()
outputStringvar(lastvar, encoded)
for((decl, variables) in encodedstringVars) {
outputStringvar(decl, variables)
}
// non-string variables
@@ -402,11 +398,11 @@ internal class AsmGen(private val program: Program,
}
}
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
val sv = lastvar.value as StringLiteralValue
private fun outputStringvar(strdecl: VarDecl, bytes: List<Short>) {
val sv = strdecl.value as StringLiteralValue
val altEncoding = if(sv.altEncoding) "@" else ""
out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
out("${strdecl.name}\t; ${strdecl.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
@@ -497,18 +493,56 @@ internal class AsmGen(private val program: Program,
return newName
}
internal fun asmSymbolName(identifier: IdentifierReference) =
internal fun asmSymbolName(identifier: IdentifierReference): String {
if(identifier.nameInSource.size==2 && identifier.nameInSource[0]=="prog8_slabs")
return identifier.nameInSource.joinToString(".")
val tgt2 = identifier.targetStatement(program)
if(tgt2==null && (identifier.nameInSource[0].startsWith("_prog8") || identifier.nameInSource[0].startsWith("prog8")))
return identifier.nameInSource.joinToString(".")
val target = identifier.targetStatement(program)!!
val targetScope = target.definingSubroutine()
val identScope = identifier.definingSubroutine()
return if(targetScope !== identScope) {
val scopedName = getScopedSymbolNameForTarget(identifier.nameInSource.last(), target)
if(target is Label) {
val last = scopedName.removeLast()
scopedName.add("_$last")
}
fixNameSymbols(scopedName.joinToString("."))
} else {
if(target is Label) {
val scopedName = identifier.nameInSource.toMutableList()
val last = scopedName.removeLast()
scopedName.add("_$last")
fixNameSymbols(scopedName.joinToString("."))
}
else fixNameSymbols(identifier.nameInSource.joinToString("."))
}
}
internal fun asmVariableName(identifier: IdentifierReference) =
fixNameSymbols(identifier.nameInSource.joinToString("."))
private fun getScopedSymbolNameForTarget(actualName: String, target: Statement): MutableList<String> {
val scopedName = mutableListOf(actualName)
var node: Node = target
while (node !is Block) {
node = node.parent
if(node is INameScope) {
scopedName.add(0, node.name)
}
}
return scopedName
}
internal fun asmSymbolName(regs: RegisterOrPair): String =
if (regs in Cx16VirtualRegisters)
"cx16." + regs.toString().lowercase()
else
throw AssemblyError("no symbol name for register $regs")
internal fun asmVariableName(identifier: IdentifierReference) =
fixNameSymbols(identifier.nameInSource.joinToString("."))
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
internal fun asmVariableName(name: String) = fixNameSymbols(name)
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
@@ -517,38 +551,48 @@ internal class AsmGen(private val program: Program,
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if (isTargetCpu(CpuType.CPU65c02)) {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" lda ($sourceName)")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda (P8ZP_SCRATCH_W1)""")
Pair(false, sourceName)
val target = pointervar.targetStatement(program)
when (target) {
is Label -> {
val sourceName = asmSymbolName(pointervar)
out(" lda $sourceName")
return Pair(true, sourceName)
}
} else {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" ldy #0 | lda ($sourceName),y")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y""")
Pair(false, sourceName)
is VarDecl -> {
val sourceName = asmVariableName(pointervar)
val scopedName = target.makeScopedName(target.name)
if (isTargetCpu(CpuType.CPU65c02)) {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" lda ($sourceName)")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda (P8ZP_SCRATCH_W1)""")
Pair(false, sourceName)
}
} else {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" ldy #0 | lda ($sourceName),y")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y""")
Pair(false, sourceName)
}
}
}
else -> throw AssemblyError("invalid pointervar")
}
}
@@ -881,7 +925,15 @@ internal class AsmGen(private val program: Program,
}
out("; variables")
out("; register saves")
for((dt, name, addr) in sub.asmGenInfo.extraVars) {
if(addr!=null)
out("$name = $addr")
else when(dt) {
DataType.UBYTE -> out("$name .byte 0")
DataType.UWORD -> out("$name .word 0")
else -> throw AssemblyError("weird dt")
}
}
if(sub.asmGenInfo.usedRegsaveA)
out("_prog8_regsaveA .byte 0")
if(sub.asmGenInfo.usedRegsaveX)
@@ -889,9 +941,9 @@ internal class AsmGen(private val program: Program,
if(sub.asmGenInfo.usedRegsaveY)
out("_prog8_regsaveY .byte 0")
if(sub.asmGenInfo.usedFloatEvalResultVar1)
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
if(sub.asmGenInfo.usedFloatEvalResultVar2)
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
vardecls2asm(sub.statements)
out(" .pend\n")
}
@@ -976,11 +1028,11 @@ internal class AsmGen(private val program: Program,
iterations == 0 -> {}
iterations <= 256 -> {
out(" lda #${iterations and 255}")
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt.body)
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt)
}
else -> {
out(" lda #<${iterations} | ldy #>${iterations}")
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt)
}
}
}
@@ -990,11 +1042,11 @@ internal class AsmGen(private val program: Program,
when(vardecl.datatype) {
DataType.UBYTE, DataType.BYTE -> {
assignVariableToRegister(name, RegisterOrPair.A)
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
}
DataType.UWORD, DataType.WORD -> {
assignVariableToRegister(name, RegisterOrPair.AY)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
}
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
}
@@ -1006,11 +1058,11 @@ internal class AsmGen(private val program: Program,
when (dt.typeOrElse(DataType.UNDEFINED)) {
in ByteDatatypes -> {
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.A)
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
}
in WordDatatypes -> {
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.AY)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
}
else -> throw AssemblyError("invalid loop expression datatype $dt")
}
@@ -1020,13 +1072,13 @@ internal class AsmGen(private val program: Program,
loopEndLabels.pop()
}
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
// note: A/Y must have been loaded with the number of iterations!
if(constIterations==0)
return
// no need to explicitly test for 0 iterations as this is done in the count down logic below
val counterVar = makeLabel("repeatcounter")
val counterVar: String = createRepeatCounterVar(DataType.UWORD, constIterations, stmt)
out("""
sta $counterVar
sty $counterVar+1
@@ -1039,44 +1091,67 @@ $repeatLabel lda $counterVar
dec $counterVar+1
+ dec $counterVar
""")
translate(body)
translate(stmt.body)
jmp(repeatLabel)
if(constIterations!=null && constIterations>=16 && zeropage.available() > 1) {
// allocate count var on ZP TODO can be shared with countervars from other subroutines
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, body.position, errors)
out("$counterVar = $zpAddr ; auto zp UWORD")
} else {
out("$counterVar .word 0")
}
out(endLabel)
}
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
// note: A must have been loaded with the number of iterations!
if(constIterations==0)
return
if(constIterations==null)
out(" beq $endLabel ; skip loop if zero iters")
val counterVar = makeLabel("repeatcounter")
val counterVar = createRepeatCounterVar(DataType.UBYTE, constIterations, stmt)
out(" sta $counterVar")
out(repeatLabel)
translate(body)
out("""
dec $counterVar
bne $repeatLabel
beq $endLabel""")
translate(stmt.body)
out(" dec $counterVar | bne $repeatLabel")
if(constIterations==null)
out(endLabel)
}
if(constIterations!=null && constIterations>=16 && zeropage.available() > 0) {
// allocate count var on ZP TODO can be shared with countervars from other subroutines
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, body.position, errors)
out("$counterVar = $zpAddr ; auto zp UBYTE")
} else {
out("$counterVar .byte 0")
private fun createRepeatCounterVar(dt: DataType, constIterations: Int?, stmt: RepeatLoop): String {
val asmInfo = stmt.definingSubroutine()!!.asmGenInfo
var parent = stmt.parent
while(parent !is ParentSentinel) {
if(parent is RepeatLoop)
break
parent = parent.parent
}
val isNested = parent is RepeatLoop
if(!isNested) {
// we can re-use a counter var from the subroutine if it already has one for that datatype
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt }
if(existingVar!=null)
return existingVar.second
}
out(endLabel)
val counterVar = makeLabel("repeatcounter")
when(dt) {
DataType.UBYTE -> {
if(constIterations!=null && constIterations>=16 && zeropage.hasByteAvailable()) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, stmt.position, errors)
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, zpAddr))
} else {
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, null))
}
}
DataType.UWORD -> {
if(constIterations!=null && constIterations>=16 && zeropage.hasWordAvailable()) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, stmt.position, errors)
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, zpAddr))
} else {
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, null))
}
}
else -> throw AssemblyError("invalidt dt")
}
return counterVar
}
private fun translate(stmt: WhileLoop) {
@@ -1237,19 +1312,14 @@ $repeatLabel lda $counterVar
when(stmt.directive) {
"%asminclude" -> {
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
val scopeprefix = stmt.args[1].str ?: ""
if(scopeprefix.isNotBlank())
out("$scopeprefix\t.proc")
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
if(scopeprefix.isNotBlank())
out(" .pend\n")
}
"%asmbinary" -> {
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
val includedSourcePath = stmt.definingModule().source.resolveSibling(stmt.args[0].str)
val relPath = Paths.get("").relativize(includedSourcePath)
out(" .binary \"./$relPath\" $offset $length")
out(" .binary \"$relPath\" $offset $length")
}
"%breakpoint" -> {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
@@ -1268,14 +1338,7 @@ $label nop""")
val label = jump.generatedLabel
val addr = jump.address
return when {
ident!=null -> {
val target = ident.targetStatement(program)
val asmName = asmSymbolName(ident)
if(target is Label)
"_$asmName" // prefix with underscore to jump to local label
else
asmName
}
ident!=null -> asmSymbolName(ident)
label!=null -> label
addr!=null -> addr.toHex()
else -> "????"
@@ -1417,38 +1480,49 @@ $label nop""")
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
if(ptrAndIndex!=null) {
val pointervar = ptrAndIndex.first as? IdentifierReference
if(write) {
if(pointervar!=null && isZpVar(pointervar)) {
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
val target = pointervar?.targetStatement(program)
when(target) {
is Label -> {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
out(" lda ${asmSymbolName(pointervar)},y")
return true
}
} else {
if(pointervar!=null && isZpVar(pointervar)) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
is VarDecl, null -> {
if(write) {
if(pointervar!=null && isZpVar(pointervar)) {
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
} else {
if(pointervar!=null && isZpVar(pointervar)) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
}
}
return true
}
else -> throw AssemblyError("invalid pointervar")
}
return true
}
}
return false
@@ -750,7 +750,7 @@ 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+1), @(ptr+2))
// 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
@@ -1696,7 +1696,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
internal fun translateDirectMemReadExpression(expr: DirectMemoryRead, pushResultOnEstack: Boolean) {
fun assignViaExprEval() {
asmgen.assignExpressionToVariable(expr.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.assignExpressionToVariable(expr.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (pushResultOnEstack) {
asmgen.out(" lda (P8ZP_SCRATCH_W2) | dex | sta P8ESTACK_LO+1,x")
@@ -288,7 +288,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
@@ -327,7 +327,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
@@ -319,10 +319,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
register!!
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
asmgen.signExtendVariableLsb(scratchVar, valueDt)
asmgen.assignVariableToRegister(scratchVar, register)
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
} else {
val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
@@ -115,7 +115,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: Expression) {
assignExpressionToVariable(expression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD, assign.target.scope)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
@@ -145,7 +145,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.EXPRESSION -> {
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = asmgen.asmVariableName(value.identifier)
val sourceName = asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName)
}
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
@@ -320,7 +320,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
if(targetDt in WordDatatypes) {
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
@@ -2089,7 +2089,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun storeViaExprEval() {
when(addressExpr) {
is NumericLiteralValue, is IdentifierReference -> {
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
@@ -2098,7 +2098,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> {
// same as above but we need to save the A register
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.out(" pla")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
@@ -1361,21 +1361,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"*" -> {
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
// TODO use an optimized word * byte multiplication routine
// TODO use an optimized word * byte multiplication routine?
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.signExtendAYlsb(valueDt)
multiplyVarByWordInAY()
}
"/" -> {
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
// TODO use an optimized word / byte divmod routine
// TODO use an optimized word / byte divmod routine?
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.signExtendAYlsb(valueDt)
divideVarByWordInAY()
}
"%" -> {
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
// TODO use an optimized word / byte divmod routine
// TODO use an optimized word / byte divmod routine?
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.signExtendAYlsb(valueDt)
remainderVarByWordInAY()
@@ -1778,7 +1778,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
else -> {
asmgen.assignExpressionToVariable(mem.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, target.scope)
asmgen.assignExpressionToVariable(mem.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
@@ -1846,7 +1846,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
else -> {
asmgen.assignExpressionToVariable(memory.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, target.scope)
asmgen.assignExpressionToVariable(memory.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
+1 -1
View File
@@ -174,7 +174,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
fun unused(decl: VarDecl): Boolean {
if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove)
if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm)
return false
if(decl.definingBlock() !in usedBlocks)
@@ -27,7 +27,12 @@ internal class StatementOptimizer(private val program: Program,
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
for(returnvar in subsThatNeedReturnVariable) {
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null, false, true, returnvar.third)
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null,
isArray = false,
autogeneratedDontRemove = true,
sharedWithAsm = false,
position = returnvar.third
)
returnvar.first.statements.add(0, decl)
}
subsThatNeedReturnVariable.clear()
@@ -43,6 +48,48 @@ internal class StatementOptimizer(private val program: Program,
}
}
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
// only do this optimization if the arg is a known-constant string literal instead of a user defined variable.
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? = if(arg is AddressOf) {
arg.identifier
} else {
arg as? IdentifierReference
}
if(stringVar!=null && stringVar.wasStringLiteral(program)) {
val string = stringVar.targetVarDecl(program)?.value as? StringLiteralValue
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if (string.value.length == 2) {
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
)
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
)
}
}
}
}
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
val subroutine = functionCallStatement.target.targetSubroutine(program)
if(subroutine!=null) {
@@ -1,9 +1,6 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.FunctionCall
@@ -65,7 +62,7 @@ internal class UnusedCodeRemover(private val program: Program,
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
if(block.name != program.internedStringsModuleName)
if(block.name != internedStringsModuleName)
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
}
@@ -106,7 +103,7 @@ internal class UnusedCodeRemover(private val program: Program,
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR) {
val forceOutput = "force_output" in decl.definingBlock().options()
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.definingBlock().isInLibrary) {
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock().isInLibrary) {
if (callgraph.unused(decl)) {
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, decl.definingScope()))
+153
View File
@@ -0,0 +1,153 @@
package prog8tests
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cpu6502.codegen.AsmGen
import java.nio.file.Path
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestAsmGen6502 {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
private fun createTestProgram(): Program {
/*
main {
label_outside:
uword var_outside
sub start () {
uword localvar = 1234
uword tgt
locallabel:
tgt = localvar
tgt = &locallabel
tgt = &var_outside
tgt = &label_outside
tgt = &main.start.localvar
tgt = &main.start.locallabel
tgt = &main.var_outside
tgt = &main.label_outside
}
}
*/
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, Position.DUMMY)
val labelInSub = Label("locallabel", Position.DUMMY)
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), Position.DUMMY)
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
val subroutine = Subroutine("start", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
val labelInBlock = Label("label_outside", Position.DUMMY)
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
val module = Module("test", mutableListOf(block), Position.DUMMY, false, Path.of(""))
module.linkParents(ParentSentinel)
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.program = program
return program
}
private fun createTestAsmGen(program: Program): AsmGen {
val errors = ErrorReporter()
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
val zp = C64MachineDefinition.C64Zeropage(options)
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of("."))
return asmgen
}
@Test
fun testSymbolNameFromStrings() {
val program = createTestProgram()
val asmgen = createTestAsmGen(program)
assertThat(asmgen.asmSymbolName("name"), equalTo("name"))
assertThat(asmgen.asmSymbolName("<name>"), equalTo("prog8_name"))
assertThat(asmgen.asmSymbolName(RegisterOrPair.R15), equalTo("cx16.r15"))
assertThat(asmgen.asmSymbolName(listOf("a", "b", "name")), equalTo("a.b.name"))
assertThat(asmgen.asmVariableName("name"), equalTo("name"))
assertThat(asmgen.asmVariableName("<name>"), equalTo("prog8_name"))
assertThat(asmgen.asmVariableName(listOf("a", "b", "name")), equalTo("a.b.name"))
}
@Test
fun testSymbolNameFromVarIdentifier() {
val program = createTestProgram()
val asmgen = createTestAsmGen(program)
val sub = program.entrypoint()
// local variable
val localvarIdent = sub.statements.filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
assertThat(asmgen.asmSymbolName(localvarIdent), equalTo("localvar"))
assertThat(asmgen.asmVariableName(localvarIdent), equalTo("localvar"))
val localvarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "start", "localvar") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(localvarIdentScoped), equalTo("main.start.localvar"))
assertThat(asmgen.asmVariableName(localvarIdentScoped), equalTo("main.start.localvar"))
// variable from outer scope (note that for Variables, no scoping prefix symbols are required,
// because they're not outputted as locally scoped symbols for the assembler
val scopedVarIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("var_outside") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(scopedVarIdent), equalTo("main.var_outside"))
assertThat(asmgen.asmVariableName(scopedVarIdent), equalTo("var_outside"))
val scopedVarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "var_outside") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(scopedVarIdentScoped), equalTo("main.var_outside"))
assertThat(asmgen.asmVariableName(scopedVarIdentScoped), equalTo("main.var_outside"))
}
@Test
fun testSymbolNameFromLabelIdentifier() {
val program = createTestProgram()
val asmgen = createTestAsmGen(program)
val sub = program.entrypoint()
// local label
val localLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("locallabel") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(localLabelIdent), equalTo("_locallabel"))
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdent), equalTo("locallabel"))
val localLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","start","locallabel") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(localLabelIdentScoped), equalTo("main.start._locallabel"))
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdentScoped), equalTo("main.start.locallabel"))
// label from outer scope needs sope prefixes because it is outputted as a locally scoped symbol for the assembler
val scopedLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("label_outside") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(scopedLabelIdent), equalTo("main._label_outside"))
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdent), equalTo("label_outside"))
val scopedLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","label_outside") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(scopedLabelIdentScoped), equalTo("main._label_outside"))
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdentScoped), equalTo("main.label_outside"))
}
}
+192
View File
@@ -0,0 +1,192 @@
package prog8tests
import org.junit.jupiter.api.Test
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import java.nio.file.Path
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class TestMemory {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test
fun testInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memory_identifiers() {
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
module.linkParents(ParentSentinel)
return target
}
@Test
fun testInValidRamC64_memory_expression() {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_variable() {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memmap_variable() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memmap_variable() {
val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array() {
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array_memmapped() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotValidRamC64_array_memmapped() {
val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
}
+116
View File
@@ -0,0 +1,116 @@
package prog8tests
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.toHex
import prog8.compiler.CompilerException
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestNumbers {
@Test
fun testToHex() {
assertEquals("0", 0.toHex())
assertEquals("1", 1.toHex())
assertEquals("1", 1.234.toHex())
assertEquals("10", 10.toHex())
assertEquals("10", 10.99.toHex())
assertEquals("15", 15.toHex())
assertEquals("\$10", 16.toHex())
assertEquals("\$ff", 255.toHex())
assertEquals("\$0100", 256.toHex())
assertEquals("\$4e5c", 20060.toHex())
assertEquals("\$c382", 50050.toHex())
assertEquals("\$ffff", 65535.toHex())
assertEquals("\$ffff", 65535L.toHex())
assertEquals("0", 0.toHex())
assertEquals("-1", (-1).toHex())
assertEquals("-1", (-1.234).toHex())
assertEquals("-10", (-10).toHex())
assertEquals("-10", (-10.99).toHex())
assertEquals("-15", (-15).toHex())
assertEquals("-\$10", (-16).toHex())
assertEquals("-\$ff", (-255).toHex())
assertEquals("-\$0100", (-256).toHex())
assertEquals("-\$4e5c", (-20060).toHex())
assertEquals("-\$c382", (-50050).toHex())
assertEquals("-\$ffff", (-65535).toHex())
assertEquals("-\$ffff", (-65535L).toHex())
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
}
@Test
fun testFloatToMflpt5() {
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)))
assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)))
assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)))
assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)))
assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)))
assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)))
assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)))
assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)))
assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)))
assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(6.283185307179586), equalTo(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(.25), equalTo(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(123.45678e22), equalTo(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)))
assertThat(Mflpt5.fromNumber(-123.45678e-22), equalTo(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)))
}
@Test
fun testFloatRange() {
assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)))
assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)))
assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)))
assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
}
@Test
fun testMflpt5ToFloat() {
val epsilon=0.000000001
assertThat(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(0.0))
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble(), closeTo(3.141592653, epsilon))
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(3.141592653589793, epsilon))
assertThat(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(32768.0))
assertThat(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.0))
assertThat(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(1.0))
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, epsilon))
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, epsilon))
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, epsilon))
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, epsilon))
assertThat(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-.5))
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, epsilon))
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, epsilon))
assertThat(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble(), equalTo(10.0))
assertThat(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0))
assertThat(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.5))
assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, epsilon))
assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, epsilon))
assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, epsilon))
assertThat(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.25))
assertThat(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble(), closeTo(123.45678e22, 1.0e15))
assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, epsilon))
}
}
@@ -14,13 +14,13 @@ import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
return lv1.type==lv2.type && lv1==lv2
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestParserNumericLiteralValue {
class TestNumericLiteralValue {
private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
return lv1.type==lv2.type && lv1==lv2
}
private val dummyPos = Position("test", 0, 0, 0)
+108
View File
@@ -0,0 +1,108 @@
package prog8tests
import org.hamcrest.MatcherAssert.assertThat
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.Position
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.target.cbm.Petscii
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestPetscii {
@Test
fun testZero() {
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(listOf<Short>(0)))
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(listOf<Short>(0)))
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
}
@Test
fun testLowercase() {
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c)))
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(listOf<Short>(0x12))) // reverse vid
assertThat(Petscii.encodePetscii("✓", true), equalTo(listOf<Short>(0xfa)))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(listOf<Short>(255)))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(listOf<Short>(0xd3)))
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256), true) }
}
@Test
fun testUppercase() {
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c)))
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(listOf<Short>(0x12))) // reverse vid
assertThat(Petscii.encodePetscii("♥"), equalTo(listOf<Short>(0xd3)))
assertThat(Petscii.encodePetscii("π"), equalTo(listOf<Short>(0xff)))
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(listOf<Short>(250)))
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256)) }
}
@Test
fun testScreencodeLowercase() {
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)
))
assertThat(Petscii.encodeScreencode("✓", true), equalTo(listOf<Short>(0x7a)))
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(listOf<Short>(83)))
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(listOf<Short>(94)))
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256), true) }
}
@Test
fun testScreencodeUppercase() {
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)))
assertThat(Petscii.encodeScreencode("♥"), equalTo(listOf<Short>(0x53)))
assertThat(Petscii.encodeScreencode("π"), equalTo(listOf<Short>(0x5e)))
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(listOf<Short>(8, 5, 12, 12, 15)))
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(listOf<Short>(8, 5, 12, 12, 15)))
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(listOf<Short>(122)))
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256)) }
}
@Test
fun testLiteralValueComparisons() {
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
assertTrue(ten != nine)
assertTrue(ten > nine)
assertTrue(ten >= nine)
assertTrue(ten >= ten)
assertFalse(ten > ten)
assertFalse(ten < nine)
assertFalse(ten <= nine)
assertTrue(ten <= ten)
assertFalse(ten < ten)
val abc = StringLiteralValue("abc", false, Position.DUMMY)
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
}
}
-601
View File
@@ -1,601 +0,0 @@
package prog8tests
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import java.nio.file.Path
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompiler {
@Test
fun testToHex() {
assertEquals("0", 0.toHex())
assertEquals("1", 1.toHex())
assertEquals("1", 1.234.toHex())
assertEquals("10", 10.toHex())
assertEquals("10", 10.99.toHex())
assertEquals("15", 15.toHex())
assertEquals("\$10", 16.toHex())
assertEquals("\$ff", 255.toHex())
assertEquals("\$0100", 256.toHex())
assertEquals("\$4e5c", 20060.toHex())
assertEquals("\$c382", 50050.toHex())
assertEquals("\$ffff", 65535.toHex())
assertEquals("\$ffff", 65535L.toHex())
assertEquals("0", 0.toHex())
assertEquals("-1", (-1).toHex())
assertEquals("-1", (-1.234).toHex())
assertEquals("-10", (-10).toHex())
assertEquals("-10", (-10.99).toHex())
assertEquals("-15", (-15).toHex())
assertEquals("-\$10", (-16).toHex())
assertEquals("-\$ff", (-255).toHex())
assertEquals("-\$0100", (-256).toHex())
assertEquals("-\$4e5c", (-20060).toHex())
assertEquals("-\$c382", (-50050).toHex())
assertEquals("-\$ffff", (-65535).toHex())
assertEquals("-\$ffff", (-65535L).toHex())
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
}
@Test
fun testFloatToMflpt5() {
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)))
assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)))
assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)))
assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)))
assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)))
assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)))
assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)))
assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)))
assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)))
assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(6.283185307179586), equalTo(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(.25), equalTo(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(123.45678e22), equalTo(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)))
assertThat(Mflpt5.fromNumber(-123.45678e-22), equalTo(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)))
}
@Test
fun testFloatRange() {
assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)))
assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)))
assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)))
assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
}
@Test
fun testMflpt5ToFloat() {
val epsilon=0.000000001
assertThat(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(0.0))
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble(), closeTo(3.141592653, epsilon))
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(3.141592653589793, epsilon))
assertThat(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(32768.0))
assertThat(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.0))
assertThat(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(1.0))
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, epsilon))
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, epsilon))
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, epsilon))
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, epsilon))
assertThat(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-.5))
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, epsilon))
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, epsilon))
assertThat(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble(), equalTo(10.0))
assertThat(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0))
assertThat(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.5))
assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, epsilon))
assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, epsilon))
assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, epsilon))
assertThat(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.25))
assertThat(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble(), closeTo(123.45678e22, 1.0e15))
assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, epsilon))
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestC64Zeropage {
private val errors = ErrorReporter()
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
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, errors)
}
zp.allocate("varname2", DataType.UBYTE, null, errors)
}
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
}
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
}
}
@Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
zp.allocate("", DataType.BYTE, null, errors)
}
}
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(85, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp1.available())
assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free)
assertTrue(49 in zp1.free)
assertTrue(101 in zp1.free)
assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
assertEquals(139, zp2.available())
assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free)
assertTrue(49 in zp2.free)
assertTrue(101 in zp2.free)
assertFalse(200 in zp2.free)
assertFalse(255 in zp2.free)
assertTrue(199 in zp2.free)
}
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free
zp.allocate("", DataType.FLOAT, null, errors)
}
for (i in 0 until zp.available()) {
val loc = zp.allocate("", DataType.UBYTE, null, errors)
assertTrue(loc > 0)
}
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UWORD, null, errors)
}
}
@Test
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp.available())
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, errors)
}
assertEquals(6,zp.available())
assertFailsWith<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate("", DataType.UWORD, null, errors)
}
for(i in 0..5) {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
// no more space
zp.allocate("", DataType.UBYTE, null, errors)
}
}
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available())
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(0x9b, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.available())
}
@Test
fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage {
@Test
fun testReservedLocations() {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
@Test
fun testFreeSpaces() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(88, zp1.available())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(175, zp3.available())
val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp1.available())
assertTrue(0x22 in zp1.free)
assertTrue(0x80 in zp1.free)
assertTrue(0xff in zp1.free)
assertFalse(0x02 in zp1.free)
assertFalse(0x21 in zp1.free)
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestPetscii {
@Test
fun testZero() {
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(listOf<Short>(0)))
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(listOf<Short>(0)))
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
}
@Test
fun testLowercase() {
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c)))
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(listOf<Short>(0x12))) // reverse vid
assertThat(Petscii.encodePetscii("✓", true), equalTo(listOf<Short>(0xfa)))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(listOf<Short>(255)))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(listOf<Short>(0xd3)))
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256), true) }
}
@Test
fun testUppercase() {
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c)))
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(listOf<Short>(0x12))) // reverse vid
assertThat(Petscii.encodePetscii("♥"), equalTo(listOf<Short>(0xd3)))
assertThat(Petscii.encodePetscii("π"), equalTo(listOf<Short>(0xff)))
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(listOf<Short>(250)))
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256)) }
}
@Test
fun testScreencodeLowercase() {
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)
))
assertThat(Petscii.encodeScreencode("✓", true), equalTo(listOf<Short>(0x7a)))
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(listOf<Short>(83)))
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(listOf<Short>(94)))
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256), true) }
}
@Test
fun testScreencodeUppercase() {
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)))
assertThat(Petscii.encodeScreencode("♥"), equalTo(listOf<Short>(0x53)))
assertThat(Petscii.encodeScreencode("π"), equalTo(listOf<Short>(0x5e)))
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(listOf<Short>(8, 5, 12, 12, 15)))
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(listOf<Short>(8, 5, 12, 12, 15)))
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(listOf<Short>(122)))
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256)) }
}
@Test
fun testLiteralValueComparisons() {
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
assertTrue(ten != nine)
assertTrue(ten > nine)
assertTrue(ten >= nine)
assertTrue(ten >= ten)
assertFalse(ten > ten)
assertFalse(ten < nine)
assertFalse(ten <= nine)
assertTrue(ten <= ten)
assertFalse(ten < ten)
val abc = StringLiteralValue("abc", false, Position.DUMMY)
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
}
}
class TestMemory {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test
fun testInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memory_identifiers() {
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
module.linkParents(ParentSentinel)
return target
}
@Test
fun testInValidRamC64_memory_expression() {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_variable() {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memmap_variable() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memmap_variable() {
val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array() {
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array_memmapped() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotValidRamC64_array_memmapped() {
val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
}
+265
View File
@@ -0,0 +1,265 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestC64Zeropage {
private val errors = ErrorReporter()
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
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, errors)
}
zp.allocate("varname2", DataType.UBYTE, null, errors)
}
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
}
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
}
}
@Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free)
assertEquals(0, zp.availableBytes())
assertFailsWith<CompilerException> {
zp.allocate("", DataType.BYTE, null, errors)
}
}
@Test
fun testFreeSpacesBytes() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp1.availableBytes())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(85, zp2.availableBytes())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(125, zp3.availableBytes())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp4.availableBytes())
zp4.allocate("test", DataType.UBYTE, null, errors)
assertEquals(237, zp4.availableBytes())
zp4.allocate("test2", DataType.UBYTE, null, errors)
assertEquals(236, zp4.availableBytes())
}
@Test
fun testFreeSpacesWords() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(6, zp1.availableWords())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(38, zp2.availableWords())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(57, zp3.availableWords())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(116, zp4.availableWords())
zp4.allocate("test", DataType.UWORD, null, errors)
assertEquals(115, zp4.availableWords())
zp4.allocate("test2", DataType.UWORD, null, errors)
assertEquals(114, zp4.availableWords())
}
@Test
fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp1.availableBytes())
assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free)
assertTrue(49 in zp1.free)
assertTrue(101 in zp1.free)
assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
assertEquals(139, zp2.availableBytes())
assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free)
assertTrue(49 in zp2.free)
assertTrue(101 in zp2.free)
assertFalse(200 in zp2.free)
assertFalse(255 in zp2.free)
assertTrue(199 in zp2.free)
}
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.availableBytes())
assertTrue(zp.hasByteAvailable())
assertTrue(zp.hasWordAvailable())
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free
zp.allocate("", DataType.FLOAT, null, errors)
}
for (i in 0 until zp.availableBytes()) {
val loc = zp.allocate("", DataType.UBYTE, null, errors)
assertTrue(loc > 0)
}
assertEquals(0, zp.availableBytes())
assertFalse(zp.hasByteAvailable())
assertFalse(zp.hasWordAvailable())
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UWORD, null, errors)
}
}
@Test
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp.availableBytes())
assertTrue(zp.hasByteAvailable())
assertTrue(zp.hasWordAvailable())
val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3)
assertFalse(loc in zp.free)
val num = zp.availableBytes() / 2
for(i in 0..num-4) {
zp.allocate("", DataType.UWORD, null, errors)
}
assertEquals(6,zp.availableBytes())
assertFailsWith<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate("", DataType.UWORD, null, errors)
}
for(i in 0..5) {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertEquals(0, zp.availableBytes())
assertFalse(zp.hasByteAvailable())
assertFalse(zp.hasWordAvailable())
assertFailsWith<ZeropageDepletedError> {
// no more space
zp.allocate("", DataType.UBYTE, null, errors)
}
}
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.availableBytes())
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(0x9b, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.availableBytes())
}
@Test
fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage {
private val errors = ErrorReporter()
@Test
fun testReservedLocations() {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
@Test
fun testFreeSpacesBytes() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(88, zp1.availableBytes())
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(175, zp2.availableBytes())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp3.availableBytes())
zp3.allocate("test", DataType.UBYTE, null, errors)
assertEquals(215, zp3.availableBytes())
zp3.allocate("test2", DataType.UBYTE, null, errors)
assertEquals(214, zp3.availableBytes())
}
@Test
fun testFreeSpacesWords() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(108, zp1.availableWords())
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(87, zp2.availableWords())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(44, zp3.availableWords())
zp3.allocate("test", DataType.UWORD, null, errors)
assertEquals(43, zp3.availableWords())
zp3.allocate("test2", DataType.UWORD, null, errors)
assertEquals(42, zp3.availableWords())
}
@Test
fun testReservedSpace() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp1.availableBytes())
assertTrue(0x22 in zp1.free)
assertTrue(0x80 in zp1.free)
assertTrue(0xff in zp1.free)
assertFalse(0x02 in zp1.free)
assertFalse(0x21 in zp1.free)
}
}
+21 -17
View File
@@ -1,7 +1,6 @@
plugins {
id 'antlr'
id 'java'
id "org.jetbrains.kotlin.jvm" version "1.5.0"
id "org.jetbrains.kotlin.jvm"
}
targetCompatibility = 11
@@ -12,22 +11,18 @@ repositories {
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
dependencies {
antlr 'org.antlr:antlr4:4.9'
implementation 'org.antlr:antlr4-runtime:4.9'
implementation 'org.antlr:antlr4-runtime:4.9.2'
implementation project(':parser')
// antlr('org.antlr:antlr4:4.9') {
// exclude group: 'com.ibm.icu', module: 'icu4j'
// }
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
}
compileKotlin {
@@ -62,6 +57,15 @@ sourceSets {
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}
+2
View File
@@ -4,11 +4,13 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="antlr-runtime-4.9" level="project" />
<orderEntry type="library" scope="TEST" name="unittest-libs" level="project" />
</component>
</module>
+1 -2
View File
@@ -7,7 +7,6 @@ import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import java.util.*
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
@@ -34,7 +33,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(block: Block) {
val addr = if(block.address!=null) block.address.toHex() else ""
outputln("~ ${block.name} $addr {")
outputln("${block.name} $addr {")
scopelevel++
for(stmt in block.statements) {
outputi("")
+31 -11
View File
@@ -8,6 +8,8 @@ import prog8.ast.walk.IAstVisitor
import java.nio.file.Path
import kotlin.math.abs
const val internedStringsModuleName = "prog8_interned_strings"
interface IStringEncoding {
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
@@ -256,8 +258,7 @@ class Program(val name: String,
get() = mainModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStrings = mutableMapOf<Pair<String, Boolean>, List<String>>()
val internedStringsModuleName = "prog8_interned_strings"
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
init {
// insert a container module for all interned strings later
@@ -281,21 +282,40 @@ class Program(val name: String,
}
fun internString(string: StringLiteralValue): List<String> {
// Move a string literal into the internal, deduplicated, string pool
// replace it with a variable declaration that points to the entry in the pool.
if(string.parent is VarDecl) {
// deduplication can only be performed safely for known-const strings (=string literals OUTSIDE OF A VARDECL)!
throw FatalAstException("cannot intern a string literal that's part of a vardecl")
}
fun getScopedName(string: StringLiteralValue): List<String> {
val internedStringsBlock = modules
.first { it.name == internedStringsModuleName }.statements
.first { it is Block && it.name == internedStringsModuleName } as Block
val varName = "string_${internedStringsBlock.statements.size}"
val decl = VarDecl(
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, position = string.position
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)
return listOf(internedStringsModuleName, decl.name)
}
val key = Pair(string.value, string.altEncoding)
val existing = internedStrings[key]
if(existing!=null)
val existing = internedStringsUnique[key]
if (existing != null)
return existing
val decl = VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, "string_${internedStrings.size}", string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
val internedStringsBlock = modules.first { it.name==internedStringsModuleName }.statements.first { it is Block && it.name == internedStringsModuleName}
(internedStringsBlock as Block).statements.add(decl)
decl.linkParents(internedStringsBlock)
val scopedName = listOf(internedStringsModuleName, decl.name)
internedStrings[key] = scopedName
val scopedName = getScopedName(string)
internedStringsUnique[key] = scopedName
return scopedName
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
override val position: Position = Position.DUMMY
@@ -13,7 +13,6 @@ import prog8.parser.prog8Parser
import java.io.CharConversionException
import java.io.File
import java.nio.file.Path
import java.util.*
/***************** Antlr Extension methods to create AST ****************/
@@ -70,6 +69,7 @@ private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncodi
it.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED()!=null,
it.toPosition()
)
}
@@ -86,6 +86,7 @@ private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncodi
cvarinit.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED() != null,
cvarinit.toPosition()
)
}
@@ -102,6 +103,7 @@ private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncodi
mvarinit.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED()!=null,
mvarinit.toPosition()
)
}
@@ -604,6 +606,7 @@ private fun prog8Parser.VardeclContext.toAst(encoding: IStringEncoding): VarDecl
null,
ARRAYSIG() != null || arrayindex() != null,
false,
SHARED()!=null,
toPosition()
)
}
@@ -10,9 +10,9 @@ fun escape(str: String): String {
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
in '\u8000'..'\u80ff' -> "\\x" + (it.code - 0x8000).toString(16).padStart(2, '0')
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
else -> "\\u" + it.code.toString(16).padStart(4, '0')
}
}
return es.joinToString("")
@@ -6,7 +6,7 @@ import prog8.ast.base.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import java.util.Objects
import java.util.*
import kotlin.math.abs
@@ -785,9 +785,19 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun inferType(program: Program): InferredTypes.InferredType {
return when (val targetStmt = targetStatement(program)) {
is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
is Label -> InferredTypes.InferredType.known(DataType.UWORD)
else -> InferredTypes.InferredType.unknown()
}
}
fun wasStringLiteral(program: Program): Boolean {
val decl = targetVarDecl(program)
if(decl == null || !decl.autogeneratedDontRemove)
return false
val scope=decl.definingModule()
return scope.name==internedStringsModuleName
}
}
class FunctionCall(override var target: IdentifierReference,
@@ -164,6 +164,7 @@ open class VarDecl(val type: VarDeclType,
var value: Expression?,
val isArray: Boolean,
val autogeneratedDontRemove: Boolean,
val sharedWithAsm: Boolean,
override val position: Position) : Statement(), ISymbolStatement {
override lateinit var parent: Node
var allowInitializeWithZero = true
@@ -183,7 +184,7 @@ open class VarDecl(val type: VarDeclType,
val declaredType = ArrayToElementTypes.getValue(arrayDt)
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array,
isArray = true, autogeneratedDontRemove = true, position = array.position)
isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, position = array.position)
}
fun defaultZero(dt: DataType, position: Position) = when(dt) {
@@ -239,7 +240,7 @@ open class VarDecl(val type: VarDeclType,
}
fun copy(): VarDecl {
val c = VarDecl(type, declaredDatatype, zeropage, arraysize, name, value, isArray, autogeneratedDontRemove, position)
val c = VarDecl(type, declaredDatatype, zeropage, arraysize, name, value, isArray, autogeneratedDontRemove, sharedWithAsm, position)
c.allowInitializeWithZero = this.allowInitializeWithZero
return c
}
@@ -247,7 +248,7 @@ open class VarDecl(val type: VarDeclType,
// a vardecl used only for subroutine parameters
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, false, true, position)
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, false, true, false, position)
class ArrayIndex(var indexExpr: Expression,
override val position: Position) : Node {
@@ -536,8 +537,8 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
}
class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() {
override val name: String
override val position: Position) : INameScope, Statement() { // TODO this isn't really a namescope...(names are scoped to the subroutine level)
override val name: String = "<anon-$sequenceNumber>"
override lateinit var parent: Node
companion object {
@@ -545,7 +546,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
}
init {
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
// make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++
}
@@ -587,6 +588,8 @@ class AsmGenInfo {
var usedRegsaveY = false
var usedFloatEvalResultVar1 = false
var usedFloatEvalResultVar2 = false
val extraVars = mutableListOf<Triple<DataType, String, Int?>>()
}
// the subroutine class covers both the normal user-defined subroutines,
+1 -1
View File
@@ -175,7 +175,7 @@ abstract class AstWalker {
fun applyModifications(): Int {
// check if there are double removes, keep only the last one
val removals = modifications.filter { it.first is IAstModification.Remove }
if(removals.size>0) {
if(removals.isNotEmpty()) {
val doubles = removals.groupBy { (it.first as IAstModification.Remove).node }.filter { it.value.size>1 }
doubles.forEach {
for(doubleRemove in it.value.dropLast(1)) {
@@ -25,7 +25,10 @@ fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
internal fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
class ModuleImporter(val program: Program, val encoder: IStringEncoding, val compilationTargetName: String, val libdirs: List<String>) {
class ModuleImporter(private val program: Program,
private val encoder: IStringEncoding,
private val compilationTargetName: String,
private val libdirs: List<String>) {
fun importModule(filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
@@ -41,8 +44,7 @@ class ModuleImporter(val program: Program, val encoder: IStringEncoding, val com
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
val content = filePath.toFile().readText().replace("\r\n", "\n")
return importModule(CharStreams.fromString(content), filePath, false)
return importModule(CharStreams.fromPath(filePath), filePath, false)
}
fun importLibraryModule(name: String): Module? {
+256
View File
@@ -0,0 +1,256 @@
package prog8tests
import org.antlr.v4.runtime.*
import org.antlr.v4.runtime.misc.ParseCancellationException
import org.junit.jupiter.api.Test
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.IStringEncoding
import prog8.ast.Program
import prog8.ast.antlr.toAst
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Block
import prog8.parser.*
import java.nio.file.Path
import kotlin.test.*
class TestAntlrParser {
class MyErrorListener: ConsoleErrorListener() {
override fun syntaxError(recognizer: Recognizer<*, *>?, offendingSymbol: Any?, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException?) {
throw ParsingFailedError("line $line:$charPositionInLine $msg")
}
}
class MyErrorStrategy: BailErrorStrategy() {
override fun recover(recognizer: Parser?, e: RecognitionException?) {
try {
// let it fill in e in all the contexts
super.recover(recognizer, e)
} catch (pce: ParseCancellationException) {
reportError(recognizer, e)
}
}
override fun recoverInline(recognizer: Parser?): Token {
throw InputMismatchException(recognizer)
}
}
private fun parseModule(srcText: String): prog8Parser.ModuleContext {
return parseModule(CharStreams.fromString(srcText))
}
private fun parseModule(srcFile: Path): prog8Parser.ModuleContext {
return parseModule(CharStreams.fromPath(srcFile))
}
private fun parseModule(srcStream: CharStream): prog8Parser.ModuleContext {
val errorListener = MyErrorListener()
val lexer = prog8Lexer(srcStream)
lexer.removeErrorListeners()
lexer.addErrorListener(errorListener)
val tokens = CommonTokenStream(lexer)
val parser = prog8Parser(tokens)
parser.errorHandler = MyErrorStrategy()
parser.removeErrorListeners()
parser.addErrorListener(errorListener)
return parser.module()
}
object DummyEncoding: IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
TODO("Not yet implemented")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
TODO("Not yet implemented")
}
}
object DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
object DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test
fun testModuleSourceNeedNotEndWithNewline() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val srcText = "foo {" + nl + "}" // source ends with '}' (= NO newline, issue #40)
// before the fix, prog8Parser would have reported (thrown) "missing <EOL> at '<EOF>'"
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 1)
}
@Test
fun testModuleSourceMayEndWithNewline() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 1)
}
@Test
fun testAllBlocksButLastMustEndWithNewline() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
// BAD: 2nd block `bar` does NOT start on new line; however, there's is a nl at the very end
val srcBad = "foo {" + nl + "}" + " bar {" + nl + "}" + nl
// GOOD: 2nd block `bar` does start on a new line; however, a nl at the very end ain't needed
val srcGood = "foo {" + nl + "}" + nl + "bar {" + nl + "}"
assertFailsWith<ParsingFailedError> { parseModule(srcBad) }
val parseTree = parseModule(srcGood)
assertEquals(parseTree.block().size, 2)
}
@Test
fun testWindowsAndMacNewlinesAreAlsoFine() {
val nlWin = "\r\n"
val nlUnix = "\n"
val nlMac = "\r"
//parseModule(Paths.get("test", "fixtures", "mac_newlines.p8").toAbsolutePath())
// a good mix of all kinds of newlines:
val srcText =
"foo {" +
nlMac +
nlWin +
"}" +
nlMac + // <-- do test a single \r (!) where an EOL is expected
"bar {" +
nlUnix +
"}" +
nlUnix + nlMac // both should be "eaten up" by just one EOL token
"combi {" +
nlMac + nlWin + nlUnix // all three should be "eaten up" by just one EOL token
"}" +
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 2)
}
@Test
fun testInterleavedEolAndCommentBeforeFirstBlock() {
// issue: #47
val srcText = """
; comment
; comment
blockA {
}
"""
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 1)
}
@Test
fun testInterleavedEolAndCommentBetweenBlocks() {
// issue: #47
val srcText = """
blockA {
}
; comment
; comment
blockB {
}
"""
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 2)
}
@Test
fun testInterleavedEolAndCommentAfterLastBlock() {
// issue: #47
val srcText = """
blockA {
}
; comment
; comment
"""
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 1)
}
@Test
fun testNewlineBetweenTwoBlocksOrDirectivesStillRequired() {
// issue: #47
// block and block
assertFailsWith<ParsingFailedError>{ parseModule("""
blockA {
} blockB {
}
""") }
// block and directive
assertFailsWith<ParsingFailedError>{ parseModule("""
blockB {
} %import textio
""") }
// The following two are bogus due to directive *args* expected to follow the directive name.
// Leaving them in anyways.
// dir and block
assertFailsWith<ParsingFailedError>{ parseModule("""
%import textio blockB {
}
""") }
assertFailsWith<ParsingFailedError>{ parseModule("""
%import textio %import syslib
""") }
}
/*
@Test
fun testImportLibraryModule() {
val program = Program("foo", mutableListOf(), DummyFunctions, DummyMemsizer)
val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures"))
//assertFailsWith<ParsingFailedError>(){ importer.importLibraryModule("import_file_with_syntax_error") }
}
*/
@Test
fun testProg8Ast() {
// can create charstreams from many other sources as well;
val charstream = CharStreams.fromString("""
main {
sub start() {
return
}
}
""")
val lexer = prog8Lexer(charstream)
val tokens = CommonTokenStream(lexer)
val parser = prog8Parser(tokens)
parser.errorHandler = BailErrorStrategy()
// parser.removeErrorListeners()
// parser.addErrorListener(MyErrorListener())
val ast = parser.module().toAst("test", false, Path.of(""), DummyEncoding)
assertIs<Block>(ast.statements.first())
assertEquals((ast.statements.first() as Block).name, "main")
}
}
+8 -8
View File
@@ -2,8 +2,8 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.5.0"
id 'com.github.johnrengelman.shadow' version '6.1.0'
id "org.jetbrains.kotlin.jvm"
id 'com.github.johnrengelman.shadow' version '7.0.0'
}
targetCompatibility = 11
@@ -28,6 +28,11 @@ dependencies {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
exclude group: "org.antlr", module: "antlr4"
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
@@ -62,8 +67,7 @@ startScripts.enabled = true
application {
mainClass = 'prog8.dbus.TestdbusKt'
mainClassName = 'prog8.dbus.TestdbusKt' // deprecated
applicationName = 'testdbus'
applicationName = 'prog8compilerservicedbus'
}
artifacts {
@@ -90,7 +94,3 @@ test {
events "skipped", "failed"
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}
+3 -3
View File
@@ -2,10 +2,10 @@
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/../dbusCompilerService">
<sourceFolder url="file://$MODULE_DIR$/../dbusCompilerService/src" isTestSource="false" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
+5 -4
View File
@@ -16,7 +16,7 @@ Then you can choose a few ways to get a compiler:
#. download a recent "fat-jar" (called something like "prog8compiler-all.jar") from `the releases on Github <https://github.com/irmen/prog8/releases>`_
#. run the compiler with "java -jar prog8compiler-all.jar" to see how you can use it.
**using the Gradle build system to make it yourself:**
**Using the Gradle build system to build it yourself:**
The Gradle build system is used to build the compiler.
The most interesting gradle commands to run are probably:
@@ -42,9 +42,10 @@ of that build task, you can start the compiler with:
(You should probably make an alias...)
.. hint::
Development and testing is done on Linux, but the compiler should run on most
operating systems. If you do have trouble building or running
the compiler on another operating system, please let me know!
Development and testing is done on Linux using the IntelliJ IDEA IDE,
but the compiler should run on most operating systems that provide a fairly modern
java runtime. If you do have trouble building or running the compiler on your
operating system, please let me know!
+1 -1
View File
@@ -33,7 +33,7 @@ release = version
def setup(app):
# add custom css
app.add_stylesheet("css/customize.css")
app.add_css_file("css/customize.css")
# -- General configuration ---------------------------------------------------
+31 -12
View File
@@ -68,6 +68,16 @@ Language features
Code example
------------
Here is a hello world program::
%import textio
main {
sub start() {
txt.print("hello world i ♥ prog8\n")
}
}
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import textio
@@ -130,11 +140,18 @@ when the exact same program is compiled for the Commander X16 target, and run on
:alt: result when run on CX16 emulator
Getting the compiler
--------------------
Usually you just download a fat jar of an official released version, but you can also build
it yourself from source.
Detailed instructions on how to obtain a version of the compiler are in :ref:`building_compiler`.
.. _requirements:
Required tools
--------------
Required additional tools
-------------------------
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
It's very easy to compile yourself.
@@ -148,20 +165,22 @@ For Windows it's possible to get that as well; check out `AdoptOpenJDK <https://
For MacOS you can use the Homebrew system to install a recent version of OpenJDK.
Finally: an **emulator** (or a real machine ofcourse) to test and run your programs on.
In C64 mode, thhe compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
In C64 mode, the compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
If you're targeting the CommanderX16 instead, there's the `x16emu <https://github.com/commanderx16/x16-emulator>`_.
Make sure you use cx16 emulator and roms **V39 or newer**! Starting from version 6.5, prog8 targets that system version.
Your program may work on V38 but that will only be by luck.
.. important::
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
.. attention:: **Commander-X16 V38 versus V39**
(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.
Starting with Prog8 7.0 the CX16 compilation target is configured for the upcoming v39 version of the emulator
and roms, that are most accurately reflecting the current state of the hardware design of the CX16.
Unfortunately, v39 has not yet been officially released. At the time of writing, v38 is still
the latest official release. So, you have to either compile the v39 emulator+roms from the source in git yourself
or obtain a precompiled version from someone else.
A cx16 program compiled by prog8 7.0 is meant for v39 but *may* still work on the older v38 release of the emulator!
For this to work you should make sure that the program is not using floating point, nor the ram/rom bank switching logic provided by the libraries.
You can also choose to just stick with Prog8 6.4 (which still targets cx16 v38) and wait it out till
the emulator v39 is officially released - but you won't be able to benefit from the compiler improvements
made since the previous release of prog8.
Instructions on how to obtain a prebuilt compiler are in :ref:`building_compiler`.
.. toctree::
+40 -20
View File
@@ -39,7 +39,7 @@ Variable declarations
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.
Variable declarations tend to appear at the top of the code block that uses them, but this is not mandatory.
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.
@@ -62,26 +62,28 @@ Subroutine
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.
Subroutines do not have to be declared before they can be called.
Label
This is a named position in your code where you can jump to from another place.
You can jump to it with a jump statement elsewhere. It is also possible to use a
subroutine call to a label (but without parameters and return value).
Labels can only be defined in a block or in another subroutine, so you can't define a label
inside a loop statement block for instance.
Scope
Also known as 'namespace', this is a named box around the symbols defined in it.
This prevents name collisions (or 'namespace pollution'), because the name of the scope
is needed as prefix to be able to access the symbols in it.
Anything *inside* the scope can refer to symbols in the same scope without using a prefix.
There are three scopes in Prog8:
Also known as 'namespace', this is a named box around the symbols defined in it.
This prevents name collisions (or 'namespace pollution'), because the name of the scope
is needed as prefix to be able to access the symbols in it.
Anything *inside* the scope can refer to symbols in the same scope without using a prefix.
There are three scope levels in Prog8:
- global (no prefix)
- code block
- subroutine
- global (no prefix)
- code block
- subroutine
Modules are *not* a scope! Everything defined in a module is merged into the global scope.
While Modules are separate files, they are *not* separate scopes!
Everything defined in a module is merged into the global scope.
This is different from most other languages that have modules.
The global scope can only contain blocks and some directives, while the others can contain variables and subroutines too.
.. _blocks:
@@ -204,6 +206,16 @@ Example::
byte @zp zeropageCounter = 42
*shared tag:*
If you add the ``@shared`` tag to the variable declaration, the compiler will know that this variable
is a prog8 variable shared with some assembly code elsewhere. This means that the assembly code can
refer to the variable even if it's otherwise not used in prog8 code itself.
(usually, these kinds of 'unused' variables are optimized away by the compiler, resulting in an error
when assembling the rest of the code). Example::
byte @shared assemblyVariable = 42
Integers
^^^^^^^^
@@ -320,14 +332,22 @@ read the syntax reference on strings.
For regular assignments you still need to use an explicit ``&`` (address-of) to take
the address of the string or array.
.. caution::
It's probably best to avoid changing the contents in strings and treat them as static.
This includes changing certain letters by index, or by assigning a new value, or by
modifying the string via other means for example ``substr`` function and its cousins.
This is because the changes persist in memory. If your program exits and is restarted
(without reloading it from disk), it will then start working with the modified strings
instead of the original ones!
The same is true for arrays! So be careful to (re)initialize them if needed.
.. note:: Strings and their (im)mutability
*String literals outside of a string variable's initialization value*,
are considered to be "constant", i.e. the string isn't going to change
during the execution of the program. The compiler takes advantage of this in certain
ways. For instance, multiple identical occurrences of a string literal are folded into
just one string allocation in memory. Examples of such strings are the string literals
passed to a subroutine as arguments.
*Strings that aren't such string literals are considered to be unique*, even if they
are the same as a string defined elsewhere. This includes the strings assigned to
a string variable in its declaration! These kind of strings are not deduplicated and
are just copied into the program in their own unique part of memory. This means that
it is okay to treat those strings as mutable; you can safely change the contents
of such a string without destroying other occurrences (as long as you stay within
the size of the allocated string!)
Special types: const and memory-mapped
+54 -12
View File
@@ -108,7 +108,7 @@ Directives
.. data:: %import <name>
Level: module, block.
Level: module.
This reads and compiles the named module source file as part of your current program.
Symbols from the imported module become available in your code,
without a module or filename prefix.
@@ -136,32 +136,71 @@ Directives
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
Level: block.
Level: not at module scope.
This directive can only be used inside a block.
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
The optional offset and length can be used to select a particular piece of the file.
The file is located relative to the current working directory!
To reference the contents of the included binary data, you can put a label in your prog8 code
just before the %asmbinary. An example program for this can be found below at the description of %asminclude.
.. data:: %asminclude "<filename>", "scopelabel"
.. data:: %asminclude "<filename>"
Level: block.
Level: not at module scope.
This directive can only be used inside a block.
The assembler will include the file as raw assembly source text at this point,
prog8 will not process this at all, with one exception: the labels.
The scopelabel argument will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications.
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
prog8 will not process this at all. Symbols defined in the included assembly can not be referenced
from prog8 code. However they can be referenced from other assembly code if properly prefixed.
You can ofcourse use a label in your prog8 code just before the %asminclude directive, and reference
that particular label to get to (the start of) the included assembly.
Be careful: you risk symbol redefinitions or duplications if you include a piece of
assembly into a prog8 block that already defines symbols itself.
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
if the file can't be found there it is searched relative to the current directory.
Here is a small example program to show how to use labels to reference the included contents from prog8 code::
%import textio
%zeropage basicsafe
main {
sub start() {
txt.print("first three bytes of included asm:\n")
uword included_addr = &included_asm
txt.print_ub(@(included_addr))
txt.spc()
txt.print_ub(@(included_addr+1))
txt.spc()
txt.print_ub(@(included_addr+2))
txt.print("\nfirst three bytes of included binary:\n")
included_addr = &included_bin
txt.print_ub(@(included_addr))
txt.spc()
txt.print_ub(@(included_addr+1))
txt.spc()
txt.print_ub(@(included_addr+2))
txt.nl()
return
included_asm:
%asminclude "inc.asm"
included_bin:
%asmbinary "inc.bin"
}
}
.. data:: %breakpoint
Level: block, subroutine.
Level: not at module scope.
Defines a debugging breakpoint at this location. See :ref:`debugging`
.. data:: %asm {{ ... }}
Level: block, subroutine.
Level: not at module scope.
Declares that there is *inline assembly code* in the lines enclosed by the curly braces.
This code will be written as-is into the generated output file.
The assembler syntax used should be for the 3rd party cross assembler tool that Prog8 uses.
@@ -235,9 +274,11 @@ for them. You can give them an initial value as well. That value can be a simple
or an expression. If you don't provide an intial value yourself, zero will be used.
You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
when selecting variables to be put into zeropage.
You can add a ``@shared`` shared-tag, to tell the compiler that the variable is shared
with some assembly code and that it should not be optimized away if not used elsewhere.
The syntax is::
<datatype> [ @zp ] <variable name> [ = <initial value> ]
<datatype> [ @shared ] [ @zp ] <variable name> [ = <initial value> ]
Various examples::
@@ -252,7 +293,8 @@ Various examples::
byte[5] values ; array of 5 bytes, initially set to zero
byte[5] values = 255 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
word @shared asmvar ; variable is used in assembly code but not elsewhere
Data types
-6
View File
@@ -2,12 +2,6 @@
TODO
====
- possible idea: option to mark vardecls 'shared' to indicate they should not be optimized away because they're shared with assembly code?
However: who even needs variables declared in prog8 code that are only used by assembly???
- github issue: make strings no longer immutable? Deduplication selectable via command line switch?
- test all examples before release of the new version
- simplify cx16.joystick_get2() once this cx16 rom issue is resolved: https://github.com/commanderx16/x16-rom/issues/203
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
- get rid of all other TODO's in the code ;-)
+2 -2
View File
@@ -23,7 +23,7 @@ main {
c64.SCROLX &= %11110111 ; 38 column mode
c64.set_rasterirq(&irq.irq, 200, false) ; enable animation via raster interrupt
c64.set_rasterirq(&irq.irqhandler, 200, false) ; enable animation via raster interrupt
ubyte target_height = 10
ubyte active_height = 24
@@ -130,7 +130,7 @@ spritedata $0f00 {
irq {
ubyte smoothx=0
sub irq() {
sub irqhandler() {
smoothx = (smoothx-1) & 7
main.perform_scroll = smoothx==7
c64.SCROLX = (c64.SCROLX & %11111000) | smoothx
+2 -2
View File
@@ -7,7 +7,7 @@ main {
sub start() {
txt.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n")
c64.set_rasterirq(&irq.irq, 60, true) ; enable playback via raster irq
c64.set_rasterirq(&irq.irqhandler, 60, true) ; enable playback via raster irq
}
}
@@ -16,7 +16,7 @@ irq {
ubyte note_index = 0
ubyte delay = 0
sub irq() {
sub irqhandler() {
c64.EXTCOL++
delay++
if delay >= 8 {
+127
View File
@@ -0,0 +1,127 @@
%import gfx2
%import palette
%import textio
; TODO WORK IN PROGRESS...
; want to make Amiga 'copper' bars color cycling effects
main {
sub start() {
palette.set_all_black()
gfx2.screen_mode(4)
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()
}
repeat {
}
}
}
colors {
ubyte cr
ubyte cg
ubyte cb
ubyte[48+16] reds
ubyte[48+16] greens
ubyte[48+16] blues
ubyte bar_size
sub random_rgb12() {
do {
uword rr = rndw()
cr = msb(rr) & 15
cg = lsb(rr)
cb = cg & 15
cg >>= 4
} until cr+cg+cb >= 12
}
sub random_bar() {
; fade black -> color then fade color -> white
random_rgb12()
ubyte r=0
ubyte g=0
ubyte b=0
ubyte different
bar_size = 0
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++
}
}
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)
}
}
}
+2 -2
View File
@@ -26,7 +26,7 @@ main {
cx16.screen_set_mode(128) ; low-res bitmap 256 colors
cx16.FB_init()
cx16.VERA_DC_VSCALE = 0 ; display trick spoiler.......: stretch display all the way to the bottom
cx16.set_rasterirq(&irq.irq, 0)
cx16.set_rasterirq(&irq.irqhandler, 0)
repeat {
; don't exit
@@ -44,7 +44,7 @@ irq {
ubyte[32] pixels = 0 to 31
sub irq() {
sub irqhandler() {
next_irq_line += 6
anim1 += 4
anim2 += 6
+2 -2
View File
@@ -31,7 +31,7 @@ main {
palette.set_color(0, 0)
palette.set_color(16, 0)
cx16.set_rasterirq(&irq.irq, 0)
cx16.set_rasterirq(&irq.irqhandler, 0)
repeat {
; don't exit
@@ -45,7 +45,7 @@ irq {
uword next_rasterline = 0
const ubyte increment = 4 ; 4 scanlines = 2 lores pixels per color swap (2 scanlines is too tight)
sub irq() {
sub irqhandler() {
if phase & 1 == 0 {
%asm {{
lda #0 ; activate palette #0 (first set of colors)
+2 -2
View File
@@ -13,7 +13,7 @@ main {
txt.plot(14,14)
txt.print("raster bars!")
cx16.set_rasterirq(&irq.irq, 0)
cx16.set_rasterirq(&irq.irqhandler, 0)
repeat {
; don't exit
@@ -39,7 +39,7 @@ irq {
ubyte yanim = 0
const ubyte barheight = 4
sub irq() {
sub irqhandler() {
uword c = colors[color_idx]
color_idx++
color_idx &= 31
+2 -2
View File
@@ -5,7 +5,7 @@ main {
sub start() {
c64.SCROLY &= %11101111 ; blank the screen
c64.set_rasterirq(&irq.irq, 40, false) ; register exclusive raster irq handler
c64.set_rasterirq(&irq.irqhandler, 40, false) ; register exclusive raster irq handler
repeat {
; enjoy the moving bars :)
@@ -22,7 +22,7 @@ irq {
ubyte color = 0
ubyte yanim = 0
sub irq() {
sub irqhandler() {
if color!=len(colors) {
c64.EXTCOL = colors[color]
c64.RASTER += barheight ; next raster Irq for next color
+2 -2
View File
@@ -46,14 +46,14 @@ main {
}
c64.SPENA = 255 ; enable all sprites
c64.set_rasterirq(&irq.irq, 255, true) ; enable animation
c64.set_rasterirq(&irq.irqhandler, 255, true) ; enable animation
}
}
irq {
sub irq() {
sub irqhandler() {
c64.EXTCOL--
; float up & wobble horizontally
+36 -16
View File
@@ -1,23 +1,43 @@
%import textio
%zeropage basicsafe
%option no_sysinit
%zeropage dontuse
main {
byte[] xs1 = "foo1" ; <<<<<<<<<<<<
str xs2 = "foo2" ; <<<<<<<<<<<<
sub start() {
txt.print(xs1)
stringopt()
}
label:
sub start() {
sub stringopt() {
str message = "a"
sub2(&label)
sub2(&label_local)
sub2(&main.sub2.label_in_sub2)
uword xx = &label_local
txt.print_uwhex(xx, true)
txt.nl()
xx = &label
txt.print_uwhex(xx, true)
txt.nl()
xx = &main.label
txt.print_uwhex(xx, true)
txt.nl()
xx = &main.sub2.label_in_sub2
txt.print_uwhex(xx, true)
txt.nl()
xx = main.sub2.sub2var
txt.print_uwhex(xx, true)
txt.nl()
xx = &main.start.label_local
txt.print_uwhex(xx, true)
txt.nl()
txt.print(message)
txt.nl()
message[0] = '@'
txt.print(message)
txt.nl()
}
label_local:
return
}
sub sub2(uword ad) {
uword sub2var = 42
txt.print_uwhex(ad,true)
txt.nl()
label_in_sub2:
txt.nl()
}
}
+2 -2
View File
@@ -38,7 +38,7 @@ main {
c64.SPRPTR[i] = $0a00/64
}
c64.SPENA = 255 ; enable all sprites
c64.set_rasterirq(&irq.irq, 230, true) ; enable animation
c64.set_rasterirq(&irq.irqhandler, 230, true) ; enable animation
}
}
@@ -46,7 +46,7 @@ main {
irq {
ubyte angle
sub irq() {
sub irqhandler() {
angle++
c64.MSIGX=0
ubyte @zp spri
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+1 -1
View File
@@ -130,7 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
Vendored
+3 -18
View File
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -64,21 +64,6 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
@@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
+9 -8
View File
@@ -2,8 +2,8 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.5.0"
id 'com.github.johnrengelman.shadow' version '6.1.0'
id "org.jetbrains.kotlin.jvm"
id 'com.github.johnrengelman.shadow' version '7.0.0'
}
targetCompatibility = 11
@@ -31,6 +31,12 @@ dependencies {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
exclude group: "org.antlr", module: "antlr4"
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
@@ -65,8 +71,7 @@ startScripts.enabled = true
application {
mainClass = 'prog8.http.TestHttpKt'
mainClassName = 'prog8.http.TestHttpKt' // deprecated
applicationName = 'testhttp'
applicationName = 'prog8compilerservicehttp'
}
artifacts {
@@ -93,7 +98,3 @@ test {
events "skipped", "failed"
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}
+1 -1
View File
@@ -5,7 +5,7 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="takes-http" level="project" />
+15 -8
View File
@@ -5,7 +5,6 @@ NOTES:
- whitespace is ignored. (tabs/spaces)
- every position can be empty, be a comment, or contain ONE statement.
- input is assumed to be a text file with UNIX line endings (\n).
*/
@@ -15,10 +14,11 @@ grammar prog8;
package prog8.parser;
}
LINECOMMENT : [\n][ \t]* COMMENT -> channel(HIDDEN);
COMMENT : ';' ~[\n]* -> channel(HIDDEN) ;
LINECOMMENT : ('\r'? '\n' | '\r') [ \t]* COMMENT -> channel(HIDDEN);
COMMENT : ';' ~[\r\n]* -> channel(HIDDEN) ;
EOL : ('\r'? '\n' | '\r')+ ;
WS : [ \t] -> skip ;
EOL : [\n]+ ;
// WS2 : '\\' EOL -> skip;
VOID: 'void';
NAME : [a-zA-Z][a-zA-Z0-9_]* ;
@@ -62,17 +62,24 @@ ZEROPAGE :
'@zp'
;
SHARED :
'@shared'
;
ARRAYSIG :
'[]'
;
cpuregister: 'A' | 'X' | 'Y';
register: 'A' | 'X' | 'Y' | 'AX' | 'AY' | 'XY' | 'Pc' | 'Pz' | 'Pn' | 'Pv' | 'R0' | 'R1' | 'R2' | 'R3' | 'R4' | 'R5' | 'R6' | 'R7' | 'R8' | 'R9' | 'R10' | 'R11' | 'R12' | 'R13' | 'R14' | 'R15';
module : (directive | block | EOL)* EOF ;
// A module (file) consists of zero or more directives or blocks, in any order.
// If there are more than one, then they must be separated by EOL (one or more newlines).
// However, trailing EOL is NOT required.
// Note: the parser may see *several* consecutive EOLs - this happens when EOL and comments are interleaved (see #47)
module: EOL* ((directive | block) (EOL+ (directive | block))*)? EOL* EOF;
block: identifier integerliteral? '{' EOL (block_statement | EOL) * '}' EOL ;
block: identifier integerliteral? '{' EOL (block_statement | EOL)* '}';
block_statement:
@@ -134,7 +141,7 @@ directive :
directivearg : stringliteral | identifier | integerliteral ;
vardecl: datatype ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ;
vardecl: datatype SHARED? ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ;
varinitializer : vardecl '=' expression ;
+7 -19
View File
@@ -10,24 +10,16 @@ repositories {
mavenCentral()
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
dependencies {
antlr 'org.antlr:antlr4:4.9'
implementation 'org.antlr:antlr4-runtime:4.9'
// antlr('org.antlr:antlr4:4.9') {
// exclude group: 'com.ibm.icu', module: 'icu4j'
// }
antlr 'org.antlr:antlr4:4.9.2'
implementation 'org.antlr:antlr4-runtime:4.9.2'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
}
compileJava {
dependsOn tasks.withType(AntlrTask)
}
@@ -47,7 +39,3 @@ sourceSets {
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}
+1 -1
View File
@@ -13,7 +13,7 @@
</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" />
<keywords3 keywords="byte;const;float;str;ubyte;uword;void;word;zp" />
<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>
<extensionMap>
+1 -1
View File
@@ -24,7 +24,7 @@
<Keywords name="Folders in comment, open"></Keywords>
<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</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="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>
+1 -1
View File
@@ -43,7 +43,7 @@ syn region prog8ArrayType matchgroup=prog8Type
\ start="\<\%(u\?byte\|u\?word\|float\|str\)\[" end="\]"
\ transparent
syn keyword prog8StorageClass const
syn match prog8StorageClass "\(^\|\s\)@zp\>"
syn match prog8StorageClass "\(^\|\s\)\(@zp\|@shared\)\>"
syn region prog8Block start="{" end="}" transparent
syn region prog8Expression start="(" end=")" transparent