Compare commits

...

8 Commits

Author SHA1 Message Date
Irmen de Jong
87c1bbbf40 readme about LSP 2025-10-01 01:15:00 +02:00
Irmen de Jong
a7ad6abdb9 Merge branch 'languageServer'
# Conflicts:
#	examples/test.p8
2025-09-30 22:14:03 +02:00
Irmen de Jong
a611406020 IR: add compilerversion attribute to p8ir file 2025-09-30 22:07:04 +02:00
Irmen de Jong
75da38224d IR: make LSIG,MSIG,CONCAT instruction set flags to skip cmp #0 afterwards (if msb(x)>0) 2025-09-30 21:48:57 +02:00
Irmen de Jong
8d6f3301c8 conv.any2uword() return values have been changed to be more useful and convenient
now returns both the actual value and the number of characters, and the return values for the virtual target now matches the others.
2025-09-30 20:44:02 +02:00
Irmen de Jong
86b52a1c5e fix endless loop in rewriting type of const long values 2025-09-29 22:28:13 +02:00
Irmen de Jong
2c8b1c2022 moved cx16.cpu_is_65816() to sys.cpu_is_65816(). It know also does proper detection on the C64 and C128 like on the X16, because those two computers can also have this CPU via a SuperCPU expansion. 2025-09-29 21:37:26 +02:00
Irmen de Jong
ab1f065752 first setup of LSP languageserver 2025-09-27 15:27:37 +02:00
42 changed files with 929 additions and 135 deletions

1
.idea/modules.xml generated
View File

@@ -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" />

View File

@@ -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,

View File

@@ -31,6 +31,7 @@ class TestCodegen: FunSpec({
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)

View File

@@ -23,6 +23,7 @@ class TestIRPeepholeOpt: FunSpec({
noSysInit = true,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)

View File

@@ -25,6 +25,7 @@ class TestVmCodeGen: FunSpec({
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)

View File

@@ -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)) {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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
)
}

View File

@@ -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 {

View File

@@ -21,6 +21,7 @@ class TestGoldenRam: FunSpec({
noSysInit = false,
romable = false,
compTarget = VMTarget(),
compilerVersion="99.99",
loadAddress = 999u,
memtopAddress = 0xffffu
)

View File

@@ -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>

View File

@@ -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

View File

@@ -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
}
})

View File

@@ -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()

View File

@@ -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!

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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++
}
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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()

View File

@@ -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(

View File

@@ -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

View 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

View 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")
}

View 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>

View 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)
}
}
}

View 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}")
}

View 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)
}
}

View 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))
}
}

View 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)
}
}

View File

@@ -10,5 +10,6 @@ include(
':codeGenCpu6502',
':codeGenExperimental',
':compiler',
':beanshell'
':beanshell',
':languageServer'
)

View File

@@ -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")

View File

@@ -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>