Compare commits

..

1 Commits

Author SHA1 Message Date
Irmen de Jong
9daa116148 first setup of LSP languageserver 2025-07-15 20:24:45 +02:00
35 changed files with 422 additions and 257 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

@@ -328,9 +328,10 @@ internal class ProgramAndVarsGen(
if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.block")
initializers.forEach { assign ->
val constvalue = assign.value as? PtNumber
if(constvalue==null || constvalue.number!=0.0 || allocator.isZpVar(assign.target.identifier!!.name))
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.translate(assign)
else
throw AssemblyError("non-zp variable should not be initialized to zero; it will be zeroed as part of BSS clear")
// the other variables that should be set to zero are done so as part of the BSS section clear.
}
asmgen.out(" rts\n .bend")

View File

@@ -75,7 +75,7 @@ class IRCodeGen(
initsToRemove += block to initialization
}
is PtNumber -> {
require(initValue.number!=0.0 || variable.zpwish!=ZeropageWish.NOT_IN_ZEROPAGE) {"non-zp variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
require(initValue.number!=0.0) { "variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.number)
initsToRemove += block to initialization
}

View File

@@ -19,7 +19,7 @@ class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
if("ignore_unused" !in parent.definingBlock.options())
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
errors.info("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
}

View File

@@ -376,7 +376,7 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; note: this can be even faster if we also have a 320 word x-lookup table, but hey, that's a lot of memory.
; see https://codebase64.net/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
; We use the 64tass syntax for range expressions to calculate this table on assembly time.

View File

@@ -3,7 +3,7 @@
;
; some more interesting routines can be found here:
; http://6502org.wikidot.com/software-math
; https://codebase64.net/doku.php?id=base:6502_6510_maths
; http://codebase64.org/doku.php?id=base:6502_6510_maths
; https://github.com/TobyLobster/multiply_test
; https://github.com/TobyLobster/sqrt_test
@@ -353,7 +353,7 @@ _divisor .word ?
randword .proc
; -- 16 bit pseudo random number generator into AY
; default seed = $00c2 $1137. NOTE: uses self-modifying code so won't work in ROM (use randword_rom instead)
; routine from https://codebase64.net/doku.php?id=6502_6510_maths:x_abc_random_number_generator_8_16_bit
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
inc x1
clc
x1=*+1
@@ -377,7 +377,7 @@ b1=*+1
randword_rom .proc
; -- 16 bit pseudo random number generator into AY. Can run from ROM.
; NOTE: you have to set the initial seed using randseed_rom! (a good default seed = $00c2 $1137)
; routine from https://codebase64.net/doku.php?id=6502_6510_maths:x_abc_random_number_generator_8_16_bit
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
inc _x1
clc
lda _x1

View File

@@ -380,7 +380,7 @@ _quadrant_region_to_direction:
asmsub atan2(ubyte x1 @R0, ubyte y1 @R1, ubyte x2 @R2, ubyte y2 @R3) -> ubyte @A {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
;; http://codebase64.net/doku.php?id=base:8bit_atan2_8-bit_angle
;; https://www.codebase64.org/doku.php?id=base:8bit_atan2_8-bit_angle
;; This uses 2 large lookup tables so uses a lot of memory but is super fast.
%asm {{

View File

@@ -138,8 +138,8 @@ diskio {
; you can call this multiple times to append more data
repeat num_bytes {
%ir {{
loadm.w r99000,diskio.f_write.bufferpointer
loadi.b r99100,r99000
loadm.w r0,diskio.f_write.bufferpointer
loadi.b r99100,r0
syscall 55 (r99100.b): r99100.b
storem.b r99100,$ff02
}}
@@ -216,12 +216,12 @@ diskio {
}
; like save() but omits the 2 byte prg header.
sub save_raw(uword filenameptr, uword start_address, uword savesize) -> bool {
sub save_raw(uword filenameptr, uword startaddress, uword savesize) -> bool {
%ir {{
load.b r99100,1
loadm.w r99000,diskio.save_raw.filenameptr
loadm.w r99001,diskio.save_raw.start_address
loadm.w r99002,diskio.save_raw.savesize
loadm.w r99000,diskio.save.filenameptr
loadm.w r99001,diskio.save.start_address
loadm.w r99002,diskio.save.savesize
syscall 42 (r99100.b, r99000.w, r99001.w, r99002.w): r99100.b
returnr.b r99100
}}

View File

@@ -468,7 +468,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report()
program.constantFold(errors, compilerOptions)
errors.report()
program.reorderStatements(compilerOptions, errors)
program.reorderStatements(errors)
errors.report()
program.desugaring(errors, compilerOptions)
errors.report()

View File

@@ -1470,17 +1470,6 @@ internal class AstChecker(private val program: Program,
}
override fun visit(functionCallStatement: FunctionCallStatement) {
if(functionCallStatement.target.nameInSource.size==1) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in program.builtinFunctions.purefunctionNames) {
if("ignore_unused" !in functionCallStatement.parent.definingBlock.options()) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return
}
}
}
// most function calls, even to builtin functions, are still regular FunctionCall nodes here.
// they get converted to the more specialized node type in BeforeAsmTypecastCleaner
val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors)

View File

@@ -20,8 +20,8 @@ internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: Compila
checker.visit(this)
}
internal fun Program.reorderStatements(options: CompilationOptions, errors: IErrorReporter) {
val reorder = StatementReorderer(this, options, errors)
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
if(errors.noErrors()) {
reorder.applyModifications()

View File

@@ -165,11 +165,8 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
}
if(srcAssign.origin == AssignmentOrigin.VARINIT && srcAssign.parent is Block && srcAssign.value.constValue(program)?.number==0.0) {
val zeropages = srcAssign.target.targetIdentifiers().mapNotNull { it.targetVarDecl()?.zeropage }
if(zeropages.any {it==ZeropageWish.NOT_IN_ZEROPAGE})
throw FatalAstException("should not have a redundant block-level variable=0 assignment for a non-ZP variable; it will be zeroed as part of BSS clear")
}
if(srcAssign.origin == AssignmentOrigin.VARINIT && srcAssign.parent is Block && srcAssign.value.constValue(program)?.number==0.0)
throw FatalAstException("should not have a redundant block-level variable=0 assignment; it will be zeroed as part of BSS clear")
val assign = PtAssignment(srcAssign.position, srcAssign.origin==AssignmentOrigin.VARINIT)
val multi = srcAssign.target.multi

View File

@@ -133,7 +133,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
is PtNumber,
is PtRange,
is PtString -> true
is PtIdentifier -> true // actually PtIdentifier IS "complex" this time (it's a variable that might change) but it's kinda annoying to give a warning message for this very common case
// note that unlike most other times, PtIdentifier IS "complex" this time (it's a variable that might change)
else -> false
}

View File

@@ -5,11 +5,13 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.core.AssociativeOperators
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
internal class StatementReorderer(
val program: Program,
val options: CompilationOptions,
val errors: IErrorReporter
) : AstWalker() {
// Reorders the statements in a way the compiler needs.
@@ -112,18 +114,11 @@ internal class StatementReorderer(
}
private fun canSkipInitializationWith0(decl: VarDecl): Boolean {
if(decl.parent is Block) {
// if the variable is declared in a block and is NOT in ZEROPAGE, we can omit the init with 0 because
// if the variable is declared in a block, we can omit the init with 0 because
// the variable will be initialized to zero when the BSS section is cleared as a whole.
if (decl.zeropage == ZeropageWish.NOT_IN_ZEROPAGE)
if(decl.parent is Block)
return true
// block level zp var that is not in zeropage, doesn't have to be cleared (will be done as part of bss clear at startup)
// note: subroutine level var HAS to be cleared because it needs to be zero at every subroutine call!
if (decl.zeropage == ZeropageWish.DONTCARE && options.zeropage == ZeropageType.DONTUSE)
return true
}
// if there is an assignment to the variable below it (regular assign, or For loop),
// and there is nothing important in between, we can skip the initialization.
val statements = (decl.parent as? IStatementContainer)?.statements ?: return false

View File

@@ -5,7 +5,6 @@ import io.kotest.engine.spec.tempdir
import io.kotest.inspectors.shouldForAll
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
import prog8.ast.statements.FunctionCallStatement
@@ -14,7 +13,6 @@ import prog8.code.core.BuiltinFunctions
import prog8.code.core.RegisterOrPair
import prog8.code.core.isNumeric
import prog8.code.target.Cx16Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
class TestBuiltinFunctions: FunSpec({
@@ -105,31 +103,5 @@ main {
compileText(Cx16Target(), true, src, outputDir, writeAssembly = true) shouldNotBe null
}
test("warning for return value discarding of pure functions") {
val src="""
main {
sub start() {
word @shared ww = 2222
abs(ww)
sgn(ww)
sqrt(ww)
min(ww, 0)
max(ww, 0)
clamp(ww, 0, 319)
}
}"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(Cx16Target(), true, src, outputDir, errors=errors, writeAssembly = false) shouldNotBe null
errors.warnings.size shouldBe 6
errors.warnings[0] shouldContain "statement has no effect"
errors.warnings[1] shouldContain "statement has no effect"
errors.warnings[2] shouldContain "statement has no effect"
errors.warnings[3] shouldContain "statement has no effect"
errors.warnings[4] shouldContain "statement has no effect"
errors.warnings[5] shouldContain "statement has no effect"
}
})

View File

@@ -10,10 +10,7 @@ import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.code.ast.PtAssignment
import prog8.code.ast.PtNumber
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@@ -109,7 +106,6 @@ class TestVariables: FunSpec({
test("global var init with array lookup should sometimes be const") {
val src="""
%zeropage dontuse
main {
bool[] barray = [true, false, true, false]
@@ -224,85 +220,4 @@ main {
(st[5] as Assignment).target.identifier?.nameInSource shouldBe listOf("v1")
(st[6] as Assignment).target.identifier?.nameInSource shouldBe listOf("v0")
}
test("nondirty zp variables should be explicitly initialized to 0") {
val src="""
main {
ubyte @shared @requirezp zpvar
ubyte @shared @requirezp @dirty dirtyzpvar
sub start() {
ubyte @shared @requirezp zpvar2
ubyte @shared @requirezp @dirty dirtyzpvar2
}
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = true)!!.codegenAst
val main = result!!.allBlocks().first { it.name=="p8b_main" }
main.children.size shouldBe 4
val zeroassignlobal = main.children.single { it is PtAssignment } as PtAssignment
(zeroassignlobal.value as PtNumber).number shouldBe 0.0
zeroassignlobal.target.identifier!!.name shouldBe "p8b_main.p8v_zpvar"
val st = result.entrypoint()!!.children
st.size shouldBe 4
val zeroassign = st.single { it is PtAssignment } as PtAssignment
(zeroassign.value as PtNumber).number shouldBe 0.0
zeroassign.target.identifier!!.name shouldBe "p8b_main.p8s_start.p8v_zpvar2"
}
test("nondirty non zp variables in block scope should not be explicitly initialized to 0 (bss clear takes care of it)") {
val src="""
%zeropage dontuse
main {
ubyte @shared v1
ubyte @shared @dirty dv1
sub start() {
ubyte @shared v2
ubyte @shared @dirty dv2
}
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = true)!!.codegenAst
// block level should not be intialized to 0 (will be done by BSS clear)
val main = result!!.allBlocks().first { it.name=="p8b_main" }
main.children.size shouldBe 3
main.children.any { it is PtAssignment } shouldBe false
// subroutine should be initialized to 0 because that needs to be done on every call to the subroutine
val st = result.entrypoint()!!.children
st.size shouldBe 4
val zeroassign = st.single { it is PtAssignment } as PtAssignment
(zeroassign.value as PtNumber).number shouldBe 0.0
zeroassign.target.identifier!!.name shouldBe "p8b_main.p8s_start.p8v_v2"
}
test("nondirty explicit non zp variables in block scope should not be explicitly initialized to 0 (bss clear takes care of it)") {
val src="""
main {
ubyte @shared @nozp v1
ubyte @shared @dirty @nozp dv1
sub start() {
ubyte @shared @nozp v2
ubyte @shared @dirty @nozp dv2
}
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = true)!!.codegenAst
// block level should not be intialized to 0 (will be done by BSS clear)
val main = result!!.allBlocks().first { it.name=="p8b_main" }
main.children.size shouldBe 3
main.children.any { it is PtAssignment } shouldBe false
// subroutine should be initialized to 0 because that needs to be done on every call to the subroutine
val st = result.entrypoint()!!.children
st.size shouldBe 4
val zeroassign = st.single { it is PtAssignment } as PtAssignment
(zeroassign.value as PtNumber).number shouldBe 0.0
zeroassign.target.identifier!!.name shouldBe "p8b_main.p8s_start.p8v_v2"
}
})

View File

@@ -1,5 +1,5 @@
Prog8 compiler v11.4.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c128.p8

View File

@@ -1,5 +1,5 @@
Prog8 compiler v11.4.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c64.p8

View File

@@ -1,5 +1,5 @@
Prog8 compiler v11.4.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-cx16.p8

View File

@@ -1,5 +1,5 @@
Prog8 compiler v11.4.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-pet32.p8

View File

@@ -1,5 +1,5 @@
Prog8 compiler v11.4.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-virtual.p8
@@ -129,7 +129,7 @@ diskio {
rename (uword oldfileptr, uword newfileptr)
rmdir (str name)
save (uword filenameptr, uword start_address, uword savesize) -> bool
save_raw (uword filenameptr, uword start_address, uword savesize) -> bool
save_raw (uword filenameptr, uword startaddress, uword savesize) -> bool
status () -> str
status_code () -> ubyte
}

View File

@@ -27,7 +27,7 @@ of these library modules automatically as required.
.. note::
Several algorithms and math routines in Prog8's assembly library files are adapted from
code publicly available on https://www.codebase64.net/
code publicly available on https://www.codebase64.org/
.. _builtinfunctions:

View File

@@ -1,10 +1,6 @@
TODO
====
Since fixing the missing zp-var initialization, programs grew in size again (assem)
Are there any redundant block-level variable initializations to 0 that we can remove in peephole optimization for example?
STRUCTS: are being developed in their own separate branch for now, called "structs".
Idea is to make it feature complete in the IR/Virtual target, then merge it to master?, and then start building the 6502 code generation for it.

View File

@@ -1,84 +1,27 @@
%option no_sysinit
%zeropage kernalsafe
%import textio
%zpallowed 224,255
main {
uword @shared @requirezp zpvar1
uword @shared @requirezp zpvar2
uword @shared @requirezp zpvar3
uword @shared @requirezp zpvar4
uword @shared @requirezp zpvar5
uword @shared @requirezp @dirty dzpvar1
uword @shared @requirezp @dirty dzpvar2
uword @shared @requirezp @dirty dzpvar3
uword @shared @requirezp @dirty dzpvar4
uword @shared @requirezp @dirty dzpvar5
uword @shared @nozp var1
uword @shared @nozp var2
uword @shared @nozp var3
uword @shared @nozp var4
uword @shared @nozp var5
uword @shared @nozp @dirty dvar1
uword @shared @nozp @dirty dvar2
uword @shared @nozp @dirty dvar3
uword @shared @nozp @dirty dvar4
uword @shared @nozp @dirty dvar5
sub start() {
txt.print("address start of zpvars: ")
txt.print_uw(&zpvar1)
txt.nl()
txt.print("address start of normal vars: ")
txt.print_uw(&var1)
txt.nl()
uword uw = 9999
word sw = -2222
ubyte ub = 42
byte sb = -99
bool bb = true
txt.print("non-dirty zp should all be 0: ")
txt.print_uw(zpvar1)
txt.spc()
txt.print_uw(zpvar2)
txt.spc()
txt.print_uw(zpvar3)
txt.spc()
txt.print_uw(zpvar4)
txt.spc()
txt.print_uw(zpvar5)
txt.nl()
txt.print("non-dirty should all be 0: ")
txt.print_uw(var1)
txt.spc()
txt.print_uw(var2)
txt.spc()
txt.print_uw(var3)
txt.spc()
txt.print_uw(var4)
txt.spc()
txt.print_uw(var5)
txt.nl()
cx16.r0 = uw
cx16.r0s = sw
cx16.r0L = ub
cx16.r0H = ub
cx16.r0sL = sb
cx16.r0sH = sb
cx16.r0bL = bb
cx16.r0bH = bb
txt.print("dirty zp may be random: ")
txt.print_uw(dzpvar1)
txt.spc()
txt.print_uw(dzpvar2)
txt.spc()
txt.print_uw(dzpvar3)
txt.spc()
txt.print_uw(dzpvar4)
txt.spc()
txt.print_uw(dzpvar5)
txt.nl()
txt.print("dirty may be random: ")
txt.print_uw(dvar1)
txt.spc()
txt.print_uw(dvar2)
txt.spc()
txt.print_uw(dvar3)
txt.spc()
txt.print_uw(dvar4)
txt.spc()
txt.print_uw(dvar5)
txt.nl()
repeat {}
uw = cx16.r0
sw = cx16.r0s
ub = cx16.r0L
ub = cx16.r0H
sb = cx16.r0sL
sb = cx16.r0sH
bb = cx16.r0bL
bb = cx16.r0bH
}
}

View File

@@ -3,4 +3,4 @@ org.gradle.console=rich
org.gradle.parallel=true
org.gradle.daemon=true
kotlin.code.style=official
version=11.4.1
version=11.5-SNAPSHOT

View File

@@ -11,10 +11,8 @@ Intermediate Representation instructions for the IR Virtual machine.
Specs of the virtual machine this will run on:
Program to execute is not stored in the system memory, it's just a separate list of instructions.
100K virtual registers, 16 bits wide, can also be used as 8 bits. r0-r99999
reserved 99000 - 99099 : WORD registers for syscall arguments and response value(s)
reserved 99100 - 99199 : BYTE registers for syscall arguments and response value(s)
100K virtual floating point registers (64 bits double precision) fr0-fr99999
65536 virtual registers, 16 bits wide, can also be used as 8 bits. r0-r65535
65536 virtual floating point registers (64 bits double precision) fr0-fr65535
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,

View File

@@ -0,0 +1,103 @@
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")
}
configurations.forEach { config ->
config.resolutionStrategy {
preferProjectModules()
}
}
sourceSets.main {
java.srcDir("src")
resources.srcDir("resources")
}
sourceSets.test {
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>() {
testLogging {
events("failed")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
}
tasks.installDist {
finalizedBy("fixFilePermissions")
}
tasks.build {
finalizedBy("installDist")
}

View File

@@ -0,0 +1,13 @@
<?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" />
</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,19 @@
package prog8lsp
import org.eclipse.lsp4j.launch.LSPLauncher
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.newSingleThreadExecutor { Thread(it, "client") }
val launcher = LSPLauncher.createServerLauncher(server, inStream, outStream, threads) { it }
server.connect(launcher.remoteProxy)
launcher.startListening()
}

View File

@@ -0,0 +1,46 @@
package prog8lsp
import org.eclipse.lsp4j.InitializeParams
import org.eclipse.lsp4j.InitializeResult
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")
InitializeResult()
}
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,62 @@
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
class Prog8TextDocumentService: TextDocumentService {
private var client: LanguageClient? = null
private val async = AsyncExecutor()
private val logger = Logger.getLogger(Prog8TextDocumentService::class.simpleName)
fun connect(client: LanguageClient) {
this.client = client
}
override fun didOpen(params: DidOpenTextDocumentParams) {
logger.info("didOpen: $params")
}
override fun didChange(params: DidChangeTextDocumentParams) {
logger.info("didChange: $params")
}
override fun didClose(params: DidCloseTextDocumentParams) {
logger.info("didClose: $params")
}
override fun didSave(params: DidSaveTextDocumentParams) {
logger.info("didSave: $params")
}
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()
val range = Range(Position(1,1), Position(1,5))
val selectionRange = Range(Position(1,2), Position(1,10))
val symbol = DocumentSymbol("test-symbolName", SymbolKind.Constant, range, selectionRange)
result.add(Either.forRight(symbol))
}
logger.info("Finished in $time ms")
result
}
override fun completion(position: CompletionParams): CompletableFuture<Either<MutableList<CompletionItem>, CompletionList>> = async.compute{
logger.info("Completion for ${position}")
val result: Either<MutableList<CompletionItem>, CompletionList>
val time = measureTimeMillis {
val list = CompletionList(false, listOf(CompletionItem("test-completionItem")))
result = Either.forRight(list)
}
logger.info("Finished in $time ms")
result
}
// TODO add all other methods that get called.... :P
}

View File

@@ -0,0 +1,80 @@
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")
return super.symbol(params)
}
override fun resolveWorkspaceSymbol(workspaceSymbol: WorkspaceSymbol): CompletableFuture<WorkspaceSymbol> {
logger.info("resolveWorkspaceSymbol $workspaceSymbol")
return super.resolveWorkspaceSymbol(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

@@ -200,7 +200,7 @@ class StStaticVariable(name: String,
// Certain codegens might want to put them back into the variable directly.
// For strings and arrays this doesn't occur - these are always already specced at creation time.
require(number!=0.0 || zpwish!=ZeropageWish.NOT_IN_ZEROPAGE) { "non-zp variable should not be initialized with 0, it will already be zeroed as part of BSS clear" }
require(number!=0.0) { "variable should not be initialized with 0, it will already be zeroed as part of BSS clear" }
initializationNumericValue = number
}