mirror of
https://github.com/irmen/prog8.git
synced 2025-10-01 03:16:26 +00:00
Compare commits
8 Commits
v12.0-beta
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
87c1bbbf40 | ||
|
a7ad6abdb9 | ||
|
a611406020 | ||
|
75da38224d | ||
|
8d6f3301c8 | ||
|
86b52a1c5e | ||
|
2c8b1c2022 | ||
|
ab1f065752 |
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -15,6 +15,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
|
||||
|
@@ -13,6 +13,7 @@ class CompilationOptions(val output: OutputType,
|
||||
val noSysInit: Boolean,
|
||||
val romable: Boolean,
|
||||
val compTarget: ICompilationTarget,
|
||||
val compilerVersion: String,
|
||||
// these are set later, based on command line arguments or options in the source code:
|
||||
var loadAddress: UInt,
|
||||
var memtopAddress: UInt,
|
||||
|
@@ -31,6 +31,7 @@ class TestCodegen: FunSpec({
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
|
@@ -23,6 +23,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
noSysInit = true,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
|
@@ -25,6 +25,7 @@ class TestVmCodeGen: FunSpec({
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
|
@@ -440,7 +440,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
val numval = decl.value as? NumericLiteral
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
if(valueDt issimpletype BaseDataType.LONG) {
|
||||
if(valueDt issimpletype BaseDataType.LONG || decl.datatype.isLong) {
|
||||
return noModifications // this is handled in the numericalvalue case
|
||||
}
|
||||
if(!(valueDt istype decl.datatype)) {
|
||||
|
@@ -1033,6 +1033,22 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
; The SuperCPU expansion for the C64/C128 contains a 65816.
|
||||
%asm {{
|
||||
php
|
||||
clv
|
||||
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
|
||||
bvc +
|
||||
lda #1
|
||||
plp
|
||||
rts
|
||||
+ lda #0
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@@ -1209,12 +1225,6 @@ cx16 {
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
@@ -1041,6 +1041,23 @@ _no_msb_size
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
; The SuperCPU expansion for the C64/C128 contains a 65816.
|
||||
%asm {{
|
||||
php
|
||||
clv
|
||||
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
|
||||
bvc +
|
||||
lda #1
|
||||
plp
|
||||
rts
|
||||
+ lda #0
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@@ -1218,12 +1235,6 @@ cx16 {
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
@@ -231,12 +231,12 @@ asmsub str_w (word value @ AY) clobbers(X) -> str @AY {
|
||||
|
||||
; ---- string conversion to numbers -----
|
||||
|
||||
asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
|
||||
asmsub any2uword(str string @AY) -> uword @AY, ubyte @X {
|
||||
; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format.
|
||||
; (the latter two require a $ or % prefix to be recognised)
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
; returns amount of processed characters in A, and the parsed number will be in cx16.r15.
|
||||
; if the string was invalid, 0 will be returned in A.
|
||||
; returns the parsed number word in AY, and the number of processed characters (including the prefix symbol) in X.
|
||||
; if the string was invalid, 0 will be returned as count in X (and the word value in AY will be undefined).
|
||||
%asm {{
|
||||
pha
|
||||
sta P8ZP_SCRATCH_W1
|
||||
@@ -257,13 +257,7 @@ _hex pla
|
||||
_bin pla
|
||||
jsr bin2uword
|
||||
_result
|
||||
pha
|
||||
lda cx16.r15
|
||||
sta P8ZP_SCRATCH_B1 ; result value
|
||||
pla
|
||||
sta cx16.r15
|
||||
sty cx16.r15+1
|
||||
lda P8ZP_SCRATCH_B1
|
||||
ldx cx16.r15
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
@@ -1454,22 +1454,6 @@ sub search_x16edit() -> ubyte {
|
||||
return 255
|
||||
}
|
||||
|
||||
asmsub cpu_is_65816() -> bool @A {
|
||||
; -- Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
%asm {{
|
||||
php
|
||||
clv
|
||||
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
|
||||
bvc +
|
||||
lda #1
|
||||
plp
|
||||
rts
|
||||
+ lda #0
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub set_program_args(str args_ptr, ubyte args_size) {
|
||||
; -- Set the inter-program arguments.
|
||||
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
|
||||
@@ -2135,6 +2119,21 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cpu_is_65816() -> bool @A {
|
||||
; -- Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
%asm {{
|
||||
php
|
||||
clv
|
||||
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
|
||||
bvc +
|
||||
lda #1
|
||||
plp
|
||||
rts
|
||||
+ lda #0
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
@@ -509,6 +509,11 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@@ -686,11 +691,6 @@ cx16 {
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
@@ -267,12 +267,15 @@ sub bin2uword(str string) -> uword {
|
||||
}
|
||||
}
|
||||
|
||||
sub any2uword(str string) -> uword {
|
||||
sub any2uword(str string) -> uword, ubyte {
|
||||
; -- convert any number string (any prefix allowed) to uword.
|
||||
; returns the parsed word value, and the number of processed characters (including the prefix symbol)
|
||||
ubyte length
|
||||
while string[length]!=0 length++
|
||||
when string[0] {
|
||||
'$' -> return hex2uword(string)
|
||||
'%' -> return bin2uword(string)
|
||||
else -> return str2uword(string)
|
||||
'$' -> return hex2uword(string), length
|
||||
'%' -> return bin2uword(string), length
|
||||
else -> return str2uword(string), length
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -244,6 +244,11 @@ sys {
|
||||
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
@@ -5,6 +5,7 @@ import prog8.ast.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.buildversion.VERSION
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.PtProgram
|
||||
@@ -456,7 +457,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
|
||||
return CompilationOptions(
|
||||
outputType, launcherType,
|
||||
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit, rombale,
|
||||
compTarget, 0u, 0xffffu
|
||||
compTarget, VERSION, 0u, 0xffffu
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -49,9 +49,11 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
val constValue = decl.value!!.constValue(program)!!
|
||||
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
|
||||
} else {
|
||||
// don't make it signed if it was unsigned and vice versa
|
||||
if(valueDt.isSigned && decl.datatype.isUnsigned ||
|
||||
valueDt.isUnsigned && decl.datatype.isSigned) {
|
||||
// don't make it signed if it was unsigned and vice versa, except when it is a long const declaration
|
||||
if(!decl.datatype.isLong &&
|
||||
(valueDt.isSigned && decl.datatype.isUnsigned ||
|
||||
valueDt.isUnsigned && decl.datatype.isSigned))
|
||||
{
|
||||
val constValue = decl.value!!.constValue(program)!!
|
||||
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
|
||||
} else {
|
||||
|
@@ -21,6 +21,7 @@ class TestGoldenRam: FunSpec({
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = VMTarget(),
|
||||
compilerVersion="99.99",
|
||||
loadAddress = 999u,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
|
@@ -12,7 +12,7 @@ class TestLaunchEmu: FunSpec({
|
||||
val target = VMTarget()
|
||||
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
|
||||
tmpfile.writeText("""<?xml version="1.0" encoding="utf-8"?>
|
||||
<PROGRAM NAME="test">
|
||||
<PROGRAM NAME="test" COMPILERVERSION="99.99">
|
||||
<OPTIONS>
|
||||
</OPTIONS>
|
||||
|
||||
|
@@ -49,6 +49,7 @@ class TestAbstractZeropage: FunSpec({
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = C64Target(),
|
||||
compilerVersion="99.99",
|
||||
loadAddress = 999u,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
@@ -69,7 +70,7 @@ class TestC64Zeropage: FunSpec({
|
||||
floats = false,
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = c64target, loadAddress = 999u, memtopAddress = 0xffffu
|
||||
compTarget = c64target, compilerVersion="99.99", loadAddress = 999u, memtopAddress = 0xffffu
|
||||
))
|
||||
|
||||
var result = zp.allocate("", DataType.UBYTE, null, null, errors)
|
||||
@@ -84,33 +85,33 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testZpFloatEnable") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
var result = zp.allocate("", DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled floats" }
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
result = zp2.allocate("", DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled ZP use" }
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp3.allocate("", DataType.FLOAT, null, null, errors)
|
||||
}
|
||||
|
||||
test("testZpModesWithFloats") {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
}
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
}
|
||||
}
|
||||
|
||||
test("testZpDontuse") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
println(zp.free)
|
||||
zp.availableBytes() shouldBe 0
|
||||
val result = zp.allocate("", DataType.BYTE, null, null, errors)
|
||||
@@ -118,13 +119,13 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 15
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 85
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp3.availableBytes() shouldBe 94
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp4.availableBytes() shouldBe 205
|
||||
zp4.allocate("test", DataType.UBYTE, null, null, errors)
|
||||
zp4.availableBytes() shouldBe 204
|
||||
@@ -133,7 +134,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 205
|
||||
4u shouldNotBeIn zp1.free
|
||||
5u shouldNotBeIn zp1.free
|
||||
@@ -146,7 +147,7 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldBeIn zp1.free
|
||||
255u shouldBeIn zp1.free
|
||||
199u shouldBeIn zp1.free
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 105
|
||||
4u shouldNotBeIn zp2.free
|
||||
5u shouldNotBeIn zp2.free
|
||||
@@ -159,7 +160,7 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldNotBeIn zp2.free
|
||||
255u shouldNotBeIn zp2.free
|
||||
199u shouldBeIn zp2.free
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 105
|
||||
4u shouldBeIn zp3.free
|
||||
5u shouldBeIn zp3.free
|
||||
@@ -168,7 +169,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testBasicsafeAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp.availableBytes() shouldBe 15
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
@@ -190,7 +191,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFullAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp.availableBytes() shouldBe 205
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
@@ -222,7 +223,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testEfficientAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp.availableBytes() shouldBe 15
|
||||
zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
|
||||
@@ -239,7 +240,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
@@ -252,18 +253,18 @@ class TestCx16Zeropage: FunSpec({
|
||||
val cx16target = Cx16Target()
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 86
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 173
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp3.availableBytes() shouldBe 214
|
||||
zp3.allocate("test", DataType.UBYTE, null, null, errors)
|
||||
zp3.availableBytes() shouldBe 213
|
||||
@@ -272,7 +273,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 214
|
||||
0x22u shouldNotBeIn zp1.free
|
||||
0x23u shouldNotBeIn zp1.free
|
||||
@@ -284,7 +285,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("preallocated zp vars") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp1.allocatedVariables["test"] shouldBe null
|
||||
zp1.allocatedVariables["cx16.r0"] shouldNotBe null
|
||||
zp1.allocatedVariables["cx16.r15"] shouldNotBe null
|
||||
|
@@ -466,4 +466,15 @@ main {
|
||||
compileText(C64Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
|
||||
}
|
||||
|
||||
test("const long with small values") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
const long notkaputt = 42
|
||||
cx16.r0L = notkaputt
|
||||
}
|
||||
}"""
|
||||
compileText(Cx16Target(), true, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
@@ -88,7 +88,7 @@ class TestAsmGenSymbols: StringSpec({
|
||||
|
||||
fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, false, C64Target(), 999u, 0xffffu)
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, false, C64Target(), "99.99", 999u, 0xffffu)
|
||||
val astchecker = AstChecker(program, errors, options)
|
||||
astchecker.visit(program)
|
||||
errors.report()
|
||||
|
@@ -422,10 +422,6 @@ On the other targets, it only contains the definition of the 16 memory-mapped vi
|
||||
``restore_virtual_registers()``
|
||||
restore the values of all 16 virtual registers r0 - r15 from the buffer. Might be useful in an IRQ handler to avoid clobbering them.
|
||||
|
||||
``cpu_is_65816()``
|
||||
Returns true if the CPU in the computer is a 65816, false otherwise (6502 cpu).
|
||||
Note that Prog8 itself has no support yet for this CPU other than detecting its presence.
|
||||
|
||||
``reset_system ()``
|
||||
Soft-reset the system back to initial power-on BASIC prompt. (same as the routine in sys)
|
||||
|
||||
@@ -1060,6 +1056,10 @@ sys (part of syslib)
|
||||
- 128 = Commodore 128
|
||||
- 255 = Virtual machine
|
||||
|
||||
``cpu_is_65816()``
|
||||
Returns true if the CPU in the computer is a 65816, false otherwise (6502 cpu).
|
||||
Note that Prog8 itself has no support yet for this CPU other than detecting its presence.
|
||||
|
||||
``exit (returncode)``
|
||||
Immediately stops the program and exits it, with the returncode in the A register.
|
||||
Note: custom interrupt handlers remain active unless manually cleared first!
|
||||
|
@@ -49,7 +49,6 @@ Future Things and Ideas
|
||||
|
||||
IR/VM
|
||||
-----
|
||||
- make MSIG instruction set flags and skip cmp #0 afterwards (if msb(x)>0)
|
||||
- is it possible to use LOADFIELD/STOREFIELD instructions even more?
|
||||
- make multiple classes of registers and maybe also categorize by life time , to prepare for better register allocation in the future
|
||||
SYSCALL_ARGS, // Reserved for syscall arguments (r99000-99099, r99100-99199)
|
||||
@@ -75,8 +74,8 @@ IR/VM
|
||||
- change the instruction format so an indirect register (a pointer) can be used more often, at least for the inplace assignment operators that operate on pointer
|
||||
- getting it in shape for code generation...: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!)
|
||||
- fix call() return value handling (... what's wrong with it again?)
|
||||
- encode asmsub/extsub clobber info in the call , or maybe include these definitions in the p8ir file itself too. (return registers are already encoded in the CALL instruction)
|
||||
- proper code gen for the CALLI instruction and that it (optionally) returns a word value that needs to be assigned to a reg
|
||||
- encode asmsub/extsub clobber info in the call , or maybe include these definitions in the p8ir file itself too. (return registers are already encoded in the CALL instruction)
|
||||
- implement fast code paths for TODO("inplace split....
|
||||
- implement more TODOs in AssignmentGen
|
||||
- do something with the 'split' tag on split word arrays
|
||||
@@ -86,10 +85,10 @@ IR/VM
|
||||
don't forget to take into account the data type of the register when it's going to be reused!
|
||||
- idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
|
||||
global initialization values are simply a list of LOAD instructions.
|
||||
Variables replaced include all subroutine parameters! So the only variables that remain as variables are arrays and strings.
|
||||
Variables replaced include all subroutine parameters? Or not? So the only variables that remain as variables are arrays and strings.
|
||||
- the @split arrays are currently also split in _lsb/_msb arrays in the IR, and operations take multiple (byte) instructions that may lead to verbose and slow operation and machine code generation down the line.
|
||||
maybe another representation is needed once actual codegeneration is done from the IR...?
|
||||
- ExpressionCodeResult: get rid of the separation between single result register and multiple result registers? maybe not, this requires hundreds of lines to change
|
||||
maybe another representation is needed once actual codegeneration is done from the IR...? Should array operations be encoded in a more high level form in the IR?
|
||||
- ExpressionCodeResult: get rid of the separation between single result register and multiple result registers? maybe not, this requires hundreds of lines to change.. :(
|
||||
- sometimes source lines end up missing in the output p8ir, for example the first assignment is gone in:
|
||||
sub start() {
|
||||
cx16.r0L = cx16.r1 as ubyte
|
||||
@@ -170,4 +169,4 @@ Optimizations
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
|
||||
for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest
|
||||
This will probably need the register categorization from the IR explained there, for the old 6502 codegen there is not enough information to act on
|
||||
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code, those checks should probably be removed, or be made permanent
|
||||
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code, those checks should probably all be removed, or be made permanent
|
||||
|
@@ -411,6 +411,11 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@@ -584,12 +589,6 @@ cx16 {
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
@@ -672,6 +672,10 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@@ -846,11 +850,6 @@ cx16 {
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
@@ -344,6 +344,11 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@@ -517,12 +522,6 @@ cx16 {
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
@@ -1,15 +1,15 @@
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
sprptr[2]^^.y++
|
||||
}
|
||||
uword @shared value
|
||||
|
||||
struct Sprite {
|
||||
ubyte x
|
||||
uword y
|
||||
}
|
||||
if msb(value)>0
|
||||
cx16.r0++
|
||||
|
||||
^^Sprite[4] @shared sprites
|
||||
^^Sprite @shared sprptr
|
||||
}
|
||||
if lsb(value)>0
|
||||
cx16.r0++
|
||||
|
||||
value = mkword(cx16.r0L, cx16.r1L)
|
||||
if_z
|
||||
cx16.r0++
|
||||
}
|
||||
}
|
||||
|
@@ -3,4 +3,4 @@ org.gradle.console=rich
|
||||
org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
kotlin.code.style=official
|
||||
version=12.0-BETA3
|
||||
version=12.0-SNAPSHOT
|
||||
|
@@ -46,7 +46,8 @@ class IRFileReader {
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="PROGRAM") { "missing PROGRAM" }
|
||||
val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value
|
||||
val options = parseOptions(reader)
|
||||
val compilerVersion = start.attributes.asSequence().single { it.name.localPart == "COMPILERVERSION" }.value
|
||||
val options = parseOptions(reader, compilerVersion)
|
||||
val asmsymbols = parseAsmSymbols(reader)
|
||||
val varsWithoutInitClean = parseVarsWithoutInit("VARIABLESNOINIT", false, reader)
|
||||
val varsWithoutInitDirty = parseVarsWithoutInit("VARIABLESNOINITDIRTY", true, reader)
|
||||
@@ -81,7 +82,7 @@ class IRFileReader {
|
||||
return program
|
||||
}
|
||||
|
||||
private fun parseOptions(reader: XMLEventReader): CompilationOptions {
|
||||
private fun parseOptions(reader: XMLEventReader, compilerVersion: String): CompilationOptions {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="OPTIONS") { "missing OPTIONS" }
|
||||
@@ -136,6 +137,7 @@ class IRFileReader {
|
||||
false,
|
||||
romable,
|
||||
target,
|
||||
compilerVersion,
|
||||
loadAddress,
|
||||
memtop,
|
||||
outputDir = outputDir,
|
||||
|
@@ -26,6 +26,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
xml.writeCharacters("\n")
|
||||
xml.writeStartElement("PROGRAM")
|
||||
xml.writeAttribute("NAME", irProgram.name)
|
||||
xml.writeAttribute("COMPILERVERSION", irProgram.options.compilerVersion)
|
||||
xml.writeCharacters("\n")
|
||||
writeOptions()
|
||||
writeAsmSymbols()
|
||||
|
@@ -17,11 +17,12 @@ Program to execute is not stored in the system memory, it's just a separate list
|
||||
100K virtual floating point registers (64 bits double precision) fr0-fr99999
|
||||
65536 bytes of memory. Thus memory pointers (addresses) are limited to 16 bits.
|
||||
Value stack, max 128 entries of 1 byte each.
|
||||
Status flags: Carry, Zero, Negative. NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC,
|
||||
LOAD instructions DO affect the Z and N flags.
|
||||
INC/DEC/NEG instructions DO affect the Z and N flags,
|
||||
other instructions only affect Z an N flags if the value in a result register is written.
|
||||
See OpcodesThatSetStatusbits
|
||||
Status flags: Carry, Zero, Negative, Overflow.
|
||||
NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC,
|
||||
LOAD instructions also DO affect the Z and N flags.
|
||||
INC/DEC/NEG instructions also DO affect the Z and N flags,
|
||||
other instructions also only affect Z an N flags if the value in a result register is written.
|
||||
See OpcodesThatSetStatusbits.
|
||||
|
||||
Instruction set is mostly a load/store architecture, there are few instructions operating on memory directly.
|
||||
|
||||
@@ -500,7 +501,10 @@ val OpcodesThatSetStatusbitsButNotCarry = arrayOf(
|
||||
Opcode.OR,
|
||||
Opcode.XORM,
|
||||
Opcode.XORR,
|
||||
Opcode.XOR
|
||||
Opcode.XOR,
|
||||
Opcode.LSIG,
|
||||
Opcode.MSIG,
|
||||
Opcode.CONCAT
|
||||
)
|
||||
|
||||
val OpcodesThatDependOnCarry = arrayOf(
|
||||
|
@@ -23,6 +23,7 @@ class TestIRFileInOut: FunSpec({
|
||||
noSysInit = true,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion = "99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu,
|
||||
outputDir = tempdir
|
||||
@@ -32,7 +33,7 @@ class TestIRFileInOut: FunSpec({
|
||||
val generatedFile = writer.write()
|
||||
val lines = generatedFile.readLines()
|
||||
lines[0] shouldBe "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
lines[1] shouldBe "<PROGRAM NAME=\"unittest-irwriter\">"
|
||||
lines[1] shouldBe "<PROGRAM NAME=\"unittest-irwriter\" COMPILERVERSION=\"99.99\">"
|
||||
lines.last() shouldBe "</PROGRAM>"
|
||||
generatedFile.deleteExisting()
|
||||
lines.size shouldBeGreaterThan 20
|
||||
@@ -40,7 +41,7 @@ class TestIRFileInOut: FunSpec({
|
||||
|
||||
test("test IR reader") {
|
||||
val source="""<?xml version="1.0" encoding="utf-8"?>
|
||||
<PROGRAM NAME="test-ir-reader">
|
||||
<PROGRAM NAME="test-ir-reader" COMPILERVERSION="99.99">
|
||||
<OPTIONS>
|
||||
compTarget=virtual
|
||||
output=PRG
|
||||
|
3
languageServer/README.TXT
Normal file
3
languageServer/README.TXT
Normal file
@@ -0,0 +1,3 @@
|
||||
This module is a totally not yet working skeleton implementation of a Prog8 LSP server.
|
||||
|
||||
https://en.wikipedia.org/wiki/Language_Server_Protocol
|
105
languageServer/build.gradle.kts
Normal file
105
languageServer/build.gradle.kts
Normal file
@@ -0,0 +1,105 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("application")
|
||||
}
|
||||
|
||||
val debugPort = 8000
|
||||
val debugArgs = "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n,quiet=y"
|
||||
|
||||
val serverMainClassName = "prog8lsp.MainKt"
|
||||
val applicationName = "prog8-language-server"
|
||||
|
||||
application {
|
||||
mainClass.set(serverMainClassName)
|
||||
description = "Code completions, diagnostics and more for Prog8"
|
||||
// applicationDefaultJvmArgs = listOf("-DkotlinLanguageServer.version=$version")
|
||||
applicationDistribution.into("bin") {
|
||||
filePermissions {
|
||||
user {
|
||||
read=true
|
||||
execute=true
|
||||
write=true
|
||||
}
|
||||
other.execute = true
|
||||
group.execute = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0")
|
||||
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.24.0")
|
||||
|
||||
// For JSON processing if needed
|
||||
//implementation("com.google.code.gson:gson:2.10.1")
|
||||
|
||||
// For more advanced text processing
|
||||
//implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||
|
||||
implementation(project(":compiler"))
|
||||
|
||||
}
|
||||
|
||||
configurations.forEach { config ->
|
||||
config.resolutionStrategy {
|
||||
preferProjectModules()
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java.srcDir("src")
|
||||
resources.srcDir("resources")
|
||||
}
|
||||
|
||||
tasks.startScripts {
|
||||
applicationName = "prog8-language-server"
|
||||
}
|
||||
|
||||
tasks.register<Exec>("fixFilePermissions") {
|
||||
// When running on macOS or Linux the start script
|
||||
// needs executable permissions to run.
|
||||
|
||||
onlyIf { !System.getProperty("os.name").lowercase().contains("windows") }
|
||||
commandLine("chmod", "+x", "${tasks.installDist.get().destinationDir}/bin/prog8-language-server")
|
||||
}
|
||||
|
||||
tasks.register<JavaExec>("debugRun") {
|
||||
mainClass.set(serverMainClassName)
|
||||
classpath(sourceSets.main.get().runtimeClasspath)
|
||||
standardInput = System.`in`
|
||||
|
||||
jvmArgs(debugArgs)
|
||||
doLast {
|
||||
println("Using debug port $debugPort")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<CreateStartScripts>("debugStartScripts") {
|
||||
applicationName = "prog8-language-server"
|
||||
mainClass.set(serverMainClassName)
|
||||
outputDir = tasks.installDist.get().destinationDir.toPath().resolve("bin").toFile()
|
||||
classpath = tasks.startScripts.get().classpath
|
||||
defaultJvmOpts = listOf(debugArgs)
|
||||
}
|
||||
|
||||
tasks.register<Sync>("installDebugDist") {
|
||||
dependsOn("installDist")
|
||||
finalizedBy("debugStartScripts")
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
// Disable tests for now since we don't have any
|
||||
enabled = false
|
||||
}
|
||||
|
||||
tasks.installDist {
|
||||
finalizedBy("fixFilePermissions")
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
finalizedBy("installDist")
|
||||
}
|
14
languageServer/languageServer.iml
Normal file
14
languageServer/languageServer.iml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="eclipse.lsp4j" level="project" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compiler" />
|
||||
</component>
|
||||
</module>
|
34
languageServer/src/prog8lsp/AsyncExecutor.kt
Normal file
34
languageServer/src/prog8lsp/AsyncExecutor.kt
Normal file
@@ -0,0 +1,34 @@
|
||||
package prog8lsp
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Supplier
|
||||
|
||||
private var threadCount = 0
|
||||
|
||||
class AsyncExecutor {
|
||||
private val workerThread = Executors.newSingleThreadExecutor { Thread(it, "async${threadCount++}") }
|
||||
|
||||
fun execute(task: () -> Unit) =
|
||||
CompletableFuture.runAsync(Runnable(task), workerThread)
|
||||
|
||||
fun <R> compute(task: () -> R) =
|
||||
CompletableFuture.supplyAsync(Supplier(task), workerThread)
|
||||
|
||||
fun <R> computeOr(defaultValue: R, task: () -> R?) =
|
||||
CompletableFuture.supplyAsync(Supplier {
|
||||
try {
|
||||
task() ?: defaultValue
|
||||
} catch (e: Exception) {
|
||||
defaultValue
|
||||
}
|
||||
}, workerThread)
|
||||
|
||||
fun shutdown(awaitTermination: Boolean) {
|
||||
workerThread.shutdown()
|
||||
if (awaitTermination) {
|
||||
workerThread.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS)
|
||||
}
|
||||
}
|
||||
}
|
22
languageServer/src/prog8lsp/Main.kt
Normal file
22
languageServer/src/prog8lsp/Main.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package prog8lsp
|
||||
|
||||
import org.eclipse.lsp4j.launch.LSPLauncher
|
||||
import prog8.buildversion.VERSION
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
Logger.getLogger("").level = Level.INFO
|
||||
|
||||
val inStream = System.`in`
|
||||
val outStream = System.out
|
||||
val server = Prog8LanguageServer()
|
||||
val threads = Executors.newCachedThreadPool { Thread(it, "client") }
|
||||
val launcher = LSPLauncher.createServerLauncher(server, inStream, outStream, threads) { it }
|
||||
|
||||
server.connect(launcher.remoteProxy)
|
||||
launcher.startListening()
|
||||
|
||||
println("Prog8 Language Server started. Prog8 version: ${VERSION}")
|
||||
}
|
85
languageServer/src/prog8lsp/Prog8LanguageServer.kt
Normal file
85
languageServer/src/prog8lsp/Prog8LanguageServer.kt
Normal file
@@ -0,0 +1,85 @@
|
||||
package prog8lsp
|
||||
|
||||
import org.eclipse.lsp4j.*
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either
|
||||
import org.eclipse.lsp4j.services.*
|
||||
import java.io.Closeable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.CompletableFuture.completedFuture
|
||||
import java.util.logging.Logger
|
||||
|
||||
class Prog8LanguageServer: LanguageServer, LanguageClientAware, Closeable {
|
||||
private lateinit var client: LanguageClient
|
||||
private val textDocuments = Prog8TextDocumentService()
|
||||
private val workspaces = Prog8WorkspaceService()
|
||||
private val async = AsyncExecutor()
|
||||
private val logger = Logger.getLogger(Prog8LanguageServer::class.simpleName)
|
||||
|
||||
override fun initialize(params: InitializeParams): CompletableFuture<InitializeResult> = async.compute {
|
||||
logger.info("Initializing LanguageServer")
|
||||
|
||||
val result = InitializeResult()
|
||||
val capabilities = ServerCapabilities()
|
||||
|
||||
// Text document synchronization
|
||||
capabilities.textDocumentSync = Either.forLeft(TextDocumentSyncKind.Full)
|
||||
|
||||
// Completion support
|
||||
val completionOptions = CompletionOptions()
|
||||
completionOptions.resolveProvider = true
|
||||
completionOptions.triggerCharacters = listOf(".", ":")
|
||||
capabilities.completionProvider = completionOptions
|
||||
|
||||
// Document symbol support
|
||||
capabilities.documentSymbolProvider = Either.forLeft(true)
|
||||
|
||||
// Hover support
|
||||
capabilities.hoverProvider = Either.forLeft(true)
|
||||
|
||||
// Definition support
|
||||
capabilities.definitionProvider = Either.forLeft(true)
|
||||
|
||||
// Code action support
|
||||
val codeActionOptions = CodeActionOptions()
|
||||
codeActionOptions.codeActionKinds = listOf(CodeActionKind.QuickFix)
|
||||
capabilities.codeActionProvider = Either.forRight(codeActionOptions)
|
||||
|
||||
// Document formatting support
|
||||
capabilities.documentFormattingProvider = Either.forLeft(true)
|
||||
capabilities.documentRangeFormattingProvider = Either.forLeft(true)
|
||||
|
||||
// Rename support
|
||||
val renameOptions = RenameOptions()
|
||||
renameOptions.prepareProvider = true
|
||||
capabilities.renameProvider = Either.forRight(renameOptions)
|
||||
|
||||
// Workspace symbol support
|
||||
capabilities.workspaceSymbolProvider = Either.forLeft(true)
|
||||
|
||||
result.capabilities = capabilities
|
||||
result
|
||||
}
|
||||
|
||||
override fun shutdown(): CompletableFuture<Any> {
|
||||
close()
|
||||
return completedFuture(null)
|
||||
}
|
||||
|
||||
override fun exit() { }
|
||||
|
||||
override fun getTextDocumentService(): TextDocumentService = textDocuments
|
||||
|
||||
override fun getWorkspaceService(): WorkspaceService = workspaces
|
||||
|
||||
override fun connect(client: LanguageClient) {
|
||||
logger.info("connecting to language client")
|
||||
this.client = client
|
||||
workspaces.connect(client)
|
||||
textDocuments.connect(client)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
logger.info("closing down")
|
||||
async.shutdown(awaitTermination = true)
|
||||
}
|
||||
}
|
389
languageServer/src/prog8lsp/Prog8TextDocumentService.kt
Normal file
389
languageServer/src/prog8lsp/Prog8TextDocumentService.kt
Normal file
@@ -0,0 +1,389 @@
|
||||
package prog8lsp
|
||||
|
||||
import org.eclipse.lsp4j.*
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either
|
||||
import org.eclipse.lsp4j.services.LanguageClient
|
||||
import org.eclipse.lsp4j.services.TextDocumentService
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.logging.Logger
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
// Document model to maintain in memory
|
||||
data class Prog8Document(
|
||||
val uri: String,
|
||||
var text: String,
|
||||
var version: Int
|
||||
)
|
||||
|
||||
class Prog8TextDocumentService: TextDocumentService {
|
||||
private var client: LanguageClient? = null
|
||||
private val async = AsyncExecutor()
|
||||
private val logger = Logger.getLogger(Prog8TextDocumentService::class.simpleName)
|
||||
|
||||
// In-memory document store
|
||||
private val documents = mutableMapOf<String, Prog8Document>()
|
||||
|
||||
fun connect(client: LanguageClient) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
override fun didOpen(params: DidOpenTextDocumentParams) {
|
||||
logger.info("didOpen: ${params.textDocument.uri}")
|
||||
|
||||
// Create and store document model
|
||||
val document = Prog8Document(
|
||||
uri = params.textDocument.uri,
|
||||
text = params.textDocument.text,
|
||||
version = params.textDocument.version
|
||||
)
|
||||
documents[params.textDocument.uri] = document
|
||||
|
||||
// Trigger diagnostics when a document is opened
|
||||
validateDocument(document)
|
||||
}
|
||||
|
||||
override fun didChange(params: DidChangeTextDocumentParams) {
|
||||
logger.info("didChange: ${params.textDocument.uri}")
|
||||
|
||||
// Get the document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
// Update document version
|
||||
document.version = params.textDocument.version
|
||||
|
||||
// Apply changes to the document text
|
||||
// For simplicity, we're assuming full document sync (TextDocumentSyncKind.Full)
|
||||
// In a real implementation, you might need to handle incremental changes
|
||||
val text = params.contentChanges.firstOrNull()?.text
|
||||
if (text != null) {
|
||||
document.text = text
|
||||
}
|
||||
|
||||
// Trigger diagnostics when a document changes
|
||||
validateDocument(document)
|
||||
}
|
||||
}
|
||||
|
||||
override fun didClose(params: DidCloseTextDocumentParams) {
|
||||
logger.info("didClose: ${params.textDocument.uri}")
|
||||
|
||||
// Remove document from our store
|
||||
documents.remove(params.textDocument.uri)
|
||||
|
||||
// Clear diagnostics when a document is closed
|
||||
client?.publishDiagnostics(PublishDiagnosticsParams(params.textDocument.uri, listOf()))
|
||||
}
|
||||
|
||||
override fun didSave(params: DidSaveTextDocumentParams) {
|
||||
logger.info("didSave: ${params.textDocument.uri}")
|
||||
// Handle save events if needed
|
||||
}
|
||||
|
||||
override fun documentSymbol(params: DocumentSymbolParams): CompletableFuture<MutableList<Either<SymbolInformation, DocumentSymbol>>> = async.compute {
|
||||
logger.info("Find symbols in ${params.textDocument.uri}")
|
||||
val result: MutableList<Either<SymbolInformation, DocumentSymbol>>
|
||||
val time = measureTimeMillis {
|
||||
result = mutableListOf()
|
||||
|
||||
// Get document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
// Parse document and extract symbols
|
||||
// This is just a placeholder implementation
|
||||
val range = Range(Position(0, 0), Position(0, 10))
|
||||
val selectionRange = Range(Position(0, 0), Position(0, 10))
|
||||
val symbol = DocumentSymbol("exampleSymbol", SymbolKind.Function, range, selectionRange)
|
||||
result.add(Either.forRight(symbol))
|
||||
}
|
||||
}
|
||||
logger.info("Finished in $time ms")
|
||||
result
|
||||
}
|
||||
|
||||
override fun completion(params: CompletionParams): CompletableFuture<Either<MutableList<CompletionItem>, CompletionList>> = async.compute {
|
||||
logger.info("Completion for ${params.textDocument.uri} at ${params.position}")
|
||||
val result: Either<MutableList<CompletionItem>, CompletionList>
|
||||
val time = measureTimeMillis {
|
||||
val items = mutableListOf<CompletionItem>()
|
||||
|
||||
// Get document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
// Implement actual completion logic based on context
|
||||
// This is just a placeholder implementation
|
||||
val printItem = CompletionItem("print")
|
||||
printItem.kind = CompletionItemKind.Function
|
||||
printItem.detail = "Print text to console"
|
||||
printItem.documentation = Either.forLeft("Outputs the given text to the console")
|
||||
|
||||
val forItem = CompletionItem("for")
|
||||
forItem.kind = CompletionItemKind.Keyword
|
||||
forItem.detail = "For loop"
|
||||
forItem.documentation = Either.forLeft("Iterates over a range or collection")
|
||||
|
||||
val ifItem = CompletionItem("if")
|
||||
ifItem.kind = CompletionItemKind.Keyword
|
||||
ifItem.detail = "Conditional statement"
|
||||
ifItem.documentation = Either.forLeft("Executes code based on a condition")
|
||||
|
||||
items.add(printItem)
|
||||
items.add(forItem)
|
||||
items.add(ifItem)
|
||||
}
|
||||
|
||||
val list = CompletionList(false, items)
|
||||
result = Either.forRight(list)
|
||||
}
|
||||
logger.info("Finished in $time ms")
|
||||
result
|
||||
}
|
||||
|
||||
override fun hover(params: HoverParams): CompletableFuture<Hover?> = async.compute {
|
||||
logger.info("Hover for ${params.textDocument.uri} at ${params.position}")
|
||||
|
||||
// Get document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
// Simple implementation that checks for keywords at the position
|
||||
val keyword = getWordAtPosition(document, params.position)
|
||||
|
||||
when (keyword) {
|
||||
"print" -> {
|
||||
val hover = Hover()
|
||||
hover.contents = Either.forLeft(listOf(Either.forLeft("**print** - Outputs text to the console\n\n```prog8\nprint \"Hello, World!\"\n```")))
|
||||
return@compute hover
|
||||
}
|
||||
"for" -> {
|
||||
val hover = Hover()
|
||||
hover.contents = Either.forLeft(listOf(Either.forLeft("**for** - Loop construct\n\n```prog8\nfor i in 0..10 {\n print i\n}\n```")))
|
||||
return@compute hover
|
||||
}
|
||||
"if" -> {
|
||||
val hover = Hover()
|
||||
hover.contents = Either.forLeft(listOf(Either.forLeft("**if** - Conditional statement\n\n```prog8\nif x > 5 {\n print \"x is greater than 5\"\n}\n```")))
|
||||
return@compute hover
|
||||
}
|
||||
"sub" -> {
|
||||
val hover = Hover()
|
||||
hover.contents = Either.forLeft(listOf(Either.forLeft("**sub** - Defines a subroutine\n\n```prog8\nsub myFunction() {\n print \"Hello from function\"\n}\n```")))
|
||||
return@compute hover
|
||||
}
|
||||
else -> {
|
||||
// Return null for unknown symbols
|
||||
return@compute null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return null if document not found
|
||||
null
|
||||
}
|
||||
|
||||
override fun definition(params: DefinitionParams): CompletableFuture<Either<MutableList<out Location>, MutableList<out LocationLink>>> = async.compute {
|
||||
logger.info("Definition request for ${params.textDocument.uri} at ${params.position}")
|
||||
|
||||
// Get document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
// Implement actual definition lookup
|
||||
// This would involve parsing the document, finding the symbol at the position,
|
||||
// and then finding where that symbol is defined
|
||||
val locations = mutableListOf<Location>()
|
||||
|
||||
// Placeholder implementation
|
||||
// locations.add(Location("file:///path/to/definition.p8", Range(Position(0, 0), Position(0, 10))))
|
||||
|
||||
return@compute Either.forLeft(locations)
|
||||
}
|
||||
|
||||
Either.forLeft(mutableListOf<Location>())
|
||||
}
|
||||
|
||||
override fun formatting(params: DocumentFormattingParams): CompletableFuture<MutableList<out TextEdit>> = async.compute {
|
||||
logger.info("Formatting document ${params.textDocument.uri}")
|
||||
|
||||
// Get document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
// Implement actual code formatting
|
||||
// This is just a placeholder implementation
|
||||
val edits = mutableListOf<TextEdit>()
|
||||
|
||||
// Example of how you might implement formatting:
|
||||
// 1. Parse the document
|
||||
// 2. Apply formatting rules (indentation, spacing, etc.)
|
||||
// 3. Generate TextEdit objects for the changes
|
||||
|
||||
return@compute edits
|
||||
}
|
||||
|
||||
mutableListOf<TextEdit>()
|
||||
}
|
||||
|
||||
override fun rangeFormatting(params: DocumentRangeFormattingParams): CompletableFuture<MutableList<out TextEdit>> = async.compute {
|
||||
logger.info("Range formatting document ${params.textDocument.uri}")
|
||||
|
||||
// Get document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
// Implement actual code formatting for range
|
||||
// This is just a placeholder implementation
|
||||
val edits = mutableListOf<TextEdit>()
|
||||
|
||||
// Example of how you might implement range formatting:
|
||||
// 1. Parse the document range
|
||||
// 2. Apply formatting rules to the selected range
|
||||
// 3. Generate TextEdit objects for the changes
|
||||
|
||||
return@compute edits
|
||||
}
|
||||
|
||||
mutableListOf<TextEdit>()
|
||||
}
|
||||
|
||||
override fun rename(params: RenameParams): CompletableFuture<WorkspaceEdit> = async.compute {
|
||||
logger.info("Rename symbol in ${params.textDocument.uri} at ${params.position}")
|
||||
|
||||
// Get document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
// Implement actual rename functionality
|
||||
// This would involve:
|
||||
// 1. Finding all references to the symbol at the given position
|
||||
// 2. Creating TextEdit objects to rename each reference
|
||||
// 3. Adding the edits to a WorkspaceEdit
|
||||
|
||||
return@compute WorkspaceEdit()
|
||||
}
|
||||
|
||||
WorkspaceEdit()
|
||||
}
|
||||
|
||||
override fun codeAction(params: CodeActionParams): CompletableFuture<MutableList<Either<Command, CodeAction>>> = async.compute {
|
||||
logger.info("Code actions for ${params.textDocument.uri}")
|
||||
|
||||
// Get document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
val actions = mutableListOf<Either<Command, CodeAction>>()
|
||||
|
||||
// Check diagnostics to provide quick fixes
|
||||
for (diagnostic in params.context.diagnostics) {
|
||||
when (diagnostic.code?.left) {
|
||||
"UnmatchedQuotes" -> {
|
||||
val action = CodeAction()
|
||||
action.title = "Add closing quote"
|
||||
action.kind = CodeActionKind.QuickFix
|
||||
action.diagnostics = listOf(diagnostic)
|
||||
action.isPreferred = true
|
||||
// TODO: Add actual TextEdit to fix the issue
|
||||
actions.add(Either.forRight(action))
|
||||
}
|
||||
"InvalidCharacter" -> {
|
||||
val action = CodeAction()
|
||||
action.title = "Remove invalid characters"
|
||||
action.kind = CodeActionKind.QuickFix
|
||||
action.diagnostics = listOf(diagnostic)
|
||||
// TODO: Add actual TextEdit to fix the issue
|
||||
actions.add(Either.forRight(action))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add some general code actions
|
||||
val organizeImportsAction = CodeAction()
|
||||
organizeImportsAction.title = "Organize imports"
|
||||
organizeImportsAction.kind = CodeActionKind.SourceOrganizeImports
|
||||
actions.add(Either.forRight(organizeImportsAction))
|
||||
|
||||
return@compute actions
|
||||
}
|
||||
|
||||
mutableListOf<Either<Command, CodeAction>>()
|
||||
}
|
||||
|
||||
private fun getWordAtPosition(document: Prog8Document, position: Position): String {
|
||||
// Extract the word at the given position from the document text
|
||||
val lines = document.text.lines()
|
||||
if (position.line < lines.size) {
|
||||
val line = lines[position.line]
|
||||
// Simple word extraction - in a real implementation, you'd want a more robust solution
|
||||
val words = line.split(Regex("\\s+|[^a-zA-Z0-9_]"))
|
||||
var charIndex = 0
|
||||
for (word in words) {
|
||||
if (position.character >= charIndex && position.character <= charIndex + word.length) {
|
||||
return word
|
||||
}
|
||||
charIndex += word.length + 1 // +1 for the separator
|
||||
}
|
||||
}
|
||||
return "" // Default to empty string
|
||||
}
|
||||
|
||||
private fun validateDocument(document: Prog8Document) {
|
||||
logger.info("Validating document: ${document.uri}")
|
||||
val diagnostics = mutableListOf<Diagnostic>()
|
||||
|
||||
// Split text into lines for easier processing
|
||||
val lines = document.text.lines()
|
||||
|
||||
// Check for syntax errors
|
||||
for ((lineNumber, line) in lines.withIndex()) {
|
||||
// Check for unmatched quotes
|
||||
val quoteCount = line.count { it == '"' }
|
||||
if (quoteCount % 2 != 0) {
|
||||
val range = Range(Position(lineNumber, 0), Position(lineNumber, line.length))
|
||||
val diagnostic = Diagnostic(
|
||||
range,
|
||||
"Unmatched quotes",
|
||||
DiagnosticSeverity.Error,
|
||||
"prog8-lsp",
|
||||
"UnmatchedQuotes"
|
||||
)
|
||||
diagnostics.add(diagnostic)
|
||||
}
|
||||
|
||||
// Check for invalid characters
|
||||
if (line.contains(Regex("[^\\u0000-\\u007F]"))) {
|
||||
val range = Range(Position(lineNumber, 0), Position(lineNumber, line.length))
|
||||
val diagnostic = Diagnostic(
|
||||
range,
|
||||
"Invalid character found",
|
||||
DiagnosticSeverity.Error,
|
||||
"prog8-lsp",
|
||||
"InvalidCharacter"
|
||||
)
|
||||
diagnostics.add(diagnostic)
|
||||
}
|
||||
|
||||
// Check for common Prog8 syntax issues
|
||||
// For example, check if a line starts with a keyword but doesn't follow proper syntax
|
||||
if (line.trim().startsWith("sub ") && !line.contains("(")) {
|
||||
val range = Range(Position(lineNumber, 0), Position(lineNumber, line.length))
|
||||
val diagnostic = Diagnostic(
|
||||
range,
|
||||
"Subroutine declaration missing parentheses",
|
||||
DiagnosticSeverity.Error,
|
||||
"prog8-lsp",
|
||||
"InvalidSubroutine"
|
||||
)
|
||||
diagnostics.add(diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for other issues
|
||||
if (document.text.contains("error")) {
|
||||
val range = Range(Position(0, 0), Position(0, 5))
|
||||
val diagnostic = Diagnostic(
|
||||
range,
|
||||
"This is a sample diagnostic",
|
||||
DiagnosticSeverity.Warning,
|
||||
"prog8-lsp",
|
||||
"SampleDiagnostic"
|
||||
)
|
||||
diagnostics.add(diagnostic)
|
||||
}
|
||||
|
||||
client?.publishDiagnostics(PublishDiagnosticsParams(document.uri, diagnostics))
|
||||
}
|
||||
}
|
89
languageServer/src/prog8lsp/Prog8WorkspaceService.kt
Normal file
89
languageServer/src/prog8lsp/Prog8WorkspaceService.kt
Normal file
@@ -0,0 +1,89 @@
|
||||
package prog8lsp
|
||||
|
||||
import org.eclipse.lsp4j.*
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either
|
||||
import org.eclipse.lsp4j.services.LanguageClient
|
||||
import org.eclipse.lsp4j.services.WorkspaceService
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.logging.Logger
|
||||
|
||||
class Prog8WorkspaceService: WorkspaceService {
|
||||
private var client: LanguageClient? = null
|
||||
private val logger = Logger.getLogger(Prog8WorkspaceService::class.simpleName)
|
||||
|
||||
fun connect(client: LanguageClient) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
override fun executeCommand(params: ExecuteCommandParams): CompletableFuture<Any> {
|
||||
logger.info("executeCommand $params")
|
||||
return super.executeCommand(params)
|
||||
}
|
||||
|
||||
override fun symbol(params: WorkspaceSymbolParams): CompletableFuture<Either<MutableList<out SymbolInformation>, MutableList<out WorkspaceSymbol>>> {
|
||||
logger.info("symbol $params")
|
||||
// TODO: Implement workspace symbol search
|
||||
// This is just a placeholder implementation
|
||||
val symbols = mutableListOf<WorkspaceSymbol>()
|
||||
val symbol = WorkspaceSymbol(
|
||||
"workspaceSymbol",
|
||||
SymbolKind.Function,
|
||||
Either.forLeft(Location("file:///example.p8", Range(Position(0, 0), Position(0, 10))))
|
||||
)
|
||||
symbols.add(symbol)
|
||||
return CompletableFuture.completedFuture(Either.forRight(symbols))
|
||||
}
|
||||
|
||||
override fun resolveWorkspaceSymbol(workspaceSymbol: WorkspaceSymbol): CompletableFuture<WorkspaceSymbol> {
|
||||
logger.info("resolveWorkspaceSymbol $workspaceSymbol")
|
||||
return CompletableFuture.completedFuture(workspaceSymbol)
|
||||
}
|
||||
|
||||
override fun didChangeConfiguration(params: DidChangeConfigurationParams) {
|
||||
logger.info("didChangeConfiguration: $params")
|
||||
}
|
||||
|
||||
override fun didChangeWatchedFiles(params: DidChangeWatchedFilesParams) {
|
||||
logger.info("didChangeWatchedFiles: $params")
|
||||
}
|
||||
|
||||
override fun didChangeWorkspaceFolders(params: DidChangeWorkspaceFoldersParams) {
|
||||
logger.info("didChangeWorkspaceFolders $params")
|
||||
super.didChangeWorkspaceFolders(params)
|
||||
}
|
||||
|
||||
override fun willCreateFiles(params: CreateFilesParams): CompletableFuture<WorkspaceEdit> {
|
||||
logger.info("willCreateFiles $params")
|
||||
return super.willCreateFiles(params)
|
||||
}
|
||||
|
||||
override fun didCreateFiles(params: CreateFilesParams) {
|
||||
logger.info("didCreateFiles $params")
|
||||
super.didCreateFiles(params)
|
||||
}
|
||||
|
||||
override fun willRenameFiles(params: RenameFilesParams): CompletableFuture<WorkspaceEdit> {
|
||||
logger.info("willRenameFiles $params")
|
||||
return super.willRenameFiles(params)
|
||||
}
|
||||
|
||||
override fun didRenameFiles(params: RenameFilesParams) {
|
||||
logger.info("didRenameFiles $params")
|
||||
super.didRenameFiles(params)
|
||||
}
|
||||
|
||||
override fun willDeleteFiles(params: DeleteFilesParams): CompletableFuture<WorkspaceEdit> {
|
||||
logger.info("willDeleteFiles $params")
|
||||
return super.willDeleteFiles(params)
|
||||
}
|
||||
|
||||
override fun didDeleteFiles(params: DeleteFilesParams) {
|
||||
logger.info("didDeleteFiles $params")
|
||||
super.didDeleteFiles(params)
|
||||
}
|
||||
|
||||
override fun diagnostic(params: WorkspaceDiagnosticParams): CompletableFuture<WorkspaceDiagnosticReport> {
|
||||
logger.info("diagnostic $params")
|
||||
return super.diagnostic(params)
|
||||
}
|
||||
}
|
@@ -10,5 +10,6 @@ include(
|
||||
':codeGenCpu6502',
|
||||
':codeGenExperimental',
|
||||
':compiler',
|
||||
':beanshell'
|
||||
':beanshell',
|
||||
':languageServer'
|
||||
)
|
||||
|
@@ -2358,6 +2358,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
IRDataType.BYTE -> {
|
||||
val value = registers.getUW(i.reg2!!)
|
||||
registers.setUB(i.reg1!!, value.toUByte())
|
||||
statusbitsNZ(value.toInt(), i.type!!)
|
||||
}
|
||||
IRDataType.WORD -> throw IllegalArgumentException("lsig.w not yet supported, requires 32-bits registers")
|
||||
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
@@ -2370,6 +2371,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
IRDataType.BYTE -> {
|
||||
val value = registers.getUW(i.reg2!!)
|
||||
val newValue = value.toInt() ushr 8
|
||||
statusbitsNZ(newValue, i.type!!)
|
||||
registers.setUB(i.reg1!!, newValue.toUByte())
|
||||
}
|
||||
IRDataType.WORD -> throw IllegalArgumentException("msig.w not yet supported, requires 32-bits registers")
|
||||
@@ -2383,7 +2385,9 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
IRDataType.BYTE -> {
|
||||
val msb = registers.getUB(i.reg2!!)
|
||||
val lsb = registers.getUB(i.reg3!!)
|
||||
registers.setUW(i.reg1!!, ((msb.toInt() shl 8) or lsb.toInt()).toUShort())
|
||||
val value = ((msb.toInt() shl 8) or lsb.toInt())
|
||||
registers.setUW(i.reg1!!, value.toUShort())
|
||||
statusbitsNZ(value.toInt(), i.type!!)
|
||||
}
|
||||
IRDataType.WORD -> throw IllegalArgumentException("concat.w not yet supported, requires 32-bits registers")
|
||||
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
|
@@ -24,6 +24,7 @@ class TestVm: FunSpec( {
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion ="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
@@ -94,7 +95,7 @@ class TestVm: FunSpec( {
|
||||
test("vmrunner") {
|
||||
val runner = VmRunner()
|
||||
val irSource="""<?xml version="1.0" encoding="utf-8"?>
|
||||
<PROGRAM NAME="test">
|
||||
<PROGRAM NAME="test" COMPILERVERSION="99.99">
|
||||
<OPTIONS>
|
||||
</OPTIONS>
|
||||
|
||||
|
Reference in New Issue
Block a user