mirror of
https://github.com/irmen/prog8.git
synced 2025-09-27 07:17:01 +00:00
Compare commits
3 Commits
v12.0-beta
...
languageSe
Author | SHA1 | Date | |
---|---|---|---|
|
d999637cdb | ||
|
4a78976aff | ||
|
c36799b7c6 |
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" />
|
||||
|
@@ -282,22 +282,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
"%" -> TODO("inplace ptr %")
|
||||
"<<" -> {
|
||||
if(target.dt.isByte) TODO("inplaceByteShiftLeft(target, value) ${target.position}")
|
||||
if(target.dt.isByte) inplaceByteShiftLeft(target, value)
|
||||
else if(target.dt.isWord) inplaceWordShiftLeft(target, value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
}
|
||||
">>" -> {
|
||||
if(target.dt.isByte) TODO("inplaceByteShiftRight(target, value) ${target.position}")
|
||||
if(target.dt.isByte) inplaceByteShiftRight(target, value)
|
||||
else if(target.dt.isWord) inplaceWordShiftRight(target, value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
}
|
||||
"&", "and" -> {
|
||||
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
|
||||
TODO("inplace ptr &")
|
||||
if(target.dt.isByteOrBool) inplaceByteAnd(target, value)
|
||||
else if(target.dt.isWord) inplaceWordAnd(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
"|", "or" -> {
|
||||
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
|
||||
TODO("inplace ptr |")
|
||||
if(target.dt.isByteOrBool) inplaceByteOr(target, value)
|
||||
else if(target.dt.isWord) inplaceWordOr(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
|
||||
@@ -305,12 +309,6 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
else if(target.dt.isWord) inplaceWordXor(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
"==" -> TODO("inplace ptr ==")
|
||||
"!=" -> TODO("inplace ptr !=")
|
||||
"<" -> TODO("inplace ptr <")
|
||||
"<=" -> TODO("inplace ptr <=")
|
||||
">" -> TODO("inplace ptr >")
|
||||
">=" -> TODO("inplace ptr >=")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@@ -693,7 +691,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
|
||||
if(target.dt.isSigned)
|
||||
TODO("signed word shift rigth ${target.position} $value")
|
||||
TODO("signed word shift right ${target.position} $value")
|
||||
|
||||
fun shift1unsigned() {
|
||||
asmgen.out("""
|
||||
@@ -720,21 +718,92 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
require(value.datatype.isByte)
|
||||
val varname = value.asmVarname
|
||||
TODO("<< variable")
|
||||
asmgen.out(" ldx $varname")
|
||||
asmgen.out("-")
|
||||
shift1unsigned()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isWord)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
|
||||
TODO("<< expression")
|
||||
require(value.datatype.isByte)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
|
||||
asmgen.out("-")
|
||||
shift1unsigned()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
require(value.datatype.isWord)
|
||||
require(value.datatype.isByte)
|
||||
val register = value.register!!
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_PTR"))
|
||||
require(register.isWord())
|
||||
TODO("<< register")
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
|
||||
asmgen.out("-")
|
||||
shift1unsigned()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteShiftRight(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
|
||||
if(target.dt.isSigned)
|
||||
TODO("signed byte shift right ${target.position} $value")
|
||||
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(number==1) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
lsr a
|
||||
sta ($zpPtrVar),y""")
|
||||
} else if(number>1) {
|
||||
asmgen.out("""
|
||||
ldx #$number
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- lsr a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isByte)
|
||||
val varname = value.asmVarname
|
||||
asmgen.out("""
|
||||
ldx $varname
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- lsr a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isByte)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- lsr a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
require(value.datatype.isByte)
|
||||
val register = value.register!!
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UWORD, null, target.position, register = RegisterOrPair.X))
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- lsr a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
@@ -768,21 +837,90 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
require(value.datatype.isByte)
|
||||
val varname = value.asmVarname
|
||||
TODO("<< variable")
|
||||
asmgen.out(" ldx $varname")
|
||||
asmgen.out("-")
|
||||
shift1()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isWord)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
|
||||
TODO("<< expression")
|
||||
require(value.datatype.isByte)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
|
||||
asmgen.out("-")
|
||||
shift1()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
require(value.datatype.isWord)
|
||||
require(value.datatype.isByte)
|
||||
val register = value.register!!
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_PTR"))
|
||||
require(register.isWord())
|
||||
TODO("<< register")
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
|
||||
asmgen.out("-")
|
||||
shift1()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteShiftLeft(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(number==1) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
asl a
|
||||
sta ($zpPtrVar),y""")
|
||||
} else if(number>1) {
|
||||
asmgen.out("""
|
||||
ldx #$number
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- asl a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isByte)
|
||||
val varname = value.asmVarname
|
||||
asmgen.out("""
|
||||
ldx $varname
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- asl a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isByte)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- asl a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
require(value.datatype.isByte)
|
||||
val register = value.register!!
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
|
||||
asmgen.out("-")
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- asl a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
@@ -1230,6 +1368,180 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteOr(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar)
|
||||
ora #$number
|
||||
sta ($zpPtrVar)""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
ora #$number
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
val varname = value.asmVarname
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar)
|
||||
ora $varname
|
||||
sta ($zpPtrVar)""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
ora $varname
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
ora ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> TODO("register | byte")
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceWordOr(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
ora #<$number
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
ora #>$number
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
val varname = value.asmVarname
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
ora $varname
|
||||
sta ($zpPtrVar),y
|
||||
lda ($zpPtrVar),y
|
||||
ora $varname+1
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isWord)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
ora ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
txa
|
||||
ora ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> TODO("register | word")
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteAnd(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar)
|
||||
and #$number
|
||||
sta ($zpPtrVar)""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
and #$number
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
val varname = value.asmVarname
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar)
|
||||
and $varname
|
||||
sta ($zpPtrVar)""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
and $varname
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
and ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> TODO("register & byte")
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceWordAnd(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
and #<$number
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
and #>$number
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
val varname = value.asmVarname
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
and $varname
|
||||
sta ($zpPtrVar),y
|
||||
lda ($zpPtrVar),y
|
||||
and $varname+1
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isWord)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
and ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
txa
|
||||
and ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> TODO("register & word")
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
|
||||
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
|
||||
val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
|
||||
|
@@ -90,6 +90,7 @@ Future Things and Ideas
|
||||
7 }
|
||||
8 modifications.forEach { it.perform() }
|
||||
9 }
|
||||
- improve ANTLR grammar with better error handling (according to Qwen AI)
|
||||
- allow memory() to occur in array initializer
|
||||
- %breakpoint after an assignment is parsed as part of the expression (x % breakpoint), that should not happen
|
||||
- when a complete block is removed because unused, suppress all info messages about everything in the block being removed
|
||||
|
@@ -13,20 +13,24 @@ main {
|
||||
}
|
||||
|
||||
sub start() {
|
||||
^^Node[] @shared nodeswithtype = [
|
||||
^^Node: [1,"one", 1000, true, 1.111],
|
||||
^^Node: [],
|
||||
]
|
||||
^^Node test = []
|
||||
|
||||
^^Node derp2 = ^^Foobar: []
|
||||
|
||||
^^Node[] @shared nodeswithout = [
|
||||
[2,"two", 2000, false, 2.222],
|
||||
[1,2,3,true,5],
|
||||
[]
|
||||
]
|
||||
|
||||
^^Node @shared nptrwithtype = ^^Node : [1, "one", 1000, false, 3.333]
|
||||
^^Node @shared nptrwithouttype = [1, "one", 1000, false, 3.333]
|
||||
test.id ++
|
||||
test.array += 1000
|
||||
test.id <<= 2
|
||||
test.id <<= cx16.r0L
|
||||
test.id >>= 3
|
||||
test.id >>= cx16.r0L
|
||||
test.id &= 1
|
||||
test.id *= 5 ; TODO implement this
|
||||
test.id /= 5 ; TODO implement this
|
||||
test.array ^= 1000
|
||||
test.array |= 1000
|
||||
test.array &= 1000
|
||||
test.array >>= 3
|
||||
test.array >>= cx16.r0L
|
||||
test.array <<= 2
|
||||
test.array <<= cx16.r0L
|
||||
test.array *= 5
|
||||
}
|
||||
}
|
||||
|
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'
|
||||
)
|
||||
|
Reference in New Issue
Block a user