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
29 changed files with 415 additions and 250 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$/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$/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$/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$/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$/.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" /> <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()) { if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.block") asmgen.out("prog8_init_vars\t.block")
initializers.forEach { assign -> initializers.forEach { assign ->
val constvalue = assign.value as? PtNumber if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
if(constvalue==null || constvalue.number!=0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.translate(assign) 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. // the other variables that should be set to zero are done so as part of the BSS section clear.
} }
asmgen.out(" rts\n .bend") asmgen.out(" rts\n .bend")

View File

@@ -75,7 +75,7 @@ class IRCodeGen(
initsToRemove += block to initialization initsToRemove += block to initialization
} }
is PtNumber -> { 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) variable.setOnetimeInitNumeric(initValue.number)
initsToRemove += block to initialization initsToRemove += block to initialization
} }

View File

@@ -19,7 +19,7 @@ class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0] val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) { if (functionName in functions.purefunctionNames) {
if("ignore_unused" !in parent.definingBlock.options()) 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)) 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 _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. ; 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) ; 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. ; 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: ; some more interesting routines can be found here:
; http://6502org.wikidot.com/software-math ; 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/multiply_test
; https://github.com/TobyLobster/sqrt_test ; https://github.com/TobyLobster/sqrt_test
@@ -353,7 +353,7 @@ _divisor .word ?
randword .proc randword .proc
; -- 16 bit pseudo random number generator into AY ; -- 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) ; 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 inc x1
clc clc
x1=*+1 x1=*+1
@@ -377,7 +377,7 @@ b1=*+1
randword_rom .proc randword_rom .proc
; -- 16 bit pseudo random number generator into AY. Can run from ROM. ; -- 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) ; 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 inc _x1
clc clc
lda _x1 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 { 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. ;; 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! ;; 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. ;; This uses 2 large lookup tables so uses a lot of memory but is super fast.
%asm {{ %asm {{

View File

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

View File

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

View File

@@ -1470,17 +1470,6 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(functionCallStatement: FunctionCallStatement) { 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. // most function calls, even to builtin functions, are still regular FunctionCall nodes here.
// they get converted to the more specialized node type in BeforeAsmTypecastCleaner // they get converted to the more specialized node type in BeforeAsmTypecastCleaner
val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors) 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) checker.visit(this)
} }
internal fun Program.reorderStatements(options: CompilationOptions, errors: IErrorReporter) { internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, options, errors) val reorder = StatementReorderer(this, errors)
reorder.visit(this) reorder.visit(this)
if(errors.noErrors()) { if(errors.noErrors()) {
reorder.applyModifications() 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) { 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 } throw FatalAstException("should not have a redundant block-level variable=0 assignment; it will be zeroed as part of BSS clear")
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")
}
val assign = PtAssignment(srcAssign.position, srcAssign.origin==AssignmentOrigin.VARINIT) val assign = PtAssignment(srcAssign.position, srcAssign.origin==AssignmentOrigin.VARINIT)
val multi = srcAssign.target.multi val multi = srcAssign.target.multi

View File

@@ -133,7 +133,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
is PtNumber, is PtNumber,
is PtRange, is PtRange,
is PtString -> true 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 else -> false
} }

View File

@@ -5,11 +5,13 @@ import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification 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( internal class StatementReorderer(
val program: Program, val program: Program,
val options: CompilationOptions,
val errors: IErrorReporter val errors: IErrorReporter
) : AstWalker() { ) : AstWalker() {
// Reorders the statements in a way the compiler needs. // Reorders the statements in a way the compiler needs.
@@ -112,18 +114,11 @@ internal class StatementReorderer(
} }
private fun canSkipInitializationWith0(decl: VarDecl): Boolean { private fun canSkipInitializationWith0(decl: VarDecl): Boolean {
if(decl.parent is Block) { // if the variable is declared in a block, we can omit the init with 0 because
// if the variable is declared in a block and is NOT in ZEROPAGE, we can omit the init with 0 because
// the variable will be initialized to zero when the BSS section is cleared as a whole. // 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 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), // 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. // and there is nothing important in between, we can skip the initialization.
val statements = (decl.parent as? IStatementContainer)?.statements ?: return false 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.inspectors.shouldForAll
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.expressions.NumericLiteral import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.FunctionCallStatement
@@ -14,7 +13,6 @@ import prog8.code.core.BuiltinFunctions
import prog8.code.core.RegisterOrPair import prog8.code.core.RegisterOrPair
import prog8.code.core.isNumeric import prog8.code.core.isNumeric
import prog8.code.target.Cx16Target import prog8.code.target.Cx16Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
class TestBuiltinFunctions: FunSpec({ class TestBuiltinFunctions: FunSpec({
@@ -105,31 +103,5 @@ main {
compileText(Cx16Target(), true, src, outputDir, writeAssembly = true) shouldNotBe null 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.AssignmentOrigin
import prog8.ast.statements.ForLoop import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.code.ast.PtAssignment
import prog8.code.ast.PtNumber
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
@@ -109,7 +106,6 @@ class TestVariables: FunSpec({
test("global var init with array lookup should sometimes be const") { test("global var init with array lookup should sometimes be const") {
val src=""" val src="""
%zeropage dontuse
main { main {
bool[] barray = [true, false, true, false] bool[] barray = [true, false, true, false]
@@ -224,85 +220,4 @@ main {
(st[5] as Assignment).target.identifier?.nameInSource shouldBe listOf("v1") (st[5] as Assignment).target.identifier?.nameInSource shouldBe listOf("v1")
(st[6] as Assignment).target.identifier?.nameInSource shouldBe listOf("v0") (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

@@ -27,7 +27,7 @@ of these library modules automatically as required.
.. note:: .. note::
Several algorithms and math routines in Prog8's assembly library files are adapted from 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: .. _builtinfunctions:

View File

@@ -1,10 +1,6 @@
TODO 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". 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. 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 { 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() { sub start() {
txt.print("address start of zpvars: ") uword uw = 9999
txt.print_uw(&zpvar1) word sw = -2222
txt.nl() ubyte ub = 42
txt.print("address start of normal vars: ") byte sb = -99
txt.print_uw(&var1) bool bb = true
txt.nl()
txt.print("non-dirty zp should all be 0: ") cx16.r0 = uw
txt.print_uw(zpvar1) cx16.r0s = sw
txt.spc() cx16.r0L = ub
txt.print_uw(zpvar2) cx16.r0H = ub
txt.spc() cx16.r0sL = sb
txt.print_uw(zpvar3) cx16.r0sH = sb
txt.spc() cx16.r0bL = bb
txt.print_uw(zpvar4) cx16.r0bH = bb
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()
txt.print("dirty zp may be random: ") uw = cx16.r0
txt.print_uw(dzpvar1) sw = cx16.r0s
txt.spc() ub = cx16.r0L
txt.print_uw(dzpvar2) ub = cx16.r0H
txt.spc() sb = cx16.r0sL
txt.print_uw(dzpvar3) sb = cx16.r0sH
txt.spc() bb = cx16.r0bL
txt.print_uw(dzpvar4) bb = cx16.r0bH
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 {}
} }
} }

View File

@@ -11,10 +11,8 @@ Intermediate Representation instructions for the IR Virtual machine.
Specs of the virtual machine this will run on: 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. 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 65536 virtual registers, 16 bits wide, can also be used as 8 bits. r0-r65535
reserved 99000 - 99099 : WORD registers for syscall arguments and response value(s) 65536 virtual floating point registers (64 bits double precision) fr0-fr65535
reserved 99100 - 99199 : BYTE registers for syscall arguments and response value(s)
100K virtual floating point registers (64 bits double precision) fr0-fr99999
65536 bytes of memory. Thus memory pointers (addresses) are limited to 16 bits. 65536 bytes of memory. Thus memory pointers (addresses) are limited to 16 bits.
Value stack, max 128 entries of 1 byte each. 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, 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', ':codeGenCpu6502',
':codeGenExperimental', ':codeGenExperimental',
':compiler', ':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. // 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. // 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 initializationNumericValue = number
} }