From 0ef9b00d09152fd1e900da5f6121478b48601653 Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 21:45:42 +0300 Subject: [PATCH 01/38] Update pdc.sk --- scripts/libs/pdc.sk | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/libs/pdc.sk b/scripts/libs/pdc.sk index 64939f2..0abfaef 100644 --- a/scripts/libs/pdc.sk +++ b/scripts/libs/pdc.sk @@ -113,7 +113,7 @@ object property p[ersistent ]d[ata ]c[ontainer]: get: return expr-1.getPersistentDataContainer() -expression [devdinc] (pdt|[persistent data ]type) (1:str[ing]|2:int[eger]|3:long|4:float|5:double|6:byte|7:short|8:bool[ean]): +expression [devdinc] (pdt|[persistent data ]type) [of] (1:str[ing]|2:int[eger]|3:long|4:float|5:double|6:byte|7:short|8:bool[ean]|9:container): parse: continue get: @@ -133,6 +133,8 @@ expression [devdinc] (pdt|[persistent data ]type) (1:str[ing]|2:int[eger]|3:long return PersistentDataType.SHORT else if parse mark is 8: return PersistentDataType.BOOLEAN + else if parse mark is 9: + return PersistentDataType.TAG_CONTAINER else: return {_none} From e0162cb64c186a231a8ae7cda40c0860204d92b4 Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 21:49:30 +0300 Subject: [PATCH 02/38] Update pdc.sk --- scripts/libs/pdc.sk | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/libs/pdc.sk b/scripts/libs/pdc.sk index 0abfaef..17c9c8e 100644 --- a/scripts/libs/pdc.sk +++ b/scripts/libs/pdc.sk @@ -113,6 +113,23 @@ object property p[ersistent ]d[ata ]c[ontainer]: get: return expr-1.getPersistentDataContainer() +expression [devdinc] new p[ersistent ]d[ata ]c[ontainer] from %object%: + parse: + continue + + get: + set {_src} to expr-1 + + if {_src} is instance of PersistentDataHolder: + set {_ctx} to {_src}.getPersistentDataContainer().getAdapterContext() + return {_ctx}.newPersistentDataContainer() + + if {_src} is instance of PersistentDataContainer: + set {_ctx} to {_src}.getAdapterContext() + return {_ctx}.newPersistentDataContainer() + + return {_none} + expression [devdinc] (pdt|[persistent data ]type) [of] (1:str[ing]|2:int[eger]|3:long|4:float|5:double|6:byte|7:short|8:bool[ean]|9:container): parse: continue From b09252cf17e9627e73dfff64a813ffca69ace6ca Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 21:53:46 +0300 Subject: [PATCH 03/38] Update test-skripts.yml --- .github/workflows/test-skripts.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-skripts.yml b/.github/workflows/test-skripts.yml index 700330c..15fc93c 100644 --- a/.github/workflows/test-skripts.yml +++ b/.github/workflows/test-skripts.yml @@ -43,7 +43,7 @@ jobs: run: | mkdir -p build/libs curl -L -o build/libs/skript-reflect.jar \ - https://github.com/SkriptLang/skript-reflect/releases/download/v2.6.1/skript-reflect-2.6.1.jar + https://github.com/SkriptLang/skript-reflect/releases/download/v2.6.2/skript-reflect-2.6.2.jar - name: Download Routines from JitPack run: | @@ -68,12 +68,12 @@ jobs: - name: Run tests uses: devdinc/skript-test-action@v1.3 with: - skript_repo_url: https://github.com/devdinc/Skript.git + skript_repo_url: https://github.com/SkriptLang/Skript.git # directory where your test scripts are located (relative to repo root) test_script_directory: tests # Skript version or ref (tag, branch, or commit) - skript_repo_ref: ef28bd5 # 2.13.2 + skript_repo_ref: 01d155a # 2.14.0-dev # directory containing addon/plugin jars (relative to repo root) extra_plugins_directory: build/libs From 1158a5e5f36d7cbd2968a30c58c2db6f2ad092dd Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:09:46 +0300 Subject: [PATCH 04/38] test: implement before/after hooks and add Skript test tracker support --- scripts/utils/testframework.sk | 117 +++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 21 deletions(-) diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 4e80bcc..cb70495 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -5,8 +5,9 @@ import: org.bukkit.Bukkit ch.njol.skript.test.utils.TestOfflinePlayer ch.njol.skript.lang.Condition + ch.njol.skript.test.runner.TestTracker -local effect testFail %string%[, %-string%]: +local effect sendTestFailMessage %string%[, %-string%]: trigger: set {_msg} to " %expr-2%" if expr-2 is set else "" send "[Skript] [&cTEST FAILURE] %expr-1%%{_msg}%" to console @@ -25,16 +26,46 @@ event "skriptTest": if {_cond} is set: set {_ok} to {_cond}.check(event) continue if {_ok} is true + +event "beforeSkriptTest": + pattern: [devdinc] before each test + event-values: string, boolean + check: + continue + +event "afterSkriptTest": + pattern: [devdinc] after each test + event-values: string, boolean + check: + continue + +event "beforeSkriptTestAll": + pattern: [devdinc] before all tests + check: + continue + +event "afterSkriptTestAll": + pattern: [devdinc] after all tests + check: + continue plural expression all tests [with test name %-string%]: return type: strings + parse: + set {_scoped} to false if expr-2 is not set + continue + get: - loop {-test.sk::tests::*}: - if any: - expr-1 is not set - loop-value is expr-1 - then: - add "%loop-value%" to {_r::*} + if {_scoped} is false: + loop {-test.sk::tests::*}: + if any: + expr-1 is not set + loop-value is expr-1 + then: + add "%loop-value%" to {_r::*} + else: + if {-test.sk::tests::%expr-1%} is set: + add "%expr-1%" to {_r::*} return {_r::*} @@ -42,11 +73,13 @@ effect (1:|2:auto)run [test][s] %strings%: trigger: set {_tests::*} to expr-1 set {_alltests::*} to all tests + call custom event "beforeSkriptTestAll" loop {_tests::*}: - delete {-test.sk::errors::%loop-value%::*} set {_list::string} to loop-value set {_list::boolean} to true if parse mark is 2 else false + call custom event "beforeSkriptTest" with {_list::*} call custom event "skriptTest" with {_list::*} + call custom event "afterSkriptTest" with {_list::*} if any: loop-value contains "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" {_alltests::*} does not contain loop-value @@ -54,22 +87,48 @@ effect (1:|2:auto)run [test][s] %strings%: add 1 to {_forgottenTestResults} continue loop add 1 to {_testFails} if size of {-test.sk::errors::%loop-value%::*} is greater than 0 + call custom event "afterSkriptTestAll" set {_validTestAmount} to size of {_tests::*} - {_forgottenTestResults} if {_validTestAmount} is not 0: send "[Skript] %{_validTestAmount} - {_testFails}%/%{_validTestAmount}% tests passed." to console else if size of {_alltests::*} is greater than 0: # skipping if there is no initial hidden test send "[Skript] No tests found." to console +condition last test result [of [test] %-string%] is a (1:pass|2:fail): + usable in: + custom event "skriptTest" + check: + if expr-1 is not set: + set {_test} to event.getEventValue("string") + else: + set {_test} to expr-1 + stop if all tests does not contain expr-1 + if all: + parse mark is 1 + size of {-test.sk::errors::%{_test}%::*} <= 0 + then: + continue + else: + if all: + parse mark is 2 + size of {-test.sk::errors::%{_test}%::*} > 0 + then: + continue + +before each test: + delete {-test.sk::errors::%event-string%::*} + set {-test.sk::testblock} to test-block's type + +after each test: + set test-block to {-test.sk::testblock} + on load: delete {-test.sk::*} - set {_block} to test-block's type wait 1 tick run test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" wait 1 tick set {_tests::*} to all tests autorun {_tests::*} - set test-block to {_block} - broadcast "Test block is permanent!" if test-block is not {_block} effect: patterns: @@ -114,7 +173,8 @@ effect: {_bool2} is true then: if parse tags does not contain "5": - testFail "Test ""%{_test}%"" with condition '%{_label}%: %{_raw}%' failed", expr-1 + sendTestFailMessage "Test ""%{_test}%"" with condition '%{_label}%: %{_raw}%' failed", expr-1 + TestTracker.testFailed(expr-1) add "%expr-1%" to {-test.sk::errors::%{_test}%::*} if parse tags contains "3": delay effect @@ -125,7 +185,8 @@ effect (3:|4:(no|without) (halt[ing]|fail[(-| )](safe|fast)|abort[ing])) fail te trigger: set {_test} to event.getEventValue("string") if parse tags does not contain "5": - testFail "Test ""%{_test}%"" failed", expr-1 + sendTestFailMessage "Test ""%{_test}%"" failed", expr-1 + TestTracker.testFailed(expr-1) add "%expr-1%" to {-test.sk::errors::%{_test}%::*} if parse tags contains "3": delay effect @@ -157,8 +218,6 @@ condition %string% (1:is|2:is not|2:isn't) autorun: parse mark is 2 continue -### this section is experimental. - import: ch.njol.skript.ScriptLoader ch.njol.skript.log.SkriptLogger @@ -193,8 +252,6 @@ plural expression last parse logs: get: return {-test.sk::latestLogs::*} -# ---- - plural expression test errors[ for [test[s]] %-strings%]: return type: strings get: @@ -210,7 +267,14 @@ plural expression test errors[ for [test[s]] %-strings%]: expression [the] test(-| )world: return type: world get: - return Bukkit.getWorlds().get(0) + set {_world} to Bukkit.getWorld("skripttest") + if any: + {_world} is not set + {_world} is null + then: + return Bukkit.getWorlds().get(0) + else: + return {_world} expression [the] test(-| )location: return type: location @@ -302,17 +366,28 @@ devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 errors do not leak between te devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 event-test returns correct name": assert true: event-test is event-string -devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 test-block is temporary": +devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 test-block is temporary A": + set {-test.sk::temptestblocka} to test-block's type test block is chest: set test-block to trapped chest else if test block is trapped chest: set test-block to chest else: set test-block to ender chest - - # test is continued at on load end + set {-test.sk::temptestblockb} to test-block's type + +devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 test-block is temporary B": + assert true: {-test.sk::temptestblocka} is test-block's type + assert true: {-test.sk::temptestblocka} is not {-test.sk::temptestblockb} devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 parse section": parse: no errors: abc # just an internal stuff to make sure test.sk loads without errors assert last parse logs contains "Can't understand this condition/effect: no errors: abc" + +devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 last test result A": + fail test with no error message + +devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 last test result B": + set {_test} to "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 last test result A" + assert true: last test result of {_test} is a fail From ad133c15538032389317c8e5dc844ef431f81fec Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:12:30 +0300 Subject: [PATCH 05/38] Create singlelinemulticonditional.sk --- scripts/lang/singlelinemulticonditional.sk | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 scripts/lang/singlelinemulticonditional.sk diff --git a/scripts/lang/singlelinemulticonditional.sk b/scripts/lang/singlelinemulticonditional.sk new file mode 100644 index 0000000..0318460 --- /dev/null +++ b/scripts/lang/singlelinemulticonditional.sk @@ -0,0 +1,28 @@ +import: + ch.njol.skript.lang.Condition + +condition <(.+)> (1:&&|2:\|\|) <(.+)>: + check: + set {_a} to first element of regex-1 + set {_b} to first element of regex-2 + set {_message} to "Can't understand this condition: " + set {_bool1} to Condition.parse({_a}, "%{_message}%%{_a}%").check(event) + set {_bool2} to Condition.parse({_b}, "%{_message}%%{_b}%").check(event) + if parse mark is 1: + if all: + {_bool1} is true + {_bool2} is true + then: + continue + else: + if any: + {_bool1} is true + {_bool2} is true + then: + continue + +expression boolean value of condition <(.+)>: + get: + set {_b} to first element of regex-1 + set {_message} to "Can't understand this condition: " + return Condition.parse({_b}, "%{_message}%%{_b}%").check(event) From 2fa77777d8e7ae0bf8208568a4809579d8c60954 Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:13:18 +0300 Subject: [PATCH 06/38] Create scopedvariable.sk --- scripts/lang/scopedvariable.sk | 106 +++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 scripts/lang/scopedvariable.sk diff --git a/scripts/lang/scopedvariable.sk b/scripts/lang/scopedvariable.sk new file mode 100644 index 0000000..d4ecf2f --- /dev/null +++ b/scripts/lang/scopedvariable.sk @@ -0,0 +1,106 @@ +import: + ch.njol.skript.lang.Variable + +plural expression scoped [variable] %objects% [([with]in|from) (%-script%|%-string%)]: + parse: + expr-1 is an instance of Variable + set {_var} to expr-1 + continue + + get: + set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + + if {_var}.isEphemeral(): + if {_var}.isList(): + return {-local::%{_script}%::%{_var}.getName()%::*} + else: + return {-local::%{_script}%::%{_var}.getName()%} + else: + if {_var}.isList(): + return {local::%{_script}%::%{_var}.getName()%::*} + else: + return {local::%{_script}%::%{_var}.getName()%} + + set: + set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + + if {_var}.isEphemeral(): + if {_var}.isList(): + set {-local::%{_script}%::%{_var}.getName()%::*} to change values + else: + set {-local::%{_script}%::%{_var}.getName()%} to change value + else: + if {_var}.isList(): + set {local::%{_script}%::%{_var}.getName()%::*} to change values + else: + set {local::%{_script}%::%{_var}.getName()%} to change value + + add: + set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + + if {_var}.isEphemeral(): + if {_var}.isList(): + add change values to {-local::%{_script}%::%{_var}.getName()%::*} + else: + add change values to {-local::%{_script}%::%{_var}.getName()%} + else: + if {_var}.isList(): + add change values to {local::%{_script}%::%{_var}.getName()%::*} + else: + add change values to {local::%{_script}%::%{_var}.getName()%} + + remove: + set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + + if {_var}.isEphemeral(): + if {_var}.isList(): + remove change values from {-local::%{_script}%::%{_var}.getName()%::*} + else: + remove change values from {-local::%{_script}%::%{_var}.getName()%} + else: + if {_var}.isList(): + remove change values from {local::%{_script}%::%{_var}.getName()%::*} + else: + remove change values from {local::%{_script}%::%{_var}.getName()%} + + remove all: + set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + + if {_var}.isEphemeral(): + if {_var}.isList(): + remove all change values from {-local::%{_script}%::%{_var}.getName()%::*} + else: + remove all change values from {-local::%{_script}%::%{_var}.getName()%} + else: + if {_var}.isList(): + remove all change values from {local::%{_script}%::%{_var}.getName()%::*} + else: + remove all change values from {local::%{_script}%::%{_var}.getName()%} + + delete: + set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + + if {_var}.isEphemeral(): + if {_var}.isList(): + delete {-local::%{_script}%::%{_var}.getName()%::*} + else: + delete {-local::%{_script}%::%{_var}.getName()%} + else: + if {_var}.isList(): + delete {local::%{_script}%::%{_var}.getName()%::*} + else: + delete {local::%{_script}%::%{_var}.getName()%} + + reset: + set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + + if {_var}.isEphemeral(): + if {_var}.isList(): + clear {-local::%{_script}%::%{_var}.getName()%::*} + else: + clear {-local::%{_script}%::%{_var}.getName()%} + else: + if {_var}.isList(): + clear {local::%{_script}%::%{_var}.getName()%::*} + else: + clear {local::%{_script}%::%{_var}.getName()%} From d077c65ad18d03a388ddd096f518447dbee212ae Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:14:06 +0300 Subject: [PATCH 07/38] Create scopedvariable.sk --- tests/scopedvariable.sk | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/scopedvariable.sk diff --git a/tests/scopedvariable.sk b/tests/scopedvariable.sk new file mode 100644 index 0000000..a8da00b --- /dev/null +++ b/tests/scopedvariable.sk @@ -0,0 +1,50 @@ +test "scoped variable - set & get": + set scoped {-test} to 12 + assert scoped {-test} is 12 + +test "scoped variable - add": + set scoped {-addtest} to 5 + add 3 to scoped {-addtest} + assert scoped {-addtest} is 8 + +test "scoped variable - lists": + set scoped {-lists::*} to "a", "b", "c" + assert scoped {-lists::*} contains "a" + assert scoped {-lists::*} contains "b" + assert scoped {-lists::*} contains "c" + +test "scoped variable - remove": + set scoped {-removetest::*} to "a", "b", "c" + remove "b" from scoped {-removetest::*} + assert scoped {-removetest::*} contains "a" + assert scoped {-removetest::*} contains "c" + assert scoped {-removetest::*} does not contain "b" + +test "scoped variable - remove all": + set scoped {-removeall::*} to "a", "b", "b", "c" + remove all "b" from scoped {-removeall::*} + assert scoped {-removeall::*} is "a", "c" + +test "scoped variable - delete": + set scoped {-deletetest} to 42 + delete scoped {-deletetest} + assert scoped {-deletetest} is not set + +test "scoped variable - reset single value": + set scoped {-resettest} to 9 + reset scoped {-resettest} + assert scoped {-resettest} is not set + +test "scoped variable - reset list": + set scoped {-resetlist::*} to 1, 2, 3 + reset scoped {-resetlist::*} + assert scoped {-resetlist::*} is not set + +test "scoped variable - non-ephemeral (named)": + set scoped {namedtest} to "hello" + assert scoped {namedtest} is "hello" + delete scoped {namedtest} + +test "scoped variable - script scoping": + set scoped {-scoped} within current script to 100 + assert scoped {-scoped} is 100 From af3b8b2f0ec7bf606e86abf95db3d487f87de3cc Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:14:37 +0300 Subject: [PATCH 08/38] Create repeateffect.sk --- scripts/lang/repeateffect.sk | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 scripts/lang/repeateffect.sk diff --git a/scripts/lang/repeateffect.sk b/scripts/lang/repeateffect.sk new file mode 100644 index 0000000..c4c5067 --- /dev/null +++ b/scripts/lang/repeateffect.sk @@ -0,0 +1,13 @@ +import: + ch.njol.skript.lang.Effect + +effect [repeat] <(.+)> (1:once|2:twice|3:thrice|4:%-number% times): + parse: + set {_repeat.sk::effect} to Effect.parse(first element of regex-1) + continue + trigger: + set {_times} to expr-1 + if parse mark is not 4: + set {_times} to parse mark + loop {_times} times: + {_repeat.sk::effect}.run(event) From 83707bd8845a04516244b1291888287d07474924 Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:29:53 +0300 Subject: [PATCH 09/38] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 38d5b1d..70e1bf8 100644 --- a/README.md +++ b/README.md @@ -40,5 +40,4 @@ Skript loads files alphabetically, with folders being prioritized. To control lo Currents plans include: - multiline lambda expression -- making the test framework support extending over the skriptlang native test suit - Auto casting for primitives, pdc.sk(new Byte,Integer etc) From 029953f85197a4794488fb660f24220676ce5128 Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:50:44 +0300 Subject: [PATCH 10/38] Update testframework.md --- docs/testframework.md | 164 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 159 insertions(+), 5 deletions(-) diff --git a/docs/testframework.md b/docs/testframework.md index 6c4cec1..be585a7 100644 --- a/docs/testframework.md +++ b/docs/testframework.md @@ -17,7 +17,7 @@ All framework state is stored under the `-test.sk::*` namespace. ## Design Goals -The framework is intentionally minimal and predictable, prioritizing correctness and isolation over flexibility. +The framework is intentionally minimal and predictable. It is designed to: @@ -39,6 +39,43 @@ It is designed to: | **Explicit failure** | Achieved | `fail test` effect | | **Parser inspection** | Experimental | Parse sections and log capture | +## Skript Native Test Tracker Integration (Experimental) + +This framework includes **experimental support for Skript’s native `TestTracker`**. + +### What This Means + +* Test failures are forwarded to Skript’s internal test tracker +* Failures appear in Skript’s native reporting pipeline +* CI-style tooling can observe results + +### Important Limitations + +**This does NOT imply full parity with Skript’s internal test framework.** + +Many test expressions and effects from this script are **not available** via test %string% pattern when using Skript's quickTest. + +You **must** use: + +```skript +devdinc test %string% +``` + +--- + +### Experimental Status + +Test tracker support is: + +* **Experimental** + +It may: + +* Break across Skript versions +* Be disabled in future releases + +No compatibility guarantees are provided. + --- ## High-Level Architecture @@ -302,14 +339,106 @@ last parse logs --- +## Test Lifecycle Hooks (Before / After) + +The framework provides **test lifecycle hooks** that allow controlled setup and teardown logic. + +### Available Hooks + +```skript +before each test +after each test +before all tests +after all tests +``` + +### Semantics + +* **`before each test`** + Runs immediately before every individual test execution. + +* **`after each test`** + Runs immediately after every individual test execution, regardless of outcome. + +* **`before all tests`** + Runs once before any test is executed. + +* **`after all tests`** + Runs once after all test execution completes. + +### Event Context + +Hooks execute with the same event values as a normal test: + +* `event-string` – current test identifier (for per-test hooks) +* `event-boolean` – autorun flag + +### Intended Usage + +Hooks are designed for: + +* Environment setup / teardown +* Temporary state mutation +* Resource allocation and cleanup +* Optional world isolation (see below) + +Hooks **must not** be used to implement assertions directly. + +--- + ## Test Environment Utilities To ensure isolation and repeatability, the framework provides controlled test fixtures: -* **`test-world`** – dedicated test world -* **`test-location`** – fixed location (`spawn + 10, 1, 0`) -* **`test-block`** – self-resetting block restored after tests +**`test-world`** – the `"skripttest"` world if present, otherwise the first loaded world +* **`test-location`** – fixed location (`spawn + 10, 1, 0`) in test-world +* **`test-block`** – self-resetting block restored after tests located at test-location * **`test-offline-player`** – generated offline player instance + +--- + +## World Isolation & Persistence Guarantees + +### Default Behavior + +By default, **tests share the same world state**. + +This is intentional. + +Automatic per-test world isolation is **not enabled** because: + +* Full world backup/restore is **disk-intensive** +* Synchronous world copying is **slow** +* Asynchronous restore is **unsafe** +* Large worlds make isolation prohibitively expensive + +The framework therefore prioritizes **predictable runtime safety** over implicit isolation. + +--- + +### Opt-In World Isolation Using Hooks + +Full isolation **can** be achieved manually using lifecycle hooks: + +```skript +before each test: + # backup world state + +after each test: + # restore world state +``` + +This allows users to: + +* Snapshot world folders +* Restore region files +* Roll back test-specific mutations + +⚠ **Important:** +This approach is **explicitly opt-in** and entirely user-controlled. + +The framework does **not** provide built-in world snapshotting utilities and makes no assumptions about storage strategy, synchronization model, or performance tradeoffs. + --- @@ -352,7 +481,7 @@ Output is suppressed entirely when `with no error message` is specified. The framework guarantees that: -* Tests are isolated by themselves +* Test execution state is isolated per test * Registration is implicit and deterministic * Autorun and manual execution are distinguishable * Failures are recorded even in non-halting mode @@ -381,3 +510,28 @@ These may change without notice. * Lightweight CI-style verification This framework is deliberately constrained to ensure reliability and transparency. + +--- + +## Reflection Usage Disclaimer + +Several advanced features rely on **reflection**: + +* Parser inspection (`ParserInstance`) +* Condition parsing +* Native test tracker forwarding +* Log capture via `SkriptLogger` + +### Guarantees + +* Reflection usage is **read-only or carefully scoped** +* No permanent internal state is mutated +* All parser state is backed up and restored + +### Non-Guarantees + +* Binary compatibility across Skript versions +* Stability under obfuscation +* Availability on forks with modified internals + +Reflection-backed features are considered **best-effort** and may degrade gracefully. From 72b64b6fab23a712595d5946a8c5cb63557ac63c Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:52:56 +0300 Subject: [PATCH 11/38] Update testframework.md --- docs/testframework.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testframework.md b/docs/testframework.md index be585a7..52ab0b5 100644 --- a/docs/testframework.md +++ b/docs/testframework.md @@ -36,8 +36,8 @@ It is designed to: | **Test structure** | Achieved | `test %string%` mirrors native test declarations | | **Conditional execution** | Achieved | `when ` skips tests dynamically | | **Assertions** | Achieved | `assert ` and `assert to fail` | -| **Explicit failure** | Achieved | `fail test` effect | | **Parser inspection** | Experimental | Parse sections and log capture | +| **Test entities** | Partial | More information below | ## Skript Native Test Tracker Integration (Experimental) From ff46235bc0086a0494258f4db20348ca176fff28 Mon Sep 17 00:00:00 2001 From: devdinc Date: Sat, 31 Jan 2026 19:25:42 +0300 Subject: [PATCH 12/38] Refactor for best load order, scoped variable fixes, pdc coercing based on pdt, unspecified pdt resolvement, routines test correction. --- .restructure | 2 +- scripts/lang/scopedvariable.sk | 87 +++++---- .../libs/{ => parser}/singlelinesection.sk | 0 scripts/libs/pdc.sk | 165 ++++++++++++------ scripts/utils/testframework.sk | 7 +- tests/pdc.sk | 63 +++---- tests/routines.sk | 2 +- tests/scopedvariable.sk | 8 + 8 files changed, 204 insertions(+), 130 deletions(-) rename scripts/libs/{ => parser}/singlelinesection.sk (100%) diff --git a/.restructure b/.restructure index 64b3cec..e518a3b 100644 --- a/.restructure +++ b/.restructure @@ -1,7 +1,7 @@ using scripts prefix: - 0 libs/singlelinesection.sk + 0 libs/parser/singlelinesection.sk 1 libs/functionsv2.sk 2 libs/routines.sk 3 . diff --git a/scripts/lang/scopedvariable.sk b/scripts/lang/scopedvariable.sk index d4ecf2f..c934162 100644 --- a/scripts/lang/scopedvariable.sk +++ b/scripts/lang/scopedvariable.sk @@ -1,106 +1,119 @@ import: ch.njol.skript.lang.Variable + ch.njol.skript.variables.Variables + ch.njol.skript.log.SkriptLogger -plural expression scoped [variable] %objects% [([with]in|from) (%-script%|%-string%)]: +# todo scoped within current package/folder, local variables? + +expression scoped [variable] %$objects% [([with]in|from) (%-script%|%-string%)]: parse: expr-1 is an instance of Variable + set {_origin} to SkriptLogger.getNode() + set {_currentscript} to script ({_origin}.getConfig().getFileName()) set {_var} to expr-1 continue - get: - set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + get: # todo sadly {_var}.getName().toString(event) does not work(ClassCastException), meaning we cant resolve expressions inside + set {_name} to try {_var}.getName().toString() + set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): if {_var}.isList(): - return {-local::%{_script}%::%{_var}.getName()%::*} + return {-local::%{_script}%::%{_name}%::*} else: - return {-local::%{_script}%::%{_var}.getName()%} + return {-local::%{_script}%::%{_name}%} else: if {_var}.isList(): - return {local::%{_script}%::%{_var}.getName()%::*} + return {local::%{_script}%::%{_name}%::*} else: - return {local::%{_script}%::%{_var}.getName()%} + return {local::%{_script}%::%{_name}%} set: - set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + set {_name} to try {_var}.getName().toString() + set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): if {_var}.isList(): - set {-local::%{_script}%::%{_var}.getName()%::*} to change values + set {-local::%{_script}%::%{_name}%::*} to change values else: - set {-local::%{_script}%::%{_var}.getName()%} to change value + set {-local::%{_script}%::%{_name}%} to change value else: if {_var}.isList(): - set {local::%{_script}%::%{_var}.getName()%::*} to change values + set {local::%{_script}%::%{_name}%::*} to change values else: - set {local::%{_script}%::%{_var}.getName()%} to change value + set {local::%{_script}%::%{_name}%} to change value add: - set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + set {_name} to try {_var}.getName().toString() + set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): if {_var}.isList(): - add change values to {-local::%{_script}%::%{_var}.getName()%::*} + add change values to {-local::%{_script}%::%{_name}%::*} else: - add change values to {-local::%{_script}%::%{_var}.getName()%} + add change values to {-local::%{_script}%::%{_name}%} else: if {_var}.isList(): - add change values to {local::%{_script}%::%{_var}.getName()%::*} + add change values to {local::%{_script}%::%{_name}%::*} else: - add change values to {local::%{_script}%::%{_var}.getName()%} + add change values to {local::%{_script}%::%{_name}%} remove: - set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + set {_name} to try {_var}.getName().toString() + set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): if {_var}.isList(): - remove change values from {-local::%{_script}%::%{_var}.getName()%::*} + remove change values from {-local::%{_script}%::%{_name}%::*} else: - remove change values from {-local::%{_script}%::%{_var}.getName()%} + remove change values from {-local::%{_script}%::%{_name}%} else: if {_var}.isList(): - remove change values from {local::%{_script}%::%{_var}.getName()%::*} + remove change values from {local::%{_script}%::%{_name}%::*} else: - remove change values from {local::%{_script}%::%{_var}.getName()%} + remove change values from {local::%{_script}%::%{_name}%} remove all: - set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + set {_name} to try {_var}.getName().toString() + set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): if {_var}.isList(): - remove all change values from {-local::%{_script}%::%{_var}.getName()%::*} + remove all change values from {-local::%{_script}%::%{_name}%::*} else: - remove all change values from {-local::%{_script}%::%{_var}.getName()%} + remove all change values from {-local::%{_script}%::%{_name}%} else: if {_var}.isList(): - remove all change values from {local::%{_script}%::%{_var}.getName()%::*} + remove all change values from {local::%{_script}%::%{_name}%::*} else: - remove all change values from {local::%{_script}%::%{_var}.getName()%} + remove all change values from {local::%{_script}%::%{_name}%} delete: - set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + set {_name} to try {_var}.getName().toString() + set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): if {_var}.isList(): - delete {-local::%{_script}%::%{_var}.getName()%::*} + delete {-local::%{_script}%::%{_name}%::*} else: - delete {-local::%{_script}%::%{_var}.getName()%} + delete {-local::%{_script}%::%{_name}%} else: if {_var}.isList(): - delete {local::%{_script}%::%{_var}.getName()%::*} + delete {local::%{_script}%::%{_name}%::*} else: - delete {local::%{_script}%::%{_var}.getName()%} + delete {local::%{_script}%::%{_name}%} reset: - set {_script} to "%current script%" if expr-2 is not set else "%expr-2%" + set {_name} to try {_var}.getName().toString() + set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): if {_var}.isList(): - clear {-local::%{_script}%::%{_var}.getName()%::*} + clear {-local::%{_script}%::%{_name}%::*} else: - clear {-local::%{_script}%::%{_var}.getName()%} + clear {-local::%{_script}%::%{_name}%} else: if {_var}.isList(): - clear {local::%{_script}%::%{_var}.getName()%::*} + clear {local::%{_script}%::%{_name}%::*} else: - clear {local::%{_script}%::%{_var}.getName()%} + clear {local::%{_script}%::%{_name}%} diff --git a/scripts/libs/singlelinesection.sk b/scripts/libs/parser/singlelinesection.sk similarity index 100% rename from scripts/libs/singlelinesection.sk rename to scripts/libs/parser/singlelinesection.sk diff --git a/scripts/libs/pdc.sk b/scripts/libs/pdc.sk index 17c9c8e..f292cb8 100644 --- a/scripts/libs/pdc.sk +++ b/scripts/libs/pdc.sk @@ -8,6 +8,18 @@ import: org.bukkit.util.io.BukkitObjectInputStream org.bukkit.util.io.BukkitObjectOutputStream ch.njol.skript.lang.Variable + java.lang.Byte + java.lang.Short + java.lang.Integer + java.lang.Long + java.lang.Float + java.lang.Double + java.lang.Boolean + +on load: + set scoped {-primitives::pdt::*} to PersistentDataType.BOOLEAN, PersistentDataType.BYTE, PersistentDataType.SHORT, PersistentDataType.INTEGER, PersistentDataType.LONG, PersistentDataType.FLOAT, PersistentDataType.DOUBLE, PersistentDataType.STRING + set scoped {-primitives::str::*} to "bool", "byte", "short", "int", "long", "float", "double", "str" + set scoped {-primitives::int::*} to 8, 4, 5, 6, 7, 2, 3, 1 local function validate_pdcexpr(key: object, pdc: object, pdt: object) :: boolean: if all: @@ -22,53 +34,96 @@ local function validate_pdcexpr(key: object, pdc: object, pdt: object) :: boolea return false return true -# e.g. key "plugin:test" in player's pdc for type byte -expression [devdinc] %object% [with]in %object%[ for %-object%]: + +local function coerce_pdt_value(pdt: object, value: object) :: object: + if {_value} is not set: + return {_none} + + if {_pdt} is PersistentDataType.INTEGER: + return try new Integer({_value}) + + else if {_pdt} is PersistentDataType.BYTE: + return try new Byte({_value}) + + else if {_pdt} is PersistentDataType.SHORT: + return try new Short({_value}) + + else if {_pdt} is PersistentDataType.LONG: + return try new Long({_value}) + + else if {_pdt} is PersistentDataType.FLOAT: + return try new Float({_value}) + + else if {_pdt} is PersistentDataType.DOUBLE: + return try new Double({_value}) + + else if {_pdt} is PersistentDataType.STRING: + return {_value} if {_value} is a text + + else if {_pdt} is PersistentDataType.BOOLEAN: + return {_value} if {_value} is a boolean + + else if {_pdt} is PersistentDataType.BYTE_ARRAY: + return {_value} + + return {_none} + + +expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: parse: set {_key} to expr-1 set {_pdt} to expr-3 - + if any: - "%{_key}%" is "[devdinc] [namespaced]( |-)key %%string%%" - {_key} is instance of Variable + "%{_pdt}%" contains "[devdinc] (pdt|[persistent data ]type)" + "%{_pdt}%" contains "PersistentDataType" + {_pdt} is instance of Variable + {_pdt} is not set then: - if any: - "%{_pdt}%" contains "[devdinc] (pdt|[persistent data ]type)" - "%{_pdt}%" contains "PersistentDataType" - {_pdt} is instance of Variable - {_pdt} is not set - then: - continue - stop + continue get: set {_key} to expr-1 set {_pdc} to expr-2.getPersistentDataContainer() if expr-2 is instance of PersistentDataHolder else expr-2 set {_pdt} to expr-3 - + stop if validate_pdcexpr({_key}, {_pdc}, {_pdt}) is false - + if {_pdt} is not set: - if {_pdc}.has({_key}, PersistentDataType.BYTE_ARRAY) is false: - return {_none} - - set {_bytes} to {_pdc}.get({_key}, PersistentDataType.BYTE_ARRAY) - set {_in} to new BukkitObjectInputStream(new ByteArrayInputStream({_bytes})) - set {_obj} to {_in}.readObject() - {_in}.close() - return {_obj} + set {_typeKey} to namespaced key "%{_key}%..type" + + if {_pdc}.has({_typeKey}, PersistentDataType.STRING): + set {_typeId} to {_pdc}.get({_typeKey}, PersistentDataType.STRING) + + set {_i} to 1 + loop scoped {-primitives::str::*}: + if {_typeId} is loop-value: + set {_pdt} to {_i}th element of scoped {-primitives::pdt::*} + return {_pdc}.get({_key}, {_pdt}) + add 1 to {_i} + + else: + if {_pdc}.has({_key}, PersistentDataType.BYTE_ARRAY) is false: + return {_none} + + set {_bytes} to {_pdc}.get({_key}, PersistentDataType.BYTE_ARRAY) + set {_in} to new BukkitObjectInputStream(new ByteArrayInputStream({_bytes})) + set {_obj} to {_in}.readObject() + {_in}.close() + return {_obj} else: return {_pdc}.get({_key}, {_pdt}) + set: set {_key} to expr-1 set {_pdc} to expr-2.getPersistentDataContainer() if expr-2 is instance of PersistentDataHolder else expr-2 set {_pdt} to expr-3 - + stop if validate_pdcexpr({_key}, {_pdc}, {_pdt}) is false - + set {_value} to change value - + if any: {_value} is null {_value} is not set @@ -77,46 +132,66 @@ expression [devdinc] %object% [with]in %object%[ for %-object%]: stop if {_pdt} is not set: + set {_typeKey} to namespaced key "%{_key}%..type" + + set {_i} to 1 + loop scoped {-primitives::pdt::*}: + set {_try} to coerce_pdt_value(loop-value, {_value}) + + if {_try} is set: + {_pdc}.set({_key}, loop-value, {_try}) + {_pdc}.set({_typeKey}, PersistentDataType.STRING, {_i}th element of scoped {-primitives::str::*}) + + stop + + add 1 to {_i} + set {_baos} to new ByteArrayOutputStream() set {_out} to new BukkitObjectOutputStream({_baos}) {_out}.writeObject({_value}) {_out}.close() - + set {_boxedBytes::*} to ...{_baos}.toByteArray() set {_bytes} to new byte[size of {_boxedBytes::*}] loop {_boxedBytes::*}: set {_bytes}[loop-index parsed as number - 1] to loop-value - + {_pdc}.set({_key}, PersistentDataType.BYTE_ARRAY, {_bytes}) else: - {_pdc}.set({_key}, {_pdt}, {_value}) + set {_coerced} to coerce_pdt_value({_pdt}, {_value}) + {_pdc}.set({_key}, {_pdt}, {_coerced}) + delete: set {_key} to expr-1 set {_pdc} to expr-2.getPersistentDataContainer() if expr-2 is instance of PersistentDataHolder else expr-2 set {_pdt} to expr-3 - + stop if validate_pdcexpr({_key}, {_pdc}, {_pdt}) is false - + + set {_typeKey} to namespaced key "%{_key}%..type" + {_pdc}.remove({_typeKey}) {_pdc}.remove({_key}) + expression [devdinc] [namespaced]( |-)key %string%: parse: continue get: return NamespacedKey.fromString(expr-1, null) - + + object property p[ersistent ]d[ata ]c[ontainer]: parse: continue get: return expr-1.getPersistentDataContainer() + expression [devdinc] new p[ersistent ]d[ata ]c[ontainer] from %object%: parse: continue - get: set {_src} to expr-1 @@ -130,28 +205,12 @@ expression [devdinc] new p[ersistent ]d[ata ]c[ontainer] from %object%: return {_none} + expression [devdinc] (pdt|[persistent data ]type) [of] (1:str[ing]|2:int[eger]|3:long|4:float|5:double|6:byte|7:short|8:bool[ean]|9:container): parse: continue get: - if parse mark is 1: - return PersistentDataType.STRING - else if parse mark is 2: - return PersistentDataType.INTEGER - else if parse mark is 3: - return PersistentDataType.LONG - else if parse mark is 4: - return PersistentDataType.FLOAT - else if parse mark is 5: - return PersistentDataType.DOUBLE - else if parse mark is 6: - return PersistentDataType.BYTE - else if parse mark is 7: - return PersistentDataType.SHORT - else if parse mark is 8: - return PersistentDataType.BOOLEAN - else if parse mark is 9: - return PersistentDataType.TAG_CONTAINER - else: - return {_none} + set {_l::*} to scoped {-primitives::int::*} + set {_i} to {_l::%parse mark%} + return {_i}th element of scoped {-primitives::pdt::*} diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index cb70495..a7d83e9 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -1,3 +1,4 @@ +# todo registration process should use parse of event using reflection import: @@ -336,15 +337,19 @@ devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 fail test halts by default": broadcast "ERROR: fail test did not halt" devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 autorun flag is true": + if event-test is not autorun: + stop assert true: event-test is autorun devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 manual run flag is false" when {_none} is set: fail test devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 manual run flag" when event-test is not autorun: - fail test + fail test if event-test is autorun devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 stop auto execution works": + if event-test is not autorun: + stop stop auto test execution here broadcast "ERROR: auto execution not stopped" diff --git a/tests/pdc.sk b/tests/pdc.sk index bbbf0be..2d89c53 100644 --- a/tests/pdc.sk +++ b/tests/pdc.sk @@ -1,80 +1,69 @@ import: java.util.HashMap - java.lang.Integer - java.lang.Byte + +on load: + set scoped {-key} to namespaced-key "plugin:test_pdt" + +after each test: + delete scoped {-key} within test-world if scoped {-key} is set test "pdc basic set/get with typed pdt": - set {_key} to namespaced-key "plugin:test_int" - - set {_key} within test-world for pdt integer to new Integer(42) - assert {_key} within test-world for pdt integer is 42 with "typed pdt get failed" + set scoped {-key} within test-world for pdt integer to 42 + assert scoped {-key} within test-world for pdt integer is 42 with "typed pdt get failed" test "pdc overwrite typed value": - set {_key} to namespaced-key "plugin:test_overwrite" - - set {_key} within test-world for pdt string to "a" - set {_key} within test-world for pdt string to "b" + set scoped {-key} within test-world for pdt string to "a" + set scoped {-key} within test-world for pdt string to "b" - assert {_key} within test-world for pdt string is "b" with "overwrite failed" + assert scoped {-key} within test-world for pdt string is "b" with "overwrite failed" test "pdc delete typed value": + set scoped {-key} within test-world for pdt long to 100 + delete scoped {-key} within test-world for pdt long - set {_key} to namespaced-key "plugin:test_delete" - - set {_key} within test-world for pdt long to 100 - delete {_key} within test-world for pdt long - - assert {_key} within test-world for pdt long is not set with "delete failed" + assert scoped {-key} within test-world for pdt long is not set with "delete failed" test "pdc object serialization (byte_array implicit)": - set {_key} to namespaced-key "plugin:test_object" - set {_obj} to new HashMap() {_obj}.put("a", 1) {_obj}.put("b", 2) - set {_key} within test-world to {_obj} + set scoped {-key} within test-world to {_obj} - set {_out} to {_key} within test-world + set {_out} to scoped {-key} within test-world assert {_out}.get("a") is 1 with "object deserialize failed (a)" assert {_out}.get("b") is 2 with "object deserialize failed (b)" test "pdc remove object via null": - set {_key} to namespaced-key "plugin:test_object_remove" + set scoped {-key} within test-world to "temp" + set scoped {-key} within test-world to null - set {_key} within test-world to "temp" - set {_key} within test-world to null - - assert {_key} within test-world is not set with "object remove via null failed" + assert scoped {-key} within test-world is not set with "object remove via null failed" test "pdc holder vs direct pdc equivalence": - - set {_key} to namespaced-key "plugin:test_holder" set {_pdc} to test-world's pdc - set {_key} within test-world for pdt byte to new Byte(7) - assert {_key} within {_pdc} for pdt byte is 7 with "direct pdc access failed" + set scoped {-key} within test-world for pdt byte to 7 + assert scoped {-key} within {_pdc} for pdt byte is 7 with "direct pdc access failed" test "pdc boolean pdt": - set {_key} to namespaced-key "plugin:test_bool" - - set {_key} within test-world for pdt boolean to true - assert {_key} within test-world for pdt boolean is true with "boolean pdt failed" + set scoped {-key} within test-world for pdt boolean to true + assert scoped {-key} within test-world for pdt boolean is true with "boolean pdt failed" test "pdc default object missing returns none": - set {_key} to namespaced-key "plugin:test_missing" - - assert {_key} within test-world is not set with "missing object did not return none" + assert scoped {-key} within test-world is not set with "missing object did not return none" + +# todo more tests with unspecified pdt diff --git a/tests/routines.sk b/tests/routines.sk index 7daff81..b76a3dc 100644 --- a/tests/routines.sk +++ b/tests/routines.sk @@ -5,7 +5,7 @@ test "routines basic": set {_res} to routine.fluent(instant now).context(async context).supply(lambda:+ new URL("https://www.dummy2.com")).join() assert "%{_res}%" is "https://www.dummy2.com" with "java call" - set {_res} to wait for fluent routine lambda: routine_local_get_url() + set {_res} to wait for fluent routine lambda:+ routine_local_get_url() assert "%{_res}%" is "https://www.dummy1.com" with "function call" diff --git a/tests/scopedvariable.sk b/tests/scopedvariable.sk index a8da00b..9c7ee91 100644 --- a/tests/scopedvariable.sk +++ b/tests/scopedvariable.sk @@ -48,3 +48,11 @@ test "scoped variable - non-ephemeral (named)": test "scoped variable - script scoping": set scoped {-scoped} within current script to 100 assert scoped {-scoped} is 100 + +test "scoped variable - inner expression resolution": + set {_i} to 1 + set scoped {-list::*} to 20 + assert scoped {-list::%{_i}%} is 20 to fail # this sadly fails + set {_list::*} to scoped {-list::*} + assert {_list::%{_i}%} is 20 + assert scoped {-list::1} is 20 to fail # same From 3d9d60daa74bf3d76b8ad5497bc6ca13734c013c Mon Sep 17 00:00:00 2001 From: devdinc Date: Sat, 31 Jan 2026 20:00:22 +0300 Subject: [PATCH 13/38] fix variable resolution inside scoped variables --- scripts/lang/scopedvariable.sk | 23 +++++++++++++++-------- tests/scopedvariable.sk | 4 ++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/scripts/lang/scopedvariable.sk b/scripts/lang/scopedvariable.sk index c934162..c0c5ffe 100644 --- a/scripts/lang/scopedvariable.sk +++ b/scripts/lang/scopedvariable.sk @@ -13,8 +13,9 @@ expression scoped [variable] %$objects% [([with]in|from) (%-script%|%-string%)]: set {_var} to expr-1 continue - get: # todo sadly {_var}.getName().toString(event) does not work(ClassCastException), meaning we cant resolve expressions inside - set {_name} to try {_var}.getName().toString() + get: + set {_name} to try {_var}.getName().toUnformattedString(event) + replace "::*" with "" in {_name} set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): @@ -29,7 +30,8 @@ expression scoped [variable] %$objects% [([with]in|from) (%-script%|%-string%)]: return {local::%{_script}%::%{_name}%} set: - set {_name} to try {_var}.getName().toString() + set {_name} to try {_var}.getName().toUnformattedString(event) + replace "::*" with "" in {_name} set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): @@ -44,7 +46,8 @@ expression scoped [variable] %$objects% [([with]in|from) (%-script%|%-string%)]: set {local::%{_script}%::%{_name}%} to change value add: - set {_name} to try {_var}.getName().toString() + set {_name} to try {_var}.getName().toUnformattedString(event) + replace "::*" with "" in {_name} set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): @@ -59,7 +62,8 @@ expression scoped [variable] %$objects% [([with]in|from) (%-script%|%-string%)]: add change values to {local::%{_script}%::%{_name}%} remove: - set {_name} to try {_var}.getName().toString() + set {_name} to try {_var}.getName().toUnformattedString(event) + replace "::*" with "" in {_name} set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): @@ -74,7 +78,8 @@ expression scoped [variable] %$objects% [([with]in|from) (%-script%|%-string%)]: remove change values from {local::%{_script}%::%{_name}%} remove all: - set {_name} to try {_var}.getName().toString() + set {_name} to try {_var}.getName().toUnformattedString(event) + replace "::*" with "" in {_name} set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): @@ -89,7 +94,8 @@ expression scoped [variable] %$objects% [([with]in|from) (%-script%|%-string%)]: remove all change values from {local::%{_script}%::%{_name}%} delete: - set {_name} to try {_var}.getName().toString() + set {_name} to try {_var}.getName().toUnformattedString(event) + replace "::*" with "" in {_name} set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): @@ -104,7 +110,8 @@ expression scoped [variable] %$objects% [([with]in|from) (%-script%|%-string%)]: delete {local::%{_script}%::%{_name}%} reset: - set {_name} to try {_var}.getName().toString() + set {_name} to try {_var}.getName().toUnformattedString(event) + replace "::*" with "" in {_name} set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" if {_var}.isEphemeral(): diff --git a/tests/scopedvariable.sk b/tests/scopedvariable.sk index 9c7ee91..d704300 100644 --- a/tests/scopedvariable.sk +++ b/tests/scopedvariable.sk @@ -52,7 +52,7 @@ test "scoped variable - script scoping": test "scoped variable - inner expression resolution": set {_i} to 1 set scoped {-list::*} to 20 - assert scoped {-list::%{_i}%} is 20 to fail # this sadly fails + assert scoped {-list::%{_i}%} is 20 set {_list::*} to scoped {-list::*} assert {_list::%{_i}%} is 20 - assert scoped {-list::1} is 20 to fail # same + assert scoped {-list::1} is 20 From 2f545b8bd343a464aec3730fcb4b4f75cd08de40 Mon Sep 17 00:00:00 2001 From: devdinc Date: Sun, 1 Feb 2026 20:46:41 +0300 Subject: [PATCH 14/38] fix repeat effect, alpha improvements on scoped variables, test framework plans and fix current script selection, more tests for scoped variable --- scripts/lang/repeateffect.sk | 3 +- scripts/lang/scopedvariable.sk | 296 ++++++++++++++++++++++++--------- scripts/utils/testframework.sk | 8 +- tests/scopedvariable.sk | 55 ++++++ 4 files changed, 282 insertions(+), 80 deletions(-) diff --git a/scripts/lang/repeateffect.sk b/scripts/lang/repeateffect.sk index c4c5067..def4dbb 100644 --- a/scripts/lang/repeateffect.sk +++ b/scripts/lang/repeateffect.sk @@ -3,7 +3,8 @@ import: effect [repeat] <(.+)> (1:once|2:twice|3:thrice|4:%-number% times): parse: - set {_repeat.sk::effect} to Effect.parse(first element of regex-1) + set {_a} to first element of regex-1 + set {_repeat.sk::effect} to Effect.parse({_a}, "Can't understand effect: ") continue trigger: set {_times} to expr-1 diff --git a/scripts/lang/scopedvariable.sk b/scripts/lang/scopedvariable.sk index c0c5ffe..2768898 100644 --- a/scripts/lang/scopedvariable.sk +++ b/scripts/lang/scopedvariable.sk @@ -1,126 +1,268 @@ import: + ch.njol.skript.log.SkriptLogger ch.njol.skript.lang.Variable + com.btk5h.skriptmirror.WrappedEvent ch.njol.skript.variables.Variables - ch.njol.skript.log.SkriptLogger + java.util.Map + +# todo move this to a seperate file +local expression variable %$objects% with event %object%: + parse: + if size of change values is greater than 1: + {_isList} is false + stop # todo a error log + + set {_var} to expr-1 + set {_isLocal} to {_var}.isLocal() + set {_isList} to {_var}.isList() + continue + set: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + + if {_isList} is true: + set {_base} to {_name}.substring(0, {_name}.length() - 3) + Variables.setVariable("%{_base}%::*", null, expr-2, {_isLocal}) + set {_i} to 1 + loop change values: + Variables.setVariable("%{_base}%::%{_i}%", loop-value, expr-2, {_isLocal}) + add 1 to {_i} + stop + + Variables.setVariable({_name}, change value, expr-2, {_isLocal}) + + + get: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + + if {_value} is instance of Map: + # convert map to list using Java Map methods + set {_base} to {_name}.substring(0, {_name}.length() - 3) + + loop {_value}.keySet(): # todo iterator would be better + set {_list::%loop-value%} to {_value}.get(loop-value) + return {_list::*} + + # scalar fallback + return {_value} + + + add: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + + if {_value} is instance of Map: + set {_base} to {_name}.substring(0, {_name}.length() - 3) + set {_i} to {_value}.size() + 1 + loop change values: + Variables.setVariable("%{_base}%::%{_i}%", loop-value, expr-2, {_isLocal}) + add 1 to {_i} + stop + + # scalar add + if {_value} is not set: + Variables.setVariable({_name}, change value, expr-2, {_isLocal}) + else: + Variables.setVariable({_name}, {_value} + change value, expr-2, {_isLocal}) + + remove: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + + if {_value} is instance of Map: + set {_it} to {_value}.entrySet().iterator() + while {_it}.hasNext(): + set {_entry} to {_it}.next() + if change values contains {_entry}.getValue(): + Variables.setVariable({_entry}.getKey(), null, expr-2, {_isLocal}) + stop + stop + + # scalar remove = subtract + Variables.setVariable({_name}, {_value} - change value, expr-2, {_isLocal}) + -# todo scoped within current package/folder, local variables? -expression scoped [variable] %$objects% [([with]in|from) (%-script%|%-string%)]: + remove all: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + + if {_value} is instance of Map: + set {_it} to {_value}.entrySet().iterator() + while {_it}.hasNext(): + set {_entry} to {_it}.next() + if change values contains {_entry}.getValue(): + Variables.setVariable({_entry}.getKey(), null, expr-2, {_isLocal}) + stop + + delete: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + Variables.setVariable({_name}, null, expr-2, {_isLocal}) + + + + reset: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + Variables.setVariable({_name}, null, expr-2, {_isLocal}) + +effect set default scope for scoped variables in current script to (a:current (x:folder|x:package|y:script)|b:(folder|package) %-string%|c: %-script%): parse: - expr-1 is an instance of Variable set {_origin} to SkriptLogger.getNode() - set {_currentscript} to script ({_origin}.getConfig().getFileName()) + set {_realscript} to script ({_origin}.getConfig().getFileName()) + continue + trigger: + if parse tags contains "a": + if parse tags contains "x": + set {_currentscript} to {_origin}.getConfig().getPath().getParent().toString() + set {_currentscript} to {_currentscript}.substring({_currentscript}.indexOf("scripts/") + 8) + set {-scoped.sk::default_scope::%{_realscript}%} to {_currentscript} + else if parse tags contains "y": + set {-scoped.sk::default_scope::%{_realscript}%} to {_realscript} + else if parse tags contains "b": + set {-scoped.sk::default_scope::%{_realscript}%} to expr-1 + else if parse tags contains "c": + set {-scoped.sk::default_scope::%{_realscript}%} to expr-2 + +local function scoped_ctx(origin: object, event: object, var: object, script: object, mark: number) :: objects: + # origin + script + set {_realscript} to script ({_origin}.getConfig().getFileName()) + + # --- scope --- + if {_mark} is 0: + set {_scope} to {_realscript} if {-scoped.sk::default_scope::%{_realscript}%} is not set else {-scoped.sk::default_scope::%{_realscript}%} + else if {_mark} is 1: + set {_scope} to {_script} + else if {_mark} is 2: + set {_scope} to {_origin}.getConfig().getPath().getParent().toString() + set {_scope} to {_scope}.substring({_scope}.indexOf("scripts/") + 8) + + if {_var}.isLocal(): + set {_scope} to "%{_origin}.getParent()%::%{_scope}%" + + # --- name --- + set {_name} to {_var}.getName().toUnformattedString({_event}) + replace "::*" with "" in {_name} + + # --- flags --- + set {_ctx::1scope} to {_scope} + set {_ctx::2name} to {_name} + set {_ctx::3list} to {_var}.isList() + if any: + {_var}.isLocal() + {_var}.isEphemeral() + then: + set {_ctx::4temp} to true + else: + set {_ctx::4temp} to false + + return {_ctx::*} + + +expression scoped [variable] %$objects% [([with]in|from|by) (1:%-script%|2:current (folder|package))]: + parse: + expr-1 is an instance of Variable set {_var} to expr-1 + + # we use this to do local variables and to capture backing expression's script, could be made cleaner. + + # for locals we need to make it ephemeral, if name starts with _, if we could access previous event we could have made it really local, but this is the only way + # we use origin.toString + set {_origin} to SkriptLogger.getNode() continue get: - set {_name} to try {_var}.getName().toUnformattedString(event) - replace "::*" with "" in {_name} - set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" - - if {_var}.isEphemeral(): - if {_var}.isList(): - return {-local::%{_script}%::%{_name}%::*} + set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) + if {_ctx::4} is true: + if {_ctx::3} is true: + return {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - return {-local::%{_script}%::%{_name}%} + return {-scoped::%{_ctx::1}%::%{_ctx::2}%} else: - if {_var}.isList(): - return {local::%{_script}%::%{_name}%::*} + if {_ctx::3} is true: + return {scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - return {local::%{_script}%::%{_name}%} + return {scoped::%{_ctx::1}%::%{_ctx::2}%} set: - set {_name} to try {_var}.getName().toUnformattedString(event) - replace "::*" with "" in {_name} - set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" - - if {_var}.isEphemeral(): - if {_var}.isList(): - set {-local::%{_script}%::%{_name}%::*} to change values + set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) + if {_ctx::4} is true: + if {_ctx::3} is true: + set {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} to change values else: - set {-local::%{_script}%::%{_name}%} to change value + set {-scoped::%{_ctx::1}%::%{_ctx::2}%} to change value else: - if {_var}.isList(): - set {local::%{_script}%::%{_name}%::*} to change values + if {_ctx::3} is true: + set {scoped::%{_ctx::1}%::%{_ctx::2}%::*} to change values else: - set {local::%{_script}%::%{_name}%} to change value + set {scoped::%{_ctx::1}%::%{_ctx::2}%} to change value add: - set {_name} to try {_var}.getName().toUnformattedString(event) - replace "::*" with "" in {_name} - set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" + set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_var}.isEphemeral(): - if {_var}.isList(): - add change values to {-local::%{_script}%::%{_name}%::*} + if {_ctx::4} is true: + if {_ctx::3} is true: + add change values to {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - add change values to {-local::%{_script}%::%{_name}%} + add change values to {-scoped::%{_ctx::1}%::%{_ctx::2}%} else: - if {_var}.isList(): - add change values to {local::%{_script}%::%{_name}%::*} + if {_ctx::3} is true: + add change values to {scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - add change values to {local::%{_script}%::%{_name}%} + add change values to {scoped::%{_ctx::1}%::%{_ctx::2}%} remove: - set {_name} to try {_var}.getName().toUnformattedString(event) - replace "::*" with "" in {_name} - set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" + set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_var}.isEphemeral(): - if {_var}.isList(): - remove change values from {-local::%{_script}%::%{_name}%::*} + if {_ctx::4} is true: + if {_ctx::3} is true: + remove change values from {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - remove change values from {-local::%{_script}%::%{_name}%} + remove change values from {-scoped::%{_ctx::1}%::%{_ctx::2}%} else: - if {_var}.isList(): - remove change values from {local::%{_script}%::%{_name}%::*} + if {_ctx::3} is true: + remove change values from {scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - remove change values from {local::%{_script}%::%{_name}%} + remove change values from {scoped::%{_ctx::1}%::%{_ctx::2}%} remove all: - set {_name} to try {_var}.getName().toUnformattedString(event) - replace "::*" with "" in {_name} - set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" + set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_var}.isEphemeral(): - if {_var}.isList(): - remove all change values from {-local::%{_script}%::%{_name}%::*} + if {_ctx::4} is true: + if {_ctx::3} is true: + remove all change values from {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - remove all change values from {-local::%{_script}%::%{_name}%} + remove all change values from {-scoped::%{_ctx::1}%::%{_ctx::2}%} else: - if {_var}.isList(): - remove all change values from {local::%{_script}%::%{_name}%::*} + if {_ctx::3} is true: + remove all change values from {scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - remove all change values from {local::%{_script}%::%{_name}%} + remove all change values from {scoped::%{_ctx::1}%::%{_ctx::2}%} delete: - set {_name} to try {_var}.getName().toUnformattedString(event) - replace "::*" with "" in {_name} - set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" + set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_var}.isEphemeral(): - if {_var}.isList(): - delete {-local::%{_script}%::%{_name}%::*} + if {_ctx::4} is true: + if {_ctx::3} is true: + delete {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - delete {-local::%{_script}%::%{_name}%} + delete {-scoped::%{_ctx::1}%::%{_ctx::2}%} else: - if {_var}.isList(): - delete {local::%{_script}%::%{_name}%::*} + if {_ctx::3} is true: + delete {scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - delete {local::%{_script}%::%{_name}%} + delete {scoped::%{_ctx::1}%::%{_ctx::2}%} reset: - set {_name} to try {_var}.getName().toUnformattedString(event) - replace "::*" with "" in {_name} - set {_script} to "%{_currentscript}%" if expr-2 is not set else "%expr-2%" + set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_var}.isEphemeral(): - if {_var}.isList(): - clear {-local::%{_script}%::%{_name}%::*} + if {_ctx::4} is true: + if {_ctx::3} is true: + clear {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - clear {-local::%{_script}%::%{_name}%} + clear {-scoped::%{_ctx::1}%::%{_ctx::2}%} else: - if {_var}.isList(): - clear {local::%{_script}%::%{_name}%::*} + if {_ctx::3} is true: + clear {scoped::%{_ctx::1}%::%{_ctx::2}%::*} else: - clear {local::%{_script}%::%{_name}%} + clear {scoped::%{_ctx::1}%::%{_ctx::2}%} + diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index a7d83e9..0e4b2da 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -1,4 +1,6 @@ # todo registration process should use parse of event +# todo tests should be isolated between .sk files, so before/after is triggered only if a test within that script is running +# todo all tests within %script% using reflection import: @@ -7,6 +9,7 @@ import: ch.njol.skript.test.utils.TestOfflinePlayer ch.njol.skript.lang.Condition ch.njol.skript.test.runner.TestTracker + ch.njol.skript.log.SkriptLogger local effect sendTestFailMessage %string%[, %-string%]: trigger: @@ -139,12 +142,14 @@ effect: custom event "skriptTest" parse: set {_raw} to first element of regex-1 + set {_origin} to SkriptLogger.getNode() + set {_currentscript} to script ({_origin}.getConfig().getFileName()) set {_parser} to ParserInstance.get() set {_parser}.[ParserInstance]isActive to true set {_backup} to {_parser}.backup() - {_parser}.setCurrentScript(current script) + {_parser}.setCurrentScript({_currentscript}) {_parser}.setCurrentEvent("assert condition", (custom event "skriptTest").getClass()) set {_cond} to Condition.parse({_raw}, "Can't understand condition: " + {_raw}) @@ -221,7 +226,6 @@ condition %string% (1:is|2:is not|2:isn't) autorun: import: ch.njol.skript.ScriptLoader - ch.njol.skript.log.SkriptLogger condition parse: parse: diff --git a/tests/scopedvariable.sk b/tests/scopedvariable.sk index d704300..3628539 100644 --- a/tests/scopedvariable.sk +++ b/tests/scopedvariable.sk @@ -48,6 +48,8 @@ test "scoped variable - non-ephemeral (named)": test "scoped variable - script scoping": set scoped {-scoped} within current script to 100 assert scoped {-scoped} is 100 + set scoped {-scoped2} to 100 + assert scoped {-scoped2} within current script is 100 test "scoped variable - inner expression resolution": set {_i} to 1 @@ -56,3 +58,56 @@ test "scoped variable - inner expression resolution": set {_list::*} to scoped {-list::*} assert {_list::%{_i}%} is 20 assert scoped {-list::1} is 20 + +test "scoped variable - folder vs current script isolation": + set scoped {-x} within current script to 1 + set scoped {-x} within current folder to 2 + + assert scoped {-x} within current script is 1 + assert scoped {-x} within current folder is 2 + +devdinc test "scoped variable - default scope switch to folder": + set default scope for scoped variables in current script to current folder + + set scoped {-d} to 42 + + without halting assert scoped {-d} within current folder is 42 + without halting assert scoped {-d} within current script is not set + set default scope for scoped variables in current script to current script + +test "scoped variable - default scope is script-local": + set scoped {-a} to 5 + + set default scope for scoped variables in current script to current folder + set scoped {-b} to 6 + + assert scoped {-a} within current script is 5 + assert scoped {-b} within current folder is 6 + set default scope for scoped variables in current script to current script + +#test "scoped variable - list folder isolation": +# set scoped {-l::*} within folder "one" to "a", "b" +# set scoped {-l::*} within folder "two" to "c" +# +# assert scoped {-l::*} within folder "one" is "a", "b" +# assert scoped {-l::*} within folder "two" is "c" + +test "scoped variable - reset does not affect other scopes": + set scoped {-r} within current folder to 1 + set scoped {-r} within current script to 2 + + reset scoped {-r} within current script + + assert scoped {-r} within current script is not set + assert scoped {-r} within current folder is 1 + +# todo sadly locals do not reset after a scope is over..., to mitigate we do this +after all tests: + delete scoped {_locals::*} + +test "scoped variable - locals a": + set scoped {_locals::a} to 15 + assert scoped {_locals::a} is 15 + +test "scoped variable - locals b": + assert scoped {_locals::a} is not 15 From ce64de06758d38ff14b9aab9bd36fb8532958f23 Mon Sep 17 00:00:00 2001 From: devdinc Date: Sun, 1 Feb 2026 21:30:45 +0300 Subject: [PATCH 15/38] eventspecifiedvariables.sk --- scripts/lang/eventspecifiedvariables.sk | 115 ++++++++++++++++++++++++ scripts/lang/scopedvariable.sk | 103 --------------------- tests/eventspecifiedvariables.sk | 90 +++++++++++++++++++ 3 files changed, 205 insertions(+), 103 deletions(-) create mode 100644 scripts/lang/eventspecifiedvariables.sk create mode 100644 tests/eventspecifiedvariables.sk diff --git a/scripts/lang/eventspecifiedvariables.sk b/scripts/lang/eventspecifiedvariables.sk new file mode 100644 index 0000000..3abc37c --- /dev/null +++ b/scripts/lang/eventspecifiedvariables.sk @@ -0,0 +1,115 @@ +import: + java.util.Map + ch.njol.skript.variables.Variables + ch.njol.skript.lang.Variable + ch.njol.skript.Skript + org.bukkit.event.Event + +expression variable %$objects% with event %object%: + parse: + if expr-1 is not an instance of Variable: + Skript.error("Expression 1 must be a variable") + stop + + if expr-2 is not an instance of Event: + Skript.error("Expression 2 must be an event") + stop + + if size of change values is greater than 1: + {_isList} is false + Skript.error("You can't set a single variable to multiple values") + stop + + set {_var} to expr-1 + set {_isLocal} to {_var}.isLocal() + set {_isList} to {_var}.isList() + continue + + set: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + + if {_isList} is true: + set {_base} to {_name}.substring(0, {_name}.length() - 3) + Variables.setVariable("%{_base}%::*", null, expr-2, {_isLocal}) + set {_i} to 1 + loop change values: + Variables.setVariable("%{_base}%::%{_i}%", loop-value, expr-2, {_isLocal}) + add 1 to {_i} + stop + + Variables.setVariable({_name}, change value, expr-2, {_isLocal}) + + get: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + + if {_value} is instance of Map: + set {_base} to {_name}.substring(0, {_name}.length() - 3) + set {_it} to {_value}.entrySet().iterator() + while {_it}.hasNext(): + set {_entry} to {_it}.next() + set {_list::%{_entry}.getKey()%} to {_entry}.getValue() + return {_list::*} + + # scalar fallback + return {_value} + + add: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + + if {_value} is instance of Map: + set {_base} to {_name}.substring(0, {_name}.length() - 3) + set {_i} to {_value}.size() + 1 + loop change values: + Variables.setVariable("%{_base}%::%{_i}%", loop-value, expr-2, {_isLocal}) + add 1 to {_i} + stop + + # scalar add + if {_value} is not set: + Variables.setVariable({_name}, change value, expr-2, {_isLocal}) + else: + Variables.setVariable({_name}, {_value} + change value, expr-2, {_isLocal}) + + remove: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + + if {_value} is instance of Map: + set {_base} to {_name}.substring(0, {_name}.length() - 3) + set {_it} to {_value}.entrySet().iterator() + while {_it}.hasNext(): + set {_entry} to {_it}.next() + if change values contains {_entry}.getValue(): + Variables.setVariable("%{_base}::%{_entry}.getKey()%", null, expr-2, {_isLocal}) + stop + stop + + # scalar remove = subtract + Variables.setVariable({_name}, {_value} - change value, expr-2, {_isLocal}) + + + + remove all: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + + if {_value} is instance of Map: + set {_base} to {_name}.substring(0, {_name}.length() - 3) + set {_it} to {_value}.entrySet().iterator() + while {_it}.hasNext(): + set {_entry} to {_it}.next() + if change values contains {_entry}.getValue(): + Variables.setVariable("%{_base}::%{_entry}.getKey()%", null, expr-2, {_isLocal}) + stop + + delete: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + Variables.setVariable({_name}, null, expr-2, {_isLocal}) + + + + reset: + set {_name} to {_var}.getName().toUnformattedString(expr-2) + Variables.setVariable({_name}, null, expr-2, {_isLocal}) diff --git a/scripts/lang/scopedvariable.sk b/scripts/lang/scopedvariable.sk index 2768898..31f8de1 100644 --- a/scripts/lang/scopedvariable.sk +++ b/scripts/lang/scopedvariable.sk @@ -1,109 +1,6 @@ import: ch.njol.skript.log.SkriptLogger ch.njol.skript.lang.Variable - com.btk5h.skriptmirror.WrappedEvent - ch.njol.skript.variables.Variables - java.util.Map - -# todo move this to a seperate file -local expression variable %$objects% with event %object%: - parse: - if size of change values is greater than 1: - {_isList} is false - stop # todo a error log - - set {_var} to expr-1 - set {_isLocal} to {_var}.isLocal() - set {_isList} to {_var}.isList() - continue - set: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - - if {_isList} is true: - set {_base} to {_name}.substring(0, {_name}.length() - 3) - Variables.setVariable("%{_base}%::*", null, expr-2, {_isLocal}) - set {_i} to 1 - loop change values: - Variables.setVariable("%{_base}%::%{_i}%", loop-value, expr-2, {_isLocal}) - add 1 to {_i} - stop - - Variables.setVariable({_name}, change value, expr-2, {_isLocal}) - - - get: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) - - if {_value} is instance of Map: - # convert map to list using Java Map methods - set {_base} to {_name}.substring(0, {_name}.length() - 3) - - loop {_value}.keySet(): # todo iterator would be better - set {_list::%loop-value%} to {_value}.get(loop-value) - return {_list::*} - - # scalar fallback - return {_value} - - - add: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) - - if {_value} is instance of Map: - set {_base} to {_name}.substring(0, {_name}.length() - 3) - set {_i} to {_value}.size() + 1 - loop change values: - Variables.setVariable("%{_base}%::%{_i}%", loop-value, expr-2, {_isLocal}) - add 1 to {_i} - stop - - # scalar add - if {_value} is not set: - Variables.setVariable({_name}, change value, expr-2, {_isLocal}) - else: - Variables.setVariable({_name}, {_value} + change value, expr-2, {_isLocal}) - - remove: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) - - if {_value} is instance of Map: - set {_it} to {_value}.entrySet().iterator() - while {_it}.hasNext(): - set {_entry} to {_it}.next() - if change values contains {_entry}.getValue(): - Variables.setVariable({_entry}.getKey(), null, expr-2, {_isLocal}) - stop - stop - - # scalar remove = subtract - Variables.setVariable({_name}, {_value} - change value, expr-2, {_isLocal}) - - - - remove all: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) - - if {_value} is instance of Map: - set {_it} to {_value}.entrySet().iterator() - while {_it}.hasNext(): - set {_entry} to {_it}.next() - if change values contains {_entry}.getValue(): - Variables.setVariable({_entry}.getKey(), null, expr-2, {_isLocal}) - stop - - delete: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - Variables.setVariable({_name}, null, expr-2, {_isLocal}) - - - - reset: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - Variables.setVariable({_name}, null, expr-2, {_isLocal}) effect set default scope for scoped variables in current script to (a:current (x:folder|x:package|y:script)|b:(folder|package) %-string%|c: %-script%): parse: diff --git a/tests/eventspecifiedvariables.sk b/tests/eventspecifiedvariables.sk new file mode 100644 index 0000000..d01d4f4 --- /dev/null +++ b/tests/eventspecifiedvariables.sk @@ -0,0 +1,90 @@ +test "variable scalar set/get": + set {_x} to 5 + set {_res} to variable {_x} with event + assert {_res} is 5 with "scalar get" + +test "variable scalar set overwrite": + set {_x} to 1 + set variable {_x} with event to 10 + assert {_x} is 10 with "scalar set overwrite" + +test "variable scalar add unset": + delete {_x} + add 3 to variable {_x} with event + assert {_x} is 3 with "scalar add when unset" + +test "variable scalar add existing": + set {_x} to 4 + add 6 to variable {_x} with event + assert {_x} is 10 with "scalar add existing" + +test "variable scalar remove": + set {_x} to 10 + remove 3 from variable {_x} with event + assert {_x} is 7 with "scalar remove subtract" + +test "variable scalar delete": + set {_x} to 5 + delete variable {_x} with event + assert {_x} is not set with "scalar delete" + +test "variable scalar reset": + set {_x} to 5 + reset variable {_x} with event + assert {_x} is not set with "scalar reset" + +test "variable list set/get": + set {_list::*} to "a", "b", "c" + set {_res::*} to variable {_list::*} with event + assert {_res::1} is "a" with "list get index 1" + assert {_res::2} is "b" with "list get index 2" + assert {_res::3} is "c" with "list get index 3" + +test "variable list set clears previous": + set {_list::*} to "x", "y" + set variable {_list::*} with event to "a", "b" + assert {_list::1} is "a" with "list overwrite clears old" + assert {_list::2} is "b" with "list overwrite new value" + assert {_list::3} is not set with "list cleared extra entries" + +test "variable list add": + set {_list::*} to "a", "b" + add "c", "d" to variable {_list::*} with event + assert {_list::1} is "a" with "list add preserves index 1" + assert {_list::2} is "b" with "list add preserves index 2" + assert {_list::3} is "c" with "list add index 3" + assert {_list::4} is "d" with "list add index 4" + +test "variable list remove first match": + set {_list::*} to "a", "b", "c" + remove "b" from variable {_list::*} with event + assert {_list::2} is not set with "list remove deletes matching entry" + assert {_list::1} is "a" with "list remove keeps others" + +test "variable list remove all": + set {_list::*} to "a", "b", "a", "c" + remove all "a" from variable {_list::*} with event + assert {_list::1} is not "a" with "list remove all removes first" + assert {_list::3} is not "a" with "list remove all removes duplicates" + +test "variable list delete": + set {_list::*} to "x", "y" + delete variable {_list::*} with event + assert {_list::*} is not set with "list delete clears list" + +test "variable list reset": + set {_list::*} to "x", "y" + reset variable {_list::*} with event + assert {_list::*} is not set with "list reset clears list" + +test "variable parse rejects non-variable": + parse: + set {_x} to variable 5 with event + assert last parse logs contains "Expression 1 must be a variable" + +test "variable parse rejects multiple change values": + parse: + set variable {_x} with event to 1, 2 + assert last parse logs contains "You can't set a single variable to multiple values" + + From cd2701a0158554c16428cda1dc2625dbbb4687fd Mon Sep 17 00:00:00 2001 From: devdinc Date: Mon, 2 Feb 2026 00:37:39 +0300 Subject: [PATCH 16/38] event specified variables finish? --- scripts/lang/eventspecifiedvariables.sk | 90 +++++++++++++------------ tests/eventspecifiedvariables.sk | 65 ++++++++++-------- 2 files changed, 85 insertions(+), 70 deletions(-) diff --git a/scripts/lang/eventspecifiedvariables.sk b/scripts/lang/eventspecifiedvariables.sk index 3abc37c..52c8889 100644 --- a/scripts/lang/eventspecifiedvariables.sk +++ b/scripts/lang/eventspecifiedvariables.sk @@ -5,20 +5,22 @@ import: ch.njol.skript.Skript org.bukkit.event.Event -expression variable %$objects% with event %object%: +expression variable %$objects% with custom-event [%-object%]: parse: if expr-1 is not an instance of Variable: Skript.error("Expression 1 must be a variable") stop - if expr-2 is not an instance of Event: + if expr-2 is set: + expr-2 is not an instance of Event Skript.error("Expression 2 must be an event") stop - - if size of change values is greater than 1: - {_isList} is false - Skript.error("You can't set a single variable to multiple values") - stop + + # change values does not exist here and we cant specify "set scope" + #if size of change values is greater than 1: + # {_isList} is false + # Skript.error("You can't set a single variable to multiple values") + # stop set {_var} to expr-1 set {_isLocal} to {_var}.isLocal() @@ -26,28 +28,31 @@ expression variable %$objects% with event %object%: continue set: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - - if {_isList} is true: + set {_event} to event if expr-2 is not set else expr-2 + set {_name} to {_var}.getName().toUnformattedString({_event}) + if any: + {_isList} is true + {_name} ends with "::*" # inner resolution, not how skript behaves but I want it this way + then: set {_base} to {_name}.substring(0, {_name}.length() - 3) - Variables.setVariable("%{_base}%::*", null, expr-2, {_isLocal}) + Variables.setVariable("%{_base}%::*", null, {_event}, {_isLocal}) set {_i} to 1 loop change values: - Variables.setVariable("%{_base}%::%{_i}%", loop-value, expr-2, {_isLocal}) + Variables.setVariable("%{_base}%::%{_i}%", loop-value, {_event}, {_isLocal}) add 1 to {_i} stop - Variables.setVariable({_name}, change value, expr-2, {_isLocal}) + Variables.setVariable({_name}, change value, {_event}, {_isLocal}) get: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + set {_event} to event if expr-2 is not set else expr-2 + set {_name} to {_var}.getName().toUnformattedString({_event}) + set {_value} to Variables.getVariable({_name}, {_event}, {_isLocal}) if {_value} is instance of Map: set {_base} to {_name}.substring(0, {_name}.length() - 3) - set {_it} to {_value}.entrySet().iterator() - while {_it}.hasNext(): - set {_entry} to {_it}.next() + loop ...{_value}.entrySet(): + set {_entry} to loop-value set {_list::%{_entry}.getKey()%} to {_entry}.getValue() return {_list::*} @@ -55,61 +60,62 @@ expression variable %$objects% with event %object%: return {_value} add: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + set {_event} to event if expr-2 is not set else expr-2 + set {_name} to {_var}.getName().toUnformattedString({_event}) + set {_value} to Variables.getVariable({_name}, {_event}, {_isLocal}) if {_value} is instance of Map: set {_base} to {_name}.substring(0, {_name}.length() - 3) set {_i} to {_value}.size() + 1 loop change values: - Variables.setVariable("%{_base}%::%{_i}%", loop-value, expr-2, {_isLocal}) + Variables.setVariable("%{_base}%::%{_i}%", loop-value, {_event}, {_isLocal}) add 1 to {_i} stop # scalar add if {_value} is not set: - Variables.setVariable({_name}, change value, expr-2, {_isLocal}) + Variables.setVariable({_name}, change value, {_event}, {_isLocal}) else: - Variables.setVariable({_name}, {_value} + change value, expr-2, {_isLocal}) + Variables.setVariable({_name}, {_value} + change value, {_event}, {_isLocal}) remove: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + set {_event} to event if expr-2 is not set else expr-2 + set {_name} to {_var}.getName().toUnformattedString({_event}) + set {_value} to Variables.getVariable({_name}, {_event}, {_isLocal}) if {_value} is instance of Map: set {_base} to {_name}.substring(0, {_name}.length() - 3) - set {_it} to {_value}.entrySet().iterator() - while {_it}.hasNext(): - set {_entry} to {_it}.next() + loop ...{_value}.entrySet(): + set {_entry} to loop-value if change values contains {_entry}.getValue(): - Variables.setVariable("%{_base}::%{_entry}.getKey()%", null, expr-2, {_isLocal}) + Variables.setVariable("%{_base}%::%{_entry}.getKey()%", null, {_event}, {_isLocal}) stop stop # scalar remove = subtract - Variables.setVariable({_name}, {_value} - change value, expr-2, {_isLocal}) + Variables.setVariable({_name}, {_value} - change value, {_event}, {_isLocal}) remove all: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - set {_value} to Variables.getVariable({_name}, expr-2, {_isLocal}) + set {_event} to event if expr-2 is not set else expr-2 + set {_name} to {_var}.getName().toUnformattedString({_event}) + set {_value} to Variables.getVariable({_name}, {_event}, {_isLocal}) if {_value} is instance of Map: set {_base} to {_name}.substring(0, {_name}.length() - 3) - set {_it} to {_value}.entrySet().iterator() - while {_it}.hasNext(): - set {_entry} to {_it}.next() + loop ...{_value}.entrySet(): + set {_entry} to loop-value if change values contains {_entry}.getValue(): - Variables.setVariable("%{_base}::%{_entry}.getKey()%", null, expr-2, {_isLocal}) + Variables.setVariable("%{_base}%::%{_entry}.getKey()%", null, {_event}, {_isLocal}) stop delete: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - Variables.setVariable({_name}, null, expr-2, {_isLocal}) - - + set {_event} to event if expr-2 is not set else expr-2 + set {_name} to {_var}.getName().toUnformattedString({_event}) + Variables.setVariable({_name}, null, {_event}, {_isLocal}) reset: - set {_name} to {_var}.getName().toUnformattedString(expr-2) - Variables.setVariable({_name}, null, expr-2, {_isLocal}) + set {_event} to event if expr-2 is not set else expr-2 + set {_name} to {_var}.getName().toUnformattedString({_event}) + Variables.setVariable({_name}, null, {_event}, {_isLocal}) diff --git a/tests/eventspecifiedvariables.sk b/tests/eventspecifiedvariables.sk index d01d4f4..3a79151 100644 --- a/tests/eventspecifiedvariables.sk +++ b/tests/eventspecifiedvariables.sk @@ -1,55 +1,58 @@ +import: + ch.njol.skript.log.SkriptLogger + test "variable scalar set/get": - set {_x} to 5 - set {_res} to variable {_x} with event + set variable {_x} with custom-event to 5 + set {_res} to variable {_x} with custom-event assert {_res} is 5 with "scalar get" test "variable scalar set overwrite": - set {_x} to 1 - set variable {_x} with event to 10 + set variable {_x} with custom-event to 1 + set variable {_x} with custom-event to 10 assert {_x} is 10 with "scalar set overwrite" test "variable scalar add unset": - delete {_x} - add 3 to variable {_x} with event + delete variable {_x} with custom-event + add 3 to variable {_x} with custom-event assert {_x} is 3 with "scalar add when unset" test "variable scalar add existing": - set {_x} to 4 - add 6 to variable {_x} with event + set variable {_x} with custom-event to 4 + add 6 to variable {_x} with custom-event assert {_x} is 10 with "scalar add existing" test "variable scalar remove": - set {_x} to 10 - remove 3 from variable {_x} with event + set variable {_x} with custom-event to 10 + remove 3 from variable {_x} with custom-event assert {_x} is 7 with "scalar remove subtract" test "variable scalar delete": - set {_x} to 5 - delete variable {_x} with event + set variable {_x} with custom-event to 5 + delete variable {_x} with custom-event assert {_x} is not set with "scalar delete" test "variable scalar reset": set {_x} to 5 - reset variable {_x} with event + reset variable {_x} with custom-event assert {_x} is not set with "scalar reset" test "variable list set/get": - set {_list::*} to "a", "b", "c" - set {_res::*} to variable {_list::*} with event + set variable {_list::*} with custom-event to "a", "b", "c" + set variable {_res::*} to variable {_list::*} with custom-event assert {_res::1} is "a" with "list get index 1" assert {_res::2} is "b" with "list get index 2" assert {_res::3} is "c" with "list get index 3" test "variable list set clears previous": set {_list::*} to "x", "y" - set variable {_list::*} with event to "a", "b" + set variable {_list::*} with custom-event to "a", "b" assert {_list::1} is "a" with "list overwrite clears old" assert {_list::2} is "b" with "list overwrite new value" assert {_list::3} is not set with "list cleared extra entries" test "variable list add": - set {_list::*} to "a", "b" - add "c", "d" to variable {_list::*} with event + set variable {_list::*} with custom-event to "a", "b" + add "c", "d" to variable {_list::*} with custom-event assert {_list::1} is "a" with "list add preserves index 1" assert {_list::2} is "b" with "list add preserves index 2" assert {_list::3} is "c" with "list add index 3" @@ -57,34 +60,40 @@ test "variable list add": test "variable list remove first match": set {_list::*} to "a", "b", "c" - remove "b" from variable {_list::*} with event + remove "b" from variable {_list::*} with custom-event assert {_list::2} is not set with "list remove deletes matching entry" assert {_list::1} is "a" with "list remove keeps others" test "variable list remove all": set {_list::*} to "a", "b", "a", "c" - remove all "a" from variable {_list::*} with event + remove all "a" from variable {_list::*} with custom-event assert {_list::1} is not "a" with "list remove all removes first" assert {_list::3} is not "a" with "list remove all removes duplicates" test "variable list delete": set {_list::*} to "x", "y" - delete variable {_list::*} with event + delete variable {_list::*} with custom-event assert {_list::*} is not set with "list delete clears list" test "variable list reset": set {_list::*} to "x", "y" - reset variable {_list::*} with event + reset variable {_list::*} with custom-event assert {_list::*} is not set with "list reset clears list" + +test "inner resolution": + # ephemeral prefix works but for skript warning "Starting a variable with a variable... warning we do this" + set {_inner} to "event.scopified.test::*" + set variable {-a%{_inner}%} with custom-event to 15 + assert {-aevent.scopified.test::*} contains 15 test "variable parse rejects non-variable": parse: - set {_x} to variable 5 with event + set {_x} to variable 5 with custom-event assert last parse logs contains "Expression 1 must be a variable" -test "variable parse rejects multiple change values": - parse: - set variable {_x} with event to 1, 2 - assert last parse logs contains "You can't set a single variable to multiple values" - +#test "variable parse rejects multiple change values": +# parse: +# set variable {_x} with custom-event to 1, 2 +# assert last parse logs contains "You can't set a single variable to multiple values" + From 9b70f476506440b24e8e5ecb4f7f0bc7c5ebc32b Mon Sep 17 00:00:00 2001 From: devdinc Date: Mon, 2 Feb 2026 13:56:01 +0300 Subject: [PATCH 17/38] event specified variables new string expression, scoped variable fixes --- scripts/lang/eventspecifiedvariables.sk | 137 +++++++++++++++++----- scripts/lang/scopedvariable.sk | 150 ++++++++++-------------- tests/eventspecifiedvariables.sk | 61 +++++----- tests/scopedvariable.sk | 4 - 4 files changed, 205 insertions(+), 147 deletions(-) diff --git a/scripts/lang/eventspecifiedvariables.sk b/scripts/lang/eventspecifiedvariables.sk index 52c8889..f2a9f9d 100644 --- a/scripts/lang/eventspecifiedvariables.sk +++ b/scripts/lang/eventspecifiedvariables.sk @@ -2,34 +2,45 @@ import: java.util.Map ch.njol.skript.variables.Variables ch.njol.skript.lang.Variable + ch.njol.skript.lang.VariableString ch.njol.skript.Skript org.bukkit.event.Event + ch.njol.skript.util.StringMode -expression variable %$objects% with custom-event [%-object%]: +expression (1:variable %-string%|2:%$-objects%) with custom-event [%-object%]: parse: - if expr-1 is not an instance of Variable: - Skript.error("Expression 1 must be a variable") - stop + #if expr-2 is not an instance of Variable: + # Skript.error("Expression 1 must be a variable") + # stop - if expr-2 is set: - expr-2 is not an instance of Event - Skript.error("Expression 2 must be an event") - stop + #if expr-3 is set: + # expr-3 is not an instance of Event + # Skript.error("Expression 2 must be an event") + # stop # change values does not exist here and we cant specify "set scope" #if size of change values is greater than 1: # {_isList} is false # Skript.error("You can't set a single variable to multiple values") # stop - - set {_var} to expr-1 - set {_isLocal} to {_var}.isLocal() - set {_isList} to {_var}.isList() continue set: - set {_event} to event if expr-2 is not set else expr-2 - set {_name} to {_var}.getName().toUnformattedString({_event}) + if parse mark is 2: + set {_var} to (raw expression of expr-2).getName() + set {_isLocal} to (raw expression of expr-2).isLocal() + set {_isList} to (raw expression of expr-2).isList() + else: + set {_isLocal} to true if expr-1 starts with "_" else false + set {_name} to expr-1 + if {_isLocal} is true: + set {_name} to {_name}.substring(1) # substring to remove "_" + set {_var} to VariableString.newInstance({_name}, StringMode.VARIABLE_NAME) + set {_isList} to true if expr-1 ends with "::*" else false + + set {_event} to event if expr-3 is not set else expr-3 + set {_name} to {_var}.toUnformattedString({_event}) + if any: {_isList} is true {_name} ends with "::*" # inner resolution, not how skript behaves but I want it this way @@ -45,8 +56,20 @@ expression variable %$objects% with custom-event [%-object%]: Variables.setVariable({_name}, change value, {_event}, {_isLocal}) get: - set {_event} to event if expr-2 is not set else expr-2 - set {_name} to {_var}.getName().toUnformattedString({_event}) + if parse mark is 2: + set {_var} to (raw expression of expr-2).getName() + set {_isLocal} to (raw expression of expr-2).isLocal() + set {_isList} to (raw expression of expr-2).isList() + else: + set {_isLocal} to true if expr-1 starts with "_" else false + set {_name} to expr-1 + if {_isLocal} is true: + set {_name} to {_name}.substring(1) # substring to remove "_" + set {_var} to VariableString.newInstance({_name}, StringMode.VARIABLE_NAME) + set {_isList} to true if expr-1 ends with "::*" else false + + set {_event} to event if expr-3 is not set else expr-3 + set {_name} to {_var}.toUnformattedString({_event}) set {_value} to Variables.getVariable({_name}, {_event}, {_isLocal}) if {_value} is instance of Map: @@ -60,8 +83,20 @@ expression variable %$objects% with custom-event [%-object%]: return {_value} add: - set {_event} to event if expr-2 is not set else expr-2 - set {_name} to {_var}.getName().toUnformattedString({_event}) + if parse mark is 2: + set {_var} to (raw expression of expr-2).getName() + set {_isLocal} to (raw expression of expr-2).isLocal() + set {_isList} to (raw expression of expr-2).isList() + else: + set {_isLocal} to true if expr-1 starts with "_" else false + set {_name} to expr-1 + if {_isLocal} is true: + set {_name} to {_name}.substring(1) # substring to remove "_" + set {_var} to VariableString.newInstance({_name}, StringMode.VARIABLE_NAME) + set {_isList} to true if expr-1 ends with "::*" else false + + set {_event} to event if expr-3 is not set else expr-3 + set {_name} to {_var}.toUnformattedString({_event}) set {_value} to Variables.getVariable({_name}, {_event}, {_isLocal}) if {_value} is instance of Map: @@ -79,8 +114,20 @@ expression variable %$objects% with custom-event [%-object%]: Variables.setVariable({_name}, {_value} + change value, {_event}, {_isLocal}) remove: - set {_event} to event if expr-2 is not set else expr-2 - set {_name} to {_var}.getName().toUnformattedString({_event}) + if parse mark is 2: + set {_var} to (raw expression of expr-2).getName() + set {_isLocal} to (raw expression of expr-2).isLocal() + set {_isList} to (raw expression of expr-2).isList() + else: + set {_isLocal} to true if expr-1 starts with "_" else false + set {_name} to expr-1 + if {_isLocal} is true: + set {_name} to {_name}.substring(1) # substring to remove "_" + set {_var} to VariableString.newInstance({_name}, StringMode.VARIABLE_NAME) + set {_isList} to true if expr-1 ends with "::*" else false + + set {_event} to event if expr-3 is not set else expr-3 + set {_name} to {_var}.toUnformattedString({_event}) set {_value} to Variables.getVariable({_name}, {_event}, {_isLocal}) if {_value} is instance of Map: @@ -95,11 +142,21 @@ expression variable %$objects% with custom-event [%-object%]: # scalar remove = subtract Variables.setVariable({_name}, {_value} - change value, {_event}, {_isLocal}) - - remove all: - set {_event} to event if expr-2 is not set else expr-2 - set {_name} to {_var}.getName().toUnformattedString({_event}) + if parse mark is 2: + set {_var} to (raw expression of expr-2).getName() + set {_isLocal} to (raw expression of expr-2).isLocal() + set {_isList} to (raw expression of expr-2).isList() + else: + set {_isLocal} to true if expr-1 starts with "_" else false + set {_name} to expr-1 + if {_isLocal} is true: + set {_name} to {_name}.substring(1) # substring to remove "_" + set {_var} to VariableString.newInstance({_name}, StringMode.VARIABLE_NAME) + set {_isList} to true if expr-1 ends with "::*" else false + + set {_event} to event if expr-3 is not set else expr-3 + set {_name} to {_var}.toUnformattedString({_event}) set {_value} to Variables.getVariable({_name}, {_event}, {_isLocal}) if {_value} is instance of Map: @@ -111,11 +168,35 @@ expression variable %$objects% with custom-event [%-object%]: stop delete: - set {_event} to event if expr-2 is not set else expr-2 - set {_name} to {_var}.getName().toUnformattedString({_event}) + if parse mark is 2: + set {_var} to (raw expression of expr-2).getName() + set {_isLocal} to (raw expression of expr-2).isLocal() + set {_isList} to (raw expression of expr-2).isList() + else: + set {_isLocal} to true if expr-1 starts with "_" else false + set {_name} to expr-1 + if {_isLocal} is true: + set {_name} to {_name}.substring(1) # substring to remove "_" + set {_var} to VariableString.newInstance({_name}, StringMode.VARIABLE_NAME) + set {_isList} to true if expr-1 ends with "::*" else false + + set {_event} to event if expr-3 is not set else expr-3 + set {_name} to {_var}.toUnformattedString({_event}) Variables.setVariable({_name}, null, {_event}, {_isLocal}) reset: - set {_event} to event if expr-2 is not set else expr-2 - set {_name} to {_var}.getName().toUnformattedString({_event}) + if parse mark is 2: + set {_var} to (raw expression of expr-2).getName() + set {_isLocal} to (raw expression of expr-2).isLocal() + set {_isList} to (raw expression of expr-2).isList() + else: + set {_isLocal} to true if expr-1 starts with "_" else false + set {_name} to expr-1 + if {_isLocal} is true: + set {_name} to {_name}.substring(1) # substring to remove "_" + set {_var} to VariableString.newInstance({_name}, StringMode.VARIABLE_NAME) + set {_isList} to true if expr-1 ends with "::*" else false + + set {_event} to event if expr-3 is not set else expr-3 + set {_name} to {_var}.toUnformattedString({_event}) Variables.setVariable({_name}, null, {_event}, {_isLocal}) diff --git a/scripts/lang/scopedvariable.sk b/scripts/lang/scopedvariable.sk index 31f8de1..6ec78df 100644 --- a/scripts/lang/scopedvariable.sk +++ b/scripts/lang/scopedvariable.sk @@ -1,6 +1,7 @@ import: ch.njol.skript.log.SkriptLogger ch.njol.skript.lang.Variable + ch.njol.skript.Skript effect set default scope for scoped variables in current script to (a:current (x:folder|x:package|y:script)|b:(folder|package) %-string%|c: %-script%): parse: @@ -10,8 +11,10 @@ effect set default scope for scoped variables in current script to (a:current (x trigger: if parse tags contains "a": if parse tags contains "x": - set {_currentscript} to {_origin}.getConfig().getPath().getParent().toString() - set {_currentscript} to {_currentscript}.substring({_currentscript}.indexOf("scripts/") + 8) + set {_scriptsFolder} to Skript.getInstance().getScriptsFolder().toPath().toAbsolutePath() + set {_scriptParent} to {_origin}.getConfig().getPath().getParent().toAbsolutePath() + set {_relative} to {_scriptsFolder}.relativize({_scriptParent}) + set {_currentscript} to {_relative}.toString().replace("\\", "/") set {-scoped.sk::default_scope::%{_realscript}%} to {_currentscript} else if parse tags contains "y": set {-scoped.sk::default_scope::%{_realscript}%} to {_realscript} @@ -20,7 +23,7 @@ effect set default scope for scoped variables in current script to (a:current (x else if parse tags contains "c": set {-scoped.sk::default_scope::%{_realscript}%} to expr-2 -local function scoped_ctx(origin: object, event: object, var: object, script: object, mark: number) :: objects: +local function scoped_ctx(origin: object, event: object, var: object, script: object, mark: number) :: strings: # origin + script set {_realscript} to script ({_origin}.getConfig().getFileName()) @@ -30,28 +33,28 @@ local function scoped_ctx(origin: object, event: object, var: object, script: ob else if {_mark} is 1: set {_scope} to {_script} else if {_mark} is 2: - set {_scope} to {_origin}.getConfig().getPath().getParent().toString() - set {_scope} to {_scope}.substring({_scope}.indexOf("scripts/") + 8) - - if {_var}.isLocal(): - set {_scope} to "%{_origin}.getParent()%::%{_scope}%" + set {_scriptsFolder} to Skript.getInstance().getScriptsFolder().toPath().toAbsolutePath() + set {_scriptParent} to {_origin}.getConfig().getPath().getParent().toAbsolutePath() + set {_relative} to {_scriptsFolder}.relativize({_scriptParent}) + set {_scope} to {_relative}.toString().replace("\\", "/") + + set {_prefix} to "" + set {_suffix} to "" + if {_var}.isLocal() is true: + set {_prefix} to "_" + + if {_var}.isEphemeral() is true: + set {_prefix} to "-" + + if {_var}.isList() is true: + set {_suffix} to "::*" # --- name --- set {_name} to {_var}.getName().toUnformattedString({_event}) replace "::*" with "" in {_name} - - # --- flags --- - set {_ctx::1scope} to {_scope} - set {_ctx::2name} to {_name} - set {_ctx::3list} to {_var}.isList() - if any: - {_var}.isLocal() - {_var}.isEphemeral() - then: - set {_ctx::4temp} to true - else: - set {_ctx::4temp} to false - + + set {_ctx::1prefix} to {_prefix} + set {_ctx::2var} to "%{_scope}%::%{_name}%%{_suffix}%" return {_ctx::*} @@ -60,106 +63,79 @@ expression scoped [variable] %$objects% [([with]in|from|by) (1:%-script%|2:curre expr-1 is an instance of Variable set {_var} to expr-1 - # we use this to do local variables and to capture backing expression's script, could be made cleaner. - - # for locals we need to make it ephemeral, if name starts with _, if we could access previous event we could have made it really local, but this is the only way + # we use this to capture backing expression's script, could be made cleaner. # we use origin.toString set {_origin} to SkriptLogger.getNode() continue get: set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_ctx::4} is true: - if {_ctx::3} is true: - return {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - return {-scoped::%{_ctx::1}%::%{_ctx::2}%} + # todo others + # we do this to remove the warning message + if {_ctx::1} is "_": + return variable "_scoped::%{_ctx::2}%" with custom-event event + else if {_ctx::1} is "-": + return variable "-scoped::%{_ctx::2}%" with custom-event event else: - if {_ctx::3} is true: - return {scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - return {scoped::%{_ctx::1}%::%{_ctx::2}%} + return variable "scoped::%{_ctx::2}%" with custom-event event set: set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_ctx::4} is true: - if {_ctx::3} is true: - set {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} to change values - else: - set {-scoped::%{_ctx::1}%::%{_ctx::2}%} to change value + + if {_ctx::1} is "_": + set variable "_scoped::%{_ctx::2}%" with custom-event event to change values + else if {_ctx::1} is "-": + set variable "-scoped::%{_ctx::2}%" with custom-event event to change values else: - if {_ctx::3} is true: - set {scoped::%{_ctx::1}%::%{_ctx::2}%::*} to change values - else: - set {scoped::%{_ctx::1}%::%{_ctx::2}%} to change value + set variable "scoped::%{_ctx::2}%" with custom-event event to change values add: set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_ctx::4} is true: - if {_ctx::3} is true: - add change values to {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - add change values to {-scoped::%{_ctx::1}%::%{_ctx::2}%} + if {_ctx::1} is "_": + add change values to variable "_scoped::%{_ctx::2}%" with custom-event event + else if {_ctx::1} is "-": + add change values to variable "-scoped::%{_ctx::2}%" with custom-event event else: - if {_ctx::3} is true: - add change values to {scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - add change values to {scoped::%{_ctx::1}%::%{_ctx::2}%} + add change values to variable "scoped::%{_ctx::2}%" with custom-event event remove: set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_ctx::4} is true: - if {_ctx::3} is true: - remove change values from {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - remove change values from {-scoped::%{_ctx::1}%::%{_ctx::2}%} + if {_ctx::1} is "_": + remove change values from variable "_scoped::%{_ctx::2}%" with custom-event event + else if {_ctx::1} is "-": + remove change values from variable "-scoped::%{_ctx::2}%" with custom-event event else: - if {_ctx::3} is true: - remove change values from {scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - remove change values from {scoped::%{_ctx::1}%::%{_ctx::2}%} + remove change values from variable "scoped::%{_ctx::2}%" with custom-event event remove all: set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_ctx::4} is true: - if {_ctx::3} is true: - remove all change values from {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - remove all change values from {-scoped::%{_ctx::1}%::%{_ctx::2}%} + if {_ctx::1} is "_": + remove all change values from variable "_scoped::%{_ctx::2}%" with custom-event event + else if {_ctx::1} is "-": + remove all change values from variable "-scoped::%{_ctx::2}%" with custom-event event else: - if {_ctx::3} is true: - remove all change values from {scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - remove all change values from {scoped::%{_ctx::1}%::%{_ctx::2}%} + remove all change values from variable "scoped::%{_ctx::2}%" with custom-event event delete: set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_ctx::4} is true: - if {_ctx::3} is true: - delete {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - delete {-scoped::%{_ctx::1}%::%{_ctx::2}%} + if {_ctx::1} is "_": + delete variable "_scoped::%{_ctx::2}%" with custom-event event + else if {_ctx::1} is "-": + delete variable "-scoped::%{_ctx::2}%" with custom-event event else: - if {_ctx::3} is true: - delete {scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - delete {scoped::%{_ctx::1}%::%{_ctx::2}%} + delete variable "scoped::%{_ctx::2}%" with custom-event event reset: set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - if {_ctx::4} is true: - if {_ctx::3} is true: - clear {-scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - clear {-scoped::%{_ctx::1}%::%{_ctx::2}%} + if {_ctx::1} is "_": + delete variable "_scoped::%{_ctx::2}%" with custom-event event + else if {_ctx::1} is "-": + delete variable "-scoped::%{_ctx::2}%" with custom-event event else: - if {_ctx::3} is true: - clear {scoped::%{_ctx::1}%::%{_ctx::2}%::*} - else: - clear {scoped::%{_ctx::1}%::%{_ctx::2}%} + delete variable "scoped::%{_ctx::2}%" with custom-event event diff --git a/tests/eventspecifiedvariables.sk b/tests/eventspecifiedvariables.sk index 3a79151..f91f7c7 100644 --- a/tests/eventspecifiedvariables.sk +++ b/tests/eventspecifiedvariables.sk @@ -2,57 +2,57 @@ import: ch.njol.skript.log.SkriptLogger test "variable scalar set/get": - set variable {_x} with custom-event to 5 - set {_res} to variable {_x} with custom-event + set {_x} with custom-event to 5 + set {_res} to {_x} with custom-event assert {_res} is 5 with "scalar get" test "variable scalar set overwrite": - set variable {_x} with custom-event to 1 - set variable {_x} with custom-event to 10 + set {_x} with custom-event to 1 + set {_x} with custom-event to 10 assert {_x} is 10 with "scalar set overwrite" test "variable scalar add unset": - delete variable {_x} with custom-event - add 3 to variable {_x} with custom-event + delete {_x} with custom-event + add 3 to {_x} with custom-event assert {_x} is 3 with "scalar add when unset" test "variable scalar add existing": - set variable {_x} with custom-event to 4 - add 6 to variable {_x} with custom-event + set {_x} with custom-event to 4 + add 6 to {_x} with custom-event assert {_x} is 10 with "scalar add existing" test "variable scalar remove": - set variable {_x} with custom-event to 10 - remove 3 from variable {_x} with custom-event + set {_x} with custom-event to 10 + remove 3 from {_x} with custom-event assert {_x} is 7 with "scalar remove subtract" test "variable scalar delete": - set variable {_x} with custom-event to 5 - delete variable {_x} with custom-event + set {_x} with custom-event to 5 + delete {_x} with custom-event assert {_x} is not set with "scalar delete" test "variable scalar reset": set {_x} to 5 - reset variable {_x} with custom-event + reset {_x} with custom-event assert {_x} is not set with "scalar reset" test "variable list set/get": - set variable {_list::*} with custom-event to "a", "b", "c" - set variable {_res::*} to variable {_list::*} with custom-event + set {_list::*} with custom-event to "a", "b", "c" + set {_res::*} to {_list::*} with custom-event assert {_res::1} is "a" with "list get index 1" assert {_res::2} is "b" with "list get index 2" assert {_res::3} is "c" with "list get index 3" test "variable list set clears previous": set {_list::*} to "x", "y" - set variable {_list::*} with custom-event to "a", "b" + set {_list::*} with custom-event to "a", "b" assert {_list::1} is "a" with "list overwrite clears old" assert {_list::2} is "b" with "list overwrite new value" assert {_list::3} is not set with "list cleared extra entries" test "variable list add": - set variable {_list::*} with custom-event to "a", "b" - add "c", "d" to variable {_list::*} with custom-event + set {_list::*} with custom-event to "a", "b" + add "c", "d" to {_list::*} with custom-event assert {_list::1} is "a" with "list add preserves index 1" assert {_list::2} is "b" with "list add preserves index 2" assert {_list::3} is "c" with "list add index 3" @@ -60,40 +60,45 @@ test "variable list add": test "variable list remove first match": set {_list::*} to "a", "b", "c" - remove "b" from variable {_list::*} with custom-event + remove "b" from {_list::*} with custom-event assert {_list::2} is not set with "list remove deletes matching entry" assert {_list::1} is "a" with "list remove keeps others" test "variable list remove all": set {_list::*} to "a", "b", "a", "c" - remove all "a" from variable {_list::*} with custom-event + remove all "a" from {_list::*} with custom-event assert {_list::1} is not "a" with "list remove all removes first" assert {_list::3} is not "a" with "list remove all removes duplicates" test "variable list delete": set {_list::*} to "x", "y" - delete variable {_list::*} with custom-event + delete {_list::*} with custom-event assert {_list::*} is not set with "list delete clears list" test "variable list reset": set {_list::*} to "x", "y" - reset variable {_list::*} with custom-event + reset {_list::*} with custom-event assert {_list::*} is not set with "list reset clears list" test "inner resolution": # ephemeral prefix works but for skript warning "Starting a variable with a variable... warning we do this" set {_inner} to "event.scopified.test::*" - set variable {-a%{_inner}%} with custom-event to 15 + set {-a%{_inner}%} with custom-event to 15 assert {-aevent.scopified.test::*} contains 15 -test "variable parse rejects non-variable": - parse: - set {_x} to variable 5 with custom-event - assert last parse logs contains "Expression 1 must be a variable" +test "string locals": + set variable "_abc" with custom-event to "abc" + assert variable "_abc" with custom-event is "abc" + assert {_abc} is "abc" + +#test "variable parse rejects non-variable": +# parse: +# set {_x} to variable 5 with custom-event +# assert last parse logs contains "Expression 1 must be a variable" #test "variable parse rejects multiple change values": # parse: -# set variable {_x} with custom-event to 1, 2 +# set {_x} with custom-event to 1, 2 # assert last parse logs contains "You can't set a single variable to multiple values" diff --git a/tests/scopedvariable.sk b/tests/scopedvariable.sk index 3628539..46b2b75 100644 --- a/tests/scopedvariable.sk +++ b/tests/scopedvariable.sk @@ -100,10 +100,6 @@ test "scoped variable - reset does not affect other scopes": assert scoped {-r} within current script is not set assert scoped {-r} within current folder is 1 - -# todo sadly locals do not reset after a scope is over..., to mitigate we do this -after all tests: - delete scoped {_locals::*} test "scoped variable - locals a": set scoped {_locals::a} to 15 From bd350b17dca1bdaceb159a9f0bb39fbe5129ccac Mon Sep 17 00:00:00 2001 From: devdinc Date: Tue, 3 Feb 2026 01:05:16 +0300 Subject: [PATCH 18/38] pdc fix, scoped variable changes --- scripts/lang/scopedvariable.sk | 2 +- scripts/libs/pdc.sk | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/lang/scopedvariable.sk b/scripts/lang/scopedvariable.sk index 6ec78df..9193bf6 100644 --- a/scripts/lang/scopedvariable.sk +++ b/scripts/lang/scopedvariable.sk @@ -70,7 +70,7 @@ expression scoped [variable] %$objects% [([with]in|from|by) (1:%-script%|2:curre get: set {_ctx::*} to scoped_ctx({_origin}, event, {_var}, expr-2, parse mark) - # todo others + # we do this to remove the warning message if {_ctx::1} is "_": return variable "_scoped::%{_ctx::2}%" with custom-event event diff --git a/scripts/libs/pdc.sk b/scripts/libs/pdc.sk index f292cb8..e23c259 100644 --- a/scripts/libs/pdc.sk +++ b/scripts/libs/pdc.sk @@ -90,7 +90,7 @@ expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: stop if validate_pdcexpr({_key}, {_pdc}, {_pdt}) is false if {_pdt} is not set: - set {_typeKey} to namespaced key "%{_key}%..type" + set {_typeKey} to NamespacedKey.fromString("%{_key}%..type", null) if {_pdc}.has({_typeKey}, PersistentDataType.STRING): set {_typeId} to {_pdc}.get({_typeKey}, PersistentDataType.STRING) @@ -132,7 +132,7 @@ expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: stop if {_pdt} is not set: - set {_typeKey} to namespaced key "%{_key}%..type" + set {_typeKey} to NamespacedKey.fromString("%{_key}%..type", null) set {_i} to 1 loop scoped {-primitives::pdt::*}: @@ -170,7 +170,7 @@ expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: stop if validate_pdcexpr({_key}, {_pdc}, {_pdt}) is false - set {_typeKey} to namespaced key "%{_key}%..type" + set {_typeKey} to NamespacedKey.fromString("%{_key}%..type", null) {_pdc}.remove({_typeKey}) {_pdc}.remove({_key}) From 15e14e4906b5205be1d82e6628c478372a17183f Mon Sep 17 00:00:00 2001 From: devdinc Date: Tue, 3 Feb 2026 01:12:24 +0300 Subject: [PATCH 19/38] fix pdc pdt --- scripts/libs/pdc.sk | 6 +++--- scripts/utils/testframework.sk | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/libs/pdc.sk b/scripts/libs/pdc.sk index e23c259..2cd2aca 100644 --- a/scripts/libs/pdc.sk +++ b/scripts/libs/pdc.sk @@ -17,9 +17,9 @@ import: java.lang.Boolean on load: - set scoped {-primitives::pdt::*} to PersistentDataType.BOOLEAN, PersistentDataType.BYTE, PersistentDataType.SHORT, PersistentDataType.INTEGER, PersistentDataType.LONG, PersistentDataType.FLOAT, PersistentDataType.DOUBLE, PersistentDataType.STRING - set scoped {-primitives::str::*} to "bool", "byte", "short", "int", "long", "float", "double", "str" - set scoped {-primitives::int::*} to 8, 4, 5, 6, 7, 2, 3, 1 + set scoped {-primitives::pdt::*} to PersistentDataType.BOOLEAN, PersistentDataType.BYTE, PersistentDataType.SHORT, PersistentDataType.INTEGER, PersistentDataType.LONG, PersistentDataType.FLOAT, PersistentDataType.DOUBLE, PersistentDataType.STRING, PersistentDataType.TAG_CONTAINER + set scoped {-primitives::str::*} to "bool", "byte", "short", "int", "long", "float", "double", "str", "pdc" + set scoped {-primitives::int::*} to 8, 4, 5, 6, 7, 2, 3, 1, 9 local function validate_pdcexpr(key: object, pdc: object, pdt: object) :: boolean: if all: diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 0e4b2da..2dd6540 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -1,6 +1,6 @@ -# todo registration process should use parse of event -# todo tests should be isolated between .sk files, so before/after is triggered only if a test within that script is running -# todo all tests within %script% +# todo registration process should use parse of event, skript reflect has bugs so we are waiting for an update +# todo tests should be isolated between .sk files, so before/after is triggered only if a test within that script is running, need to do parse first +# todo all tests within %script%, remove with test name, need to do parse first using reflection import: From bbb4e4a7e358064aac5cd5af7152dc7b96f73474 Mon Sep 17 00:00:00 2001 From: devdinc Date: Tue, 3 Feb 2026 01:15:01 +0300 Subject: [PATCH 20/38] some pdc tests --- tests/pdc.sk | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/tests/pdc.sk b/tests/pdc.sk index 2d89c53..d3268d1 100644 --- a/tests/pdc.sk +++ b/tests/pdc.sk @@ -65,5 +65,62 @@ test "pdc boolean pdt": test "pdc default object missing returns none": assert scoped {-key} within test-world is not set with "missing object did not return none" - -# todo more tests with unspecified pdt + +test "pdc unspecified pdt boolean": + + set scoped {-key} within test-world to true + assert scoped {-key} within test-world is true with "unspecified boolean failed" + + +test "pdc unspecified pdt byte": + + set scoped {-key} within test-world to 5 + assert scoped {-key} within test-world is 5 with "unspecified byte failed" + + +test "pdc unspecified pdt short": + + set scoped {-key} within test-world to 12 + assert scoped {-key} within test-world is 12 with "unspecified short failed" + + +test "pdc unspecified pdt integer": + + set scoped {-key} within test-world to 123 + assert scoped {-key} within test-world is 123 with "unspecified integer failed" + + +test "pdc unspecified pdt long": + + set scoped {-key} within test-world to 123 + assert scoped {-key} within test-world is 123 with "unspecified long failed" + + +test "pdc unspecified pdt float": + + set scoped {-key} within test-world to 1.5 + assert scoped {-key} within test-world is 1.5 with "unspecified float failed" + + +test "pdc unspecified pdt double": + + set scoped {-key} within test-world to 2.75 + assert scoped {-key} within test-world is 2.75 with "unspecified double failed" + + +test "pdc unspecified pdt string": + + set scoped {-key} within test-world to "hello" + assert scoped {-key} within test-world is "hello" with "unspecified string failed" + + +test "pdc unspecified pdt tag_container": + + set {_inner-key} to namespaced-key "plugin:inner" + set scoped {_inner-key} within test-world for pdt integer to 9 + + set {_container} to test-world's pdc + set scoped {-key} within test-world to {_container} + + set {_out} to scoped {-key} within test-world + assert scoped {_inner-key} within {_out} for pdt integer is 9 with "unspecified tag_container failed" From 408ded4e40a3a864285f07bce0c0af89a2ed6b5f Mon Sep 17 00:00:00 2001 From: devdinc Date: Tue, 3 Feb 2026 01:18:18 +0300 Subject: [PATCH 21/38] fix pdc pdt --- scripts/libs/pdc.sk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/libs/pdc.sk b/scripts/libs/pdc.sk index 2cd2aca..bf1899c 100644 --- a/scripts/libs/pdc.sk +++ b/scripts/libs/pdc.sk @@ -19,7 +19,7 @@ import: on load: set scoped {-primitives::pdt::*} to PersistentDataType.BOOLEAN, PersistentDataType.BYTE, PersistentDataType.SHORT, PersistentDataType.INTEGER, PersistentDataType.LONG, PersistentDataType.FLOAT, PersistentDataType.DOUBLE, PersistentDataType.STRING, PersistentDataType.TAG_CONTAINER set scoped {-primitives::str::*} to "bool", "byte", "short", "int", "long", "float", "double", "str", "pdc" - set scoped {-primitives::int::*} to 8, 4, 5, 6, 7, 2, 3, 1, 9 + set scoped {-primitives::int::*} to 8, 6, 7, 2, 3, 4, 5, 1, 9 local function validate_pdcexpr(key: object, pdc: object, pdt: object) :: boolean: if all: From 3649adafbb1f186baa227fa1ea687544aa34c3d1 Mon Sep 17 00:00:00 2001 From: devdinc Date: Tue, 3 Feb 2026 11:57:28 +0300 Subject: [PATCH 22/38] fix all pdc unspecified pdt --- scripts/libs/pdc.sk | 11 +++++++---- tests/pdc.sk | 14 +++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/scripts/libs/pdc.sk b/scripts/libs/pdc.sk index bf1899c..84f5271 100644 --- a/scripts/libs/pdc.sk +++ b/scripts/libs/pdc.sk @@ -40,16 +40,16 @@ local function coerce_pdt_value(pdt: object, value: object) :: object: return {_none} if {_pdt} is PersistentDataType.INTEGER: - return try new Integer({_value}) + return try new Integer({_value}) if {_value} is an integer else if {_pdt} is PersistentDataType.BYTE: - return try new Byte({_value}) + return try new Byte({_value}) if {_value} is an integer else if {_pdt} is PersistentDataType.SHORT: - return try new Short({_value}) + return try new Short({_value}) if {_value} is an integer else if {_pdt} is PersistentDataType.LONG: - return try new Long({_value}) + return try new Long({_value}) if {_value} is an integer else if {_pdt} is PersistentDataType.FLOAT: return try new Float({_value}) @@ -62,6 +62,9 @@ local function coerce_pdt_value(pdt: object, value: object) :: object: else if {_pdt} is PersistentDataType.BOOLEAN: return {_value} if {_value} is a boolean + + else if {_pdt} is PersistentDataType.TAG_CONTAINER: + return {_value} if {_value} is an instance of PersistentDataContainer else if {_pdt} is PersistentDataType.BYTE_ARRAY: return {_value} diff --git a/tests/pdc.sk b/tests/pdc.sk index d3268d1..6ad60bf 100644 --- a/tests/pdc.sk +++ b/tests/pdc.sk @@ -103,7 +103,7 @@ test "pdc unspecified pdt float": test "pdc unspecified pdt double": - + set scoped {-key} within test-world to 2.75 assert scoped {-key} within test-world is 2.75 with "unspecified double failed" @@ -116,11 +116,11 @@ test "pdc unspecified pdt string": test "pdc unspecified pdt tag_container": - set {_inner-key} to namespaced-key "plugin:inner" - set scoped {_inner-key} within test-world for pdt integer to 9 + set {_inner-pdc} to new pdc from test-world + set {_inner-key} to namespaced-key "plugin:inner_key" + set {_inner-key} within {_inner-pdc} for pdt integer to 9 - set {_container} to test-world's pdc - set scoped {-key} within test-world to {_container} + set scoped {-key} within test-world to {_inner-pdc} - set {_out} to scoped {-key} within test-world - assert scoped {_inner-key} within {_out} for pdt integer is 9 with "unspecified tag_container failed" + set {_out-pdc} to scoped {-key} within test-world + assert {_inner-key} within {_out-pdc} for pdt integer is 9 with "unspecified tag_container failed" From 95caef3befbb247eb19424dcd9b5afd18eb3ba0e Mon Sep 17 00:00:00 2001 From: devdinc Date: Wed, 4 Feb 2026 17:14:44 +0300 Subject: [PATCH 23/38] test: breaking changes; tests are now associated with scripts, event-test, all tests etc return an array, where first value is the name and second value is the script. all tests within %script% expression has been added. tests are registered in parse time now. --- .github/workflows/test-skripts.yml | 23 +++--- scripts/utils/testframework.sk | 112 ++++++++++++++++++----------- 2 files changed, 84 insertions(+), 51 deletions(-) diff --git a/.github/workflows/test-skripts.yml b/.github/workflows/test-skripts.yml index 15fc93c..bec4c21 100644 --- a/.github/workflows/test-skripts.yml +++ b/.github/workflows/test-skripts.yml @@ -54,16 +54,17 @@ jobs: curl -L --fail -o build/libs/skript-reflect/routines-paper.jar \ https://jitpack.io/com/github/devdinc/routines/routines-paper/v2.2.1/routines-paper-v2.2.1.jar - # Disable pdc for now, i want to remove skbee req - # Disable config reload - - name: Prepare scripts - run: | - mkdir -p tests/scripts - rsync -av scripts/ tests/scripts/ - mv tests/scripts/libs/singlelinesection.sk tests/scripts/libs/0_singlelinesection.sk - rm -f tests/scripts/utils/testframework.sk - rm -f tests/scripts/utils/configreloadv2.sk - + + - name: Order scripts using restructure rules + uses: devdinc/restructure-action@v0.1.1 + with: + source-ref: ${{ github.head_ref || github.ref_name }} + restructure-file: .restructure + + # Disable config reload (intentional) + - name: Remove config reload script + run: | + rm -f tests/scripts/utils/configreloadv2.sk - name: Run tests uses: devdinc/skript-test-action@v1.3 @@ -73,7 +74,7 @@ jobs: test_script_directory: tests # Skript version or ref (tag, branch, or commit) - skript_repo_ref: 01d155a # 2.14.0-dev + skript_repo_ref: dev/feature # directory containing addon/plugin jars (relative to repo root) extra_plugins_directory: build/libs diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 2dd6540..2d5c439 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -1,6 +1,3 @@ -# todo registration process should use parse of event, skript reflect has bugs so we are waiting for an update -# todo tests should be isolated between .sk files, so before/after is triggered only if a test within that script is running, need to do parse first -# todo all tests within %script%, remove with test name, need to do parse first using reflection import: @@ -10,6 +7,7 @@ import: ch.njol.skript.lang.Condition ch.njol.skript.test.runner.TestTracker ch.njol.skript.log.SkriptLogger + java.lang.Runnable local effect sendTestFailMessage %string%[, %-string%]: trigger: @@ -19,9 +17,23 @@ local effect sendTestFailMessage %string%[, %-string%]: event "skriptTest": pattern: [devdinc] test %string% [when <(.+)>] event-values: string, boolean + parse: + set {_origin} to SkriptLogger.getNode() + set {_realscript} to script ({_origin}.getConfig().getFileName()) + # we don't have access to event-string or expr-1, so we can't do it in parse time. + # one thing we can do is cause a call to event to register tho. + set {_list::string} to "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" + set {_list::boolean} to false + create section with {_proxy} stored in {_functions::run}: + wait 2 ticks + call custom event "skriptTest" with {_list::*} + set {_proxy} to new proxy instance of Runnable using {_functions::*} + Bukkit.getGlobalRegionScheduler().execute(instance of plugin "Skript", {_proxy}) + continue check: + set {_realscript} to current script if {_realscript} is not set # before skript-reflect 2.6.4, parse section in events within same script doesn't run set {_ok} to true - set {-test.sk::tests::%expr-1%} to "%expr-1%" + set {-test.sk::tests::%{_realscript}%.%expr-1%} to ["%expr-1%", {_realscript}] # registering if "%expr-1%" is event-string: set {_raw} to first element of regex-1 if {_raw} is set: @@ -33,28 +45,29 @@ event "skriptTest": event "beforeSkriptTest": pattern: [devdinc] before each test - event-values: string, boolean + event-values: string, boolean, script check: continue event "afterSkriptTest": pattern: [devdinc] after each test - event-values: string, boolean + event-values: string, boolean, script check: continue event "beforeSkriptTestAll": pattern: [devdinc] before all tests + event-values: script check: continue event "afterSkriptTestAll": pattern: [devdinc] after all tests + event-values: script check: continue -plural expression all tests [with test name %-string%]: - return type: strings +plural expression [all] tests [[with] [test] [name] %-string%] [[with][in] %-script%]: parse: set {_scoped} to false if expr-2 is not set continue @@ -64,63 +77,77 @@ plural expression all tests [with test name %-string%]: loop {-test.sk::tests::*}: if any: expr-1 is not set - loop-value is expr-1 + loop-value[0] is expr-1 then: - add "%loop-value%" to {_r::*} + add loop-value to {_r::*} else: - if {-test.sk::tests::%expr-1%} is set: - add "%expr-1%" to {_r::*} + loop {-test.sk::tests::*}: + loop-value[1] is expr-2 + if any: + expr-1 is not set + loop-value[0] is expr-1 + then: + add loop-value to {_r::*} return {_r::*} -effect (1:|2:auto)run [test][s] %strings%: +effect (1:|2:auto)run %objects%: trigger: set {_tests::*} to expr-1 set {_alltests::*} to all tests - call custom event "beforeSkriptTestAll" loop {_tests::*}: - set {_list::string} to loop-value + set {_list::script} to loop-value[1] + {_scripts::*} does not contain {_list::script} + add {_list::script} to {_scripts::*} + call custom event "beforeSkriptTestAll" with {_list::*} + loop {_tests::*}: + set {_list::string} to loop-value[0] + set {_list::script} to loop-value[1] set {_list::boolean} to true if parse mark is 2 else false call custom event "beforeSkriptTest" with {_list::*} call custom event "skriptTest" with {_list::*} call custom event "afterSkriptTest" with {_list::*} if any: - loop-value contains "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" - {_alltests::*} does not contain loop-value + {_list::string} contains "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" + size of ({_alltests::*} where ["%input%" is "%loop-value%"]) is 0 # todo then: add 1 to {_forgottenTestResults} continue loop add 1 to {_testFails} if size of {-test.sk::errors::%loop-value%::*} is greater than 0 - call custom event "afterSkriptTestAll" + loop {_scripts::*}: + set {_list::script} to loop-value + call custom event "afterSkriptTestAll" with {_list::*} set {_validTestAmount} to size of {_tests::*} - {_forgottenTestResults} if {_validTestAmount} is not 0: send "[Skript] %{_validTestAmount} - {_testFails}%/%{_validTestAmount}% tests passed." to console else if size of {_alltests::*} is greater than 0: # skipping if there is no initial hidden test send "[Skript] No tests found." to console -condition last test result [of [test] %-string%] is a (1:pass|2:fail): +condition last test result [of %-object%] is a (1:pass|2:fail): usable in: custom event "skriptTest" check: if expr-1 is not set: set {_test} to event.getEventValue("string") + set {_script} to event.getEventValue("script") else: - set {_test} to expr-1 - stop if all tests does not contain expr-1 + set {_test} to expr-1[0] + set {_script} to expr-1[1] + stop if size of (all tests where ["%input%" is "%expr-1%"]) is 0 if all: parse mark is 1 - size of {-test.sk::errors::%{_test}%::*} <= 0 + size of {-test.sk::errors::%{_script}%.%{_test}%::*} <= 0 then: continue else: if all: parse mark is 2 - size of {-test.sk::errors::%{_test}%::*} > 0 + size of {-test.sk::errors::%{_script}%.%{_test}%::*} > 0 then: continue before each test: - delete {-test.sk::errors::%event-string%::*} + delete {-test.sk::errors::%event-script%.%event-string%::*} set {-test.sk::testblock} to test-block's type after each test: @@ -129,7 +156,9 @@ after each test: on load: delete {-test.sk::*} wait 1 tick - run test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" + set {_list::string} to "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" + set {_list::boolean} to false + call custom event "skriptTest" with {_list::*} wait 1 tick set {_tests::*} to all tests autorun {_tests::*} @@ -162,6 +191,7 @@ effect: set {_label} to "assert true" if parse tags contains "1" else "assert false" set {_test} to event.getEventValue("string") + set {_script} to event.getEventValue("script") if all: parse tags contains "1" {_ok} is false @@ -181,7 +211,7 @@ effect: if parse tags does not contain "5": sendTestFailMessage "Test ""%{_test}%"" with condition '%{_label}%: %{_raw}%' failed", expr-1 TestTracker.testFailed(expr-1) - add "%expr-1%" to {-test.sk::errors::%{_test}%::*} + add "%expr-1%" to {-test.sk::errors::%{_script}%.%{_test}%::*} if parse tags contains "3": delay effect @@ -190,10 +220,11 @@ effect (3:|4:(no|without) (halt[ing]|fail[(-| )](safe|fast)|abort[ing])) fail te custom event "skriptTest" trigger: set {_test} to event.getEventValue("string") + set {_script} to event.getEventValue("script") if parse tags does not contain "5": sendTestFailMessage "Test ""%{_test}%"" failed", expr-1 TestTracker.testFailed(expr-1) - add "%expr-1%" to {-test.sk::errors::%{_test}%::*} + add "%expr-1%" to {-test.sk::errors::%{_script}%.%{_test}%::*} if parse tags contains "3": delay effect @@ -205,14 +236,13 @@ effect stop auto [test] execution [here]: if {_bool} is true: delay effect -expression [event(-| )]test: - return type: string +expression event(-| )test: # usable in: # custom event "skriptTest" get: - return event.getEventValue("string") + return [event.getEventValue("string"), event.getEventValue("script")] -condition %string% (1:is|2:is not|2:isn't) autorun: +condition event(-| )test (1:is|2:is not|2:isn't) autorun: # usable in: # custom event "skriptTest" check: @@ -257,16 +287,18 @@ plural expression last parse logs: get: return {-test.sk::latestLogs::*} -plural expression test errors[ for [test[s]] %-strings%]: +plural expression test errors[ for %-objects%]: return type: strings get: if expr-1 is not set: set {_test} to event.getEventValue("string") - return {-test.sk::errors::%{_test}%::*} + set {_script} to event.getEventValue("script") + return {-test.sk::errors::%{_script}%.%{_test}%::*} else: loop ...expr-1: - set {_test} to loop-value - add {-test.sk::errors::%{_test}%::*} to {_r::*} + set {_test} to loop-value[0] + set {_script} to loop-value[1] + add {-test.sk::errors::%{_script}%.%{_test}%::*} to {_r::*} return {_r::*} expression [the] test(-| )world: @@ -358,8 +390,8 @@ devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 stop auto execution works": broadcast "ERROR: auto execution not stopped" devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 all tests returns this test": - set {_all::*} to all tests - assert true: {_all::*} contains "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 all tests returns this test" + set {_all::*} to all tests with test name "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 all tests returns this test" + assert true: size of {_all::*} > 0 devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 all tests": set {_all::*} to all tests @@ -373,7 +405,7 @@ devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 errors do not leak between te assert true: size of test errors is 0 devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 event-test returns correct name": - assert true: event-test is event-string + assert true: event-test[0] is event-string devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 test-block is temporary A": set {-test.sk::temptestblocka} to test-block's type @@ -398,5 +430,5 @@ devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 last test result A": fail test with no error message devdinc test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 last test result B": - set {_test} to "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 last test result A" - assert true: last test result of {_test} is a fail + set {_test::*} to all tests with test name "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 last test result A" + assert true: last test result of {_test::1} is a fail From 2b7d209a544d279d304b1aeb510370b028d2aa5f Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:19:42 +0300 Subject: [PATCH 24/38] Update test-skripts.yml --- .github/workflows/test-skripts.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test-skripts.yml b/.github/workflows/test-skripts.yml index bec4c21..b6ef188 100644 --- a/.github/workflows/test-skripts.yml +++ b/.github/workflows/test-skripts.yml @@ -55,16 +55,11 @@ jobs: curl -L --fail -o build/libs/skript-reflect/routines-paper.jar \ https://jitpack.io/com/github/devdinc/routines/routines-paper/v2.2.1/routines-paper-v2.2.1.jar - - name: Order scripts using restructure rules - uses: devdinc/restructure-action@v0.1.1 - with: - source-ref: ${{ github.head_ref || github.ref_name }} - restructure-file: .restructure - - # Disable config reload (intentional) - - name: Remove config reload script - run: | - rm -f tests/scripts/utils/configreloadv2.sk + - name: Prepare scripts + run: | + mkdir -p tests/scripts + rsync -av scripts/ tests/scripts/ + rm -f tests/scripts/utils/configreloadv2.sk - name: Run tests uses: devdinc/skript-test-action@v1.3 From d0664650f40c41a5b61e617c6ba21cbef423e726 Mon Sep 17 00:00:00 2001 From: devdinc Date: Wed, 4 Feb 2026 18:36:36 +0300 Subject: [PATCH 25/38] pdc: breaking changes; namespaced key expression is removed, fixes when used with scoped variables --- scripts/libs/pdc.sk | 21 ++----- tests/pdc.sk | 133 ++++++++++++++++++++------------------------ 2 files changed, 67 insertions(+), 87 deletions(-) diff --git a/scripts/libs/pdc.sk b/scripts/libs/pdc.sk index 84f5271..b101372 100644 --- a/scripts/libs/pdc.sk +++ b/scripts/libs/pdc.sk @@ -72,11 +72,10 @@ local function coerce_pdt_value(pdt: object, value: object) :: object: return {_none} -expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: +expression [devdinc] [namespaced]( |-)key %string% [with]in %object%[ for %-object%]: parse: - set {_key} to expr-1 set {_pdt} to expr-3 - + if any: "%{_pdt}%" contains "[devdinc] (pdt|[persistent data ]type)" "%{_pdt}%" contains "PersistentDataType" @@ -86,7 +85,7 @@ expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: continue get: - set {_key} to expr-1 + set {_key} to NamespacedKey.fromString(expr-1, null) set {_pdc} to expr-2.getPersistentDataContainer() if expr-2 is instance of PersistentDataHolder else expr-2 set {_pdt} to expr-3 @@ -102,7 +101,7 @@ expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: loop scoped {-primitives::str::*}: if {_typeId} is loop-value: set {_pdt} to {_i}th element of scoped {-primitives::pdt::*} - return {_pdc}.get({_key}, {_pdt}) + return try {_pdc}.get({_key}, {_pdt}) # try is used for if the key does not exist add 1 to {_i} else: @@ -117,9 +116,8 @@ expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: else: return {_pdc}.get({_key}, {_pdt}) - set: - set {_key} to expr-1 + set {_key} to NamespacedKey.fromString(expr-1, null) set {_pdc} to expr-2.getPersistentDataContainer() if expr-2 is instance of PersistentDataHolder else expr-2 set {_pdt} to expr-3 @@ -167,7 +165,7 @@ expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: delete: - set {_key} to expr-1 + set {_key} to NamespacedKey.fromString(expr-1, null) set {_pdc} to expr-2.getPersistentDataContainer() if expr-2 is instance of PersistentDataHolder else expr-2 set {_pdt} to expr-3 @@ -178,13 +176,6 @@ expression [devdinc] %namespacedkey% [with]in %object%[ for %-object%]: {_pdc}.remove({_key}) -expression [devdinc] [namespaced]( |-)key %string%: - parse: - continue - get: - return NamespacedKey.fromString(expr-1, null) - - object property p[ersistent ]d[ata ]c[ontainer]: parse: continue diff --git a/tests/pdc.sk b/tests/pdc.sk index 6ad60bf..aad6eb4 100644 --- a/tests/pdc.sk +++ b/tests/pdc.sk @@ -2,125 +2,114 @@ import: java.util.HashMap on load: - set scoped {-key} to namespaced-key "plugin:test_pdt" + set scoped {-key} to "plugin:test_pdt" after each test: - delete scoped {-key} within test-world if scoped {-key} is set - -test "pdc basic set/get with typed pdt": - - set scoped {-key} within test-world for pdt integer to 42 - assert scoped {-key} within test-world for pdt integer is 42 with "typed pdt get failed" - + event-script is current script + delete namespaced-key scoped {-key} within test-world if scoped {-key} is set + +# since we use after each test we have to use devdinc test pattern here -test "pdc overwrite typed value": +devdinc test "pdc basic set/get with typed pdt": - set scoped {-key} within test-world for pdt string to "a" - set scoped {-key} within test-world for pdt string to "b" + set namespaced-key scoped {-key} within test-world for pdt integer to 42 + assert namespaced-key scoped {-key} within test-world for pdt integer is 42 with "typed pdt get failed" - assert scoped {-key} within test-world for pdt string is "b" with "overwrite failed" +devdinc test "pdc overwrite typed value": + set namespaced-key scoped {-key} within test-world for pdt string to "a" + set namespaced-key scoped {-key} within test-world for pdt string to "b" -test "pdc delete typed value": - set scoped {-key} within test-world for pdt long to 100 - delete scoped {-key} within test-world for pdt long + assert namespaced-key scoped {-key} within test-world for pdt string is "b" with "overwrite failed" - assert scoped {-key} within test-world for pdt long is not set with "delete failed" +devdinc test "pdc delete typed value": + set namespaced-key scoped {-key} within test-world for pdt long to 100 + delete namespaced-key scoped {-key} within test-world for pdt long + assert namespaced-key scoped {-key} within test-world for pdt long is not set with "delete failed" -test "pdc object serialization (byte_array implicit)": +devdinc test "pdc object serialization (byte_array implicit)": set {_obj} to new HashMap() {_obj}.put("a", 1) {_obj}.put("b", 2) - set scoped {-key} within test-world to {_obj} + set namespaced-key scoped {-key} within test-world to {_obj} - set {_out} to scoped {-key} within test-world + set {_out} to namespaced-key scoped {-key} within test-world assert {_out}.get("a") is 1 with "object deserialize failed (a)" assert {_out}.get("b") is 2 with "object deserialize failed (b)" +devdinc test "pdc remove object via null": -test "pdc remove object via null": + set namespaced-key scoped {-key} within test-world to "temp" + set namespaced-key scoped {-key} within test-world to null - set scoped {-key} within test-world to "temp" - set scoped {-key} within test-world to null + assert namespaced-key scoped {-key} within test-world is not set with "object remove via null failed" - assert scoped {-key} within test-world is not set with "object remove via null failed" - - -test "pdc holder vs direct pdc equivalence": +devdinc test "pdc holder vs direct pdc equivalence": set {_pdc} to test-world's pdc - set scoped {-key} within test-world for pdt byte to 7 - assert scoped {-key} within {_pdc} for pdt byte is 7 with "direct pdc access failed" - - -test "pdc boolean pdt": + set namespaced-key scoped {-key} within test-world for pdt byte to 7 + assert namespaced-key scoped {-key} within {_pdc} for pdt byte is 7 with "direct pdc access failed" - set scoped {-key} within test-world for pdt boolean to true - assert scoped {-key} within test-world for pdt boolean is true with "boolean pdt failed" +devdinc test "pdc boolean pdt": + set namespaced-key scoped {-key} within test-world for pdt boolean to true + assert namespaced-key scoped {-key} within test-world for pdt boolean is true with "boolean pdt failed" -test "pdc default object missing returns none": +devdinc test "pdc default object missing returns none": - assert scoped {-key} within test-world is not set with "missing object did not return none" + assert namespaced-key scoped {-key} within test-world is not set with "missing object did not return none" -test "pdc unspecified pdt boolean": +devdinc test "pdc unspecified pdt boolean": - set scoped {-key} within test-world to true - assert scoped {-key} within test-world is true with "unspecified boolean failed" + set namespaced-key scoped {-key} within test-world to true + assert namespaced-key scoped {-key} within test-world is true with "unspecified boolean failed" +devdinc test "pdc unspecified pdt byte": -test "pdc unspecified pdt byte": + set namespaced-key scoped {-key} within test-world to 5 + assert namespaced-key scoped {-key} within test-world is 5 with "unspecified byte failed" - set scoped {-key} within test-world to 5 - assert scoped {-key} within test-world is 5 with "unspecified byte failed" +devdinc test "pdc unspecified pdt short": + set namespaced-key scoped {-key} within test-world to 12 + assert namespaced-key scoped {-key} within test-world is 12 with "unspecified short failed" -test "pdc unspecified pdt short": +devdinc test "pdc unspecified pdt integer": - set scoped {-key} within test-world to 12 - assert scoped {-key} within test-world is 12 with "unspecified short failed" + set namespaced-key scoped {-key} within test-world to 123 + assert namespaced-key scoped {-key} within test-world is 123 with "unspecified integer failed" +devdinc test "pdc unspecified pdt long": -test "pdc unspecified pdt integer": + set namespaced-key scoped {-key} within test-world to 123 + assert namespaced-key scoped {-key} within test-world is 123 with "unspecified long failed" - set scoped {-key} within test-world to 123 - assert scoped {-key} within test-world is 123 with "unspecified integer failed" +devdinc test "pdc unspecified pdt float": + set namespaced-key scoped {-key} within test-world to 1.5 + assert namespaced-key scoped {-key} within test-world is 1.5 with "unspecified float failed" -test "pdc unspecified pdt long": - - set scoped {-key} within test-world to 123 - assert scoped {-key} within test-world is 123 with "unspecified long failed" - - -test "pdc unspecified pdt float": - - set scoped {-key} within test-world to 1.5 - assert scoped {-key} within test-world is 1.5 with "unspecified float failed" - - -test "pdc unspecified pdt double": +devdinc test "pdc unspecified pdt double": - set scoped {-key} within test-world to 2.75 - assert scoped {-key} within test-world is 2.75 with "unspecified double failed" - + set namespaced-key scoped {-key} within test-world to 2.75 + assert namespaced-key scoped {-key} within test-world is 2.75 with "unspecified double failed" -test "pdc unspecified pdt string": - set scoped {-key} within test-world to "hello" - assert scoped {-key} within test-world is "hello" with "unspecified string failed" +devdinc test "pdc unspecified pdt string": + set namespaced-key scoped {-key} within test-world to "hello" + assert namespaced-key scoped {-key} within test-world is "hello" with "unspecified string failed" -test "pdc unspecified pdt tag_container": +devdinc test "pdc unspecified pdt tag_container": set {_inner-pdc} to new pdc from test-world - set {_inner-key} to namespaced-key "plugin:inner_key" - set {_inner-key} within {_inner-pdc} for pdt integer to 9 + set {_inner-key} to "plugin:inner_key" + set namespaced-key {_inner-key} within {_inner-pdc} for pdt integer to 9 - set scoped {-key} within test-world to {_inner-pdc} + set namespaced-key scoped {-key} within test-world to {_inner-pdc} - set {_out-pdc} to scoped {-key} within test-world - assert {_inner-key} within {_out-pdc} for pdt integer is 9 with "unspecified tag_container failed" + set {_out-pdc} to namespaced-key scoped {-key} within test-world + assert namespaced-key {_inner-key} within {_out-pdc} for pdt integer is 9 with "unspecified tag_container failed" From ac9709b47195c3fbe70e05df52d030c2ea817204 Mon Sep 17 00:00:00 2001 From: devdinc Date: Wed, 4 Feb 2026 18:48:12 +0300 Subject: [PATCH 26/38] test: fix error counting --- scripts/utils/testframework.sk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 2d5c439..14d121a 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -109,11 +109,11 @@ effect (1:|2:auto)run %objects%: call custom event "afterSkriptTest" with {_list::*} if any: {_list::string} contains "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" - size of ({_alltests::*} where ["%input%" is "%loop-value%"]) is 0 # todo + size of ({_alltests::*} where ["%input%" is "%loop-value%"]) is 0 then: add 1 to {_forgottenTestResults} continue loop - add 1 to {_testFails} if size of {-test.sk::errors::%loop-value%::*} is greater than 0 + add 1 to {_testFails} if size of {-test.sk::errors::%{_list::script}%.%{_list::string}%::*} is greater than 0 loop {_scripts::*}: set {_list::script} to loop-value call custom event "afterSkriptTestAll" with {_list::*} From 7f294fb4da6f2d89b874da067f5eb1d3fe7db8c2 Mon Sep 17 00:00:00 2001 From: devdinc Date: Wed, 4 Feb 2026 18:55:46 +0300 Subject: [PATCH 27/38] test: there is a chanche there is a bug, try to fix, skript native test not running devdinc tests --- scripts/utils/testframework.sk | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 14d121a..85d3821 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -162,6 +162,20 @@ on load: wait 1 tick set {_tests::*} to all tests autorun {_tests::*} + +# force skript's native test to run +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 native": + send "Devdinc tests are running" to console" + event-test is [event.getEventValue("number"), event.getEventValue("number")] + send "Running devdinc tests" to console + delete {-test.sk::*} + wait 1 tick + set {_list::string} to "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" + set {_list::boolean} to false + call custom event "skriptTest" with {_list::*} + wait 1 tick + set {_tests::*} to all tests + autorun {_tests::*} effect: patterns: From 254d97b65cc136260a98f5571bb0bbb37d363570 Mon Sep 17 00:00:00 2001 From: devdinc Date: Wed, 4 Feb 2026 18:59:41 +0300 Subject: [PATCH 28/38] typo --- scripts/utils/testframework.sk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 85d3821..a278c57 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -165,7 +165,7 @@ on load: # force skript's native test to run test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 native": - send "Devdinc tests are running" to console" + send "Devdinc tests are running" to console event-test is [event.getEventValue("number"), event.getEventValue("number")] send "Running devdinc tests" to console delete {-test.sk::*} From e012072348a9db961c47558eae7077521e59b79b Mon Sep 17 00:00:00 2001 From: devdinc Date: Wed, 4 Feb 2026 19:06:25 +0300 Subject: [PATCH 29/38] test: skript native test force run --- scripts/utils/testframework.sk | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index a278c57..64faf8a 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -163,10 +163,12 @@ on load: set {_tests::*} to all tests autorun {_tests::*} +import: + ch.njol.skript.test.runner.SkriptTestEvent + # force skript's native test to run test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 native": - send "Devdinc tests are running" to console - event-test is [event.getEventValue("number"), event.getEventValue("number")] + event is instance of SkriptTestEvent send "Running devdinc tests" to console delete {-test.sk::*} wait 1 tick From d0b01efeee2644118ab98567e88bfe02ca1b9475 Mon Sep 17 00:00:00 2001 From: devdinc Date: Wed, 4 Feb 2026 19:13:15 +0300 Subject: [PATCH 30/38] test: skript native test force run, another try --- scripts/utils/testframework.sk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 64faf8a..5bb0f59 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -178,6 +178,8 @@ test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 native": wait 1 tick set {_tests::*} to all tests autorun {_tests::*} + broadcast {-test.sk::*} + broadcast {_tests::*} effect: patterns: From 98cb948f1b7f6a31d8d698fd05e0eb2546f2db65 Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Wed, 4 Feb 2026 19:55:07 +0300 Subject: [PATCH 31/38] update debug --- scripts/utils/testframework.sk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 5bb0f59..54eb006 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -179,7 +179,7 @@ test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 native": set {_tests::*} to all tests autorun {_tests::*} broadcast {-test.sk::*} - broadcast {_tests::*} + broadcast "%{_tests::*}%" effect: patterns: From 804678888602e0ed93d626f9257c6a1c442de51e Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Wed, 4 Feb 2026 20:56:40 +0300 Subject: [PATCH 32/38] remove wait to see if it works --- scripts/utils/testframework.sk | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 54eb006..225430e 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -171,11 +171,9 @@ test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 native": event is instance of SkriptTestEvent send "Running devdinc tests" to console delete {-test.sk::*} - wait 1 tick set {_list::string} to "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" set {_list::boolean} to false call custom event "skriptTest" with {_list::*} - wait 1 tick set {_tests::*} to all tests autorun {_tests::*} broadcast {-test.sk::*} From 400aa5a8992ec74902a55e453651e4bc137ea6ca Mon Sep 17 00:00:00 2001 From: devdinc Date: Wed, 4 Feb 2026 21:16:28 +0300 Subject: [PATCH 33/38] adjust test framework and test-skripts workflow to make sure native loads all tests using load order --- .github/workflows/test-skripts.yml | 21 ++++++++++++++++++++- scripts/utils/testframework.sk | 16 ---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test-skripts.yml b/.github/workflows/test-skripts.yml index b6ef188..61be5e3 100644 --- a/.github/workflows/test-skripts.yml +++ b/.github/workflows/test-skripts.yml @@ -43,7 +43,7 @@ jobs: run: | mkdir -p build/libs curl -L -o build/libs/skript-reflect.jar \ - https://github.com/SkriptLang/skript-reflect/releases/download/v2.6.2/skript-reflect-2.6.2.jar + https://github.com/SkriptLang/skript-reflect/releases/download/v2.6.3/skript-reflect-2.6.3.jar - name: Download Routines from JitPack run: | @@ -60,6 +60,25 @@ jobs: mkdir -p tests/scripts rsync -av scripts/ tests/scripts/ rm -f tests/scripts/utils/configreloadv2.sk + + - name: Force-load Skript native test suite + run: | + cat > tests/zzzz_force_native_tests.sk <<'EOF' + import: + ch.njol.skript.test.runner.SkriptTestEvent + + test "run devdinc tests": + event is instance of SkriptTestEvent + send "Running devdinc tests" to console + + set {_list::string} to "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" + set {_list::boolean} to false + + call custom event "skriptTest" with {_list::*} + + set {_tests::*} to all tests + autorun {_tests::*} + EOF - name: Run tests uses: devdinc/skript-test-action@v1.3 diff --git a/scripts/utils/testframework.sk b/scripts/utils/testframework.sk index 225430e..14d121a 100644 --- a/scripts/utils/testframework.sk +++ b/scripts/utils/testframework.sk @@ -162,22 +162,6 @@ on load: wait 1 tick set {_tests::*} to all tests autorun {_tests::*} - -import: - ch.njol.skript.test.runner.SkriptTestEvent - -# force skript's native test to run -test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 native": - event is instance of SkriptTestEvent - send "Running devdinc tests" to console - delete {-test.sk::*} - set {_list::string} to "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" - set {_list::boolean} to false - call custom event "skriptTest" with {_list::*} - set {_tests::*} to all tests - autorun {_tests::*} - broadcast {-test.sk::*} - broadcast "%{_tests::*}%" effect: patterns: From f85d90309f31cb9db966a2747260c2aa56ad2331 Mon Sep 17 00:00:00 2001 From: devdinc Date: Thu, 5 Feb 2026 16:31:10 +0300 Subject: [PATCH 34/38] multiline lambda --- README.md | 6 -- scripts/lang/parseexpressions.sk | 1 + scripts/libs/functionsv2.sk | 172 ++++++++++++++++++++++++------- tests/functionsv2.sk | 29 ++++++ tests/singlinesection.sk | 4 + 5 files changed, 166 insertions(+), 46 deletions(-) create mode 100644 scripts/lang/parseexpressions.sk diff --git a/README.md b/README.md index 70e1bf8..1a025f2 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,3 @@ If you encounter errors such as `Can't understand this expression`, the issue is Skript loads files alphabetically, with folders being prioritized. To control load order, prefix the Skript file with a folder or a character such as `0_` or `!` (e.g., `!testframework.sk`) - -## TODO - -Currents plans include: -- multiline lambda expression -- Auto casting for primitives, pdc.sk(new Byte,Integer etc) diff --git a/scripts/lang/parseexpressions.sk b/scripts/lang/parseexpressions.sk new file mode 100644 index 0000000..bb41cdf --- /dev/null +++ b/scripts/lang/parseexpressions.sk @@ -0,0 +1 @@ +# todo parse node, parse script diff --git a/scripts/libs/functionsv2.sk b/scripts/libs/functionsv2.sk index 3135cdd..5551d1e 100644 --- a/scripts/libs/functionsv2.sk +++ b/scripts/libs/functionsv2.sk @@ -8,12 +8,11 @@ import: ch.njol.skript.lang.ExpressionList ch.njol.skript.lang.Variable ch.njol.skript.Skript - ch.njol.skript.lang.EffectSection - ch.njol.skript.lang.function.EffFunctionCall - ch.njol.skript.lang.SkriptParser + ch.njol.skript.lang.Effect ch.njol.skript.log.SkriptLogger - ch.njol.skript.lang.EffectSectionEffect - org.skriptlang.skript.registration.SyntaxRegistry + ch.njol.skript.config.SectionNode + ch.njol.skript.config.Node + java.util.ArrayList local function FextractArgs(vars: object) :: objects: if {_vars} is instance of ExpressionList: @@ -29,71 +28,51 @@ local function isEffect(input:string) :: boolean: return false return true -local function getFunctionReference(call: object) :: object: - set {_class} to {_call}.getClass() - set {_field} to {_class}.getDeclaredField("function") - {_field}.setAccessible(true) - return {_field}.get({_call}) - # equals effect.parse local function parseEffectSilent(input: string) :: object: - set {_log} to SkriptLogger.startParseLogHandler() - set {_func} to EffFunctionCall.parse({_input}) - if {_func} is set: - set {_ref} to getFunctionReference({_func}) - if {_ref}.getReturnType() is set: - {_log}.close() - return {_none} - {_log}.close() - return {_func} - set {_section} to EffectSection.parse({_input}, null, null, null) - if {_section} is set: - {_log}.close() - return new EffectSectionEffect({_section}) - set {_registry} to Skript.instance().syntaxRegistry() - set {_iterator} to {_registry}.syntaxes(SyntaxRegistry.EFFECT).iterator() - set {_effect} to SkriptParser.parse({_input}, {_iterator}, null) + set {_log} to SkriptLogger.startRetainingLog() + set {_effect} to Effect.parse({_input}, null) {_log}.clear() {_log}.close() return {_effect} - -expression build lambda args %objects% body %string% noreturn %boolean% script %script%: + +expression build lambda args %objects% body %strings% noreturn %boolean% script %script%: get: set {_functionsv2.sk::ctx::args::*} to expr-1 - set {_functionsv2.sk::ctx::body} to expr-2 + set {_functionsv2.sk::ctx::body::*} to expr-2 set {_functionsv2.sk::ctx::noreturn} to expr-3 set {_functionsv2.sk::ctx::script} to expr-4 set {_functionsv2.sk::ctx::arity} to size of {_functionsv2.sk::ctx::args::*} if {_functionsv2.sk::ctx::noreturn} is true: if {_functionsv2.sk::ctx::arity} is 0: - set {_functionsv2.sk::ctx::functions::run} to line section {_functionsv2.sk::ctx::body} with args {_this} + set {_functionsv2.sk::ctx::functions::run} to line section {_functionsv2.sk::ctx::body::*} with args {_this} set {_functionsv2.sk::ctx::proxy} to new proxy instance of Runnable using {_functionsv2.sk::ctx::functions::*} else if {_functionsv2.sk::ctx::arity} is 1: set {_functionsv2.sk::ctx::proxy} to raw expression of {_this} - set {_functionsv2.sk::ctx::functions::accept} to line section {_functionsv2.sk::ctx::body} with args "%{_functionsv2.sk::ctx::proxy}%", "%{_functionsv2.sk::ctx::args::1}%" in {_functionsv2.sk::ctx::script} + set {_functionsv2.sk::ctx::functions::accept} to line section {_functionsv2.sk::ctx::body::*} with args "%{_functionsv2.sk::ctx::proxy}%", "%{_functionsv2.sk::ctx::args::1}%" in {_functionsv2.sk::ctx::script} set {_functionsv2.sk::ctx::proxy} to new proxy instance of Consumer using {_functionsv2.sk::ctx::functions::*} else: set {_functionsv2.sk::ctx::proxy} to raw expression of {_this} - set {_functionsv2.sk::ctx::functions::accept} to line section {_functionsv2.sk::ctx::body} with args "%{_functionsv2.sk::ctx::proxy}%", "%{_functionsv2.sk::ctx::args::1}%", "%{_functionsv2.sk::ctx::args::2}%" in {_functionsv2.sk::ctx::script} + set {_functionsv2.sk::ctx::functions::accept} to line section {_functionsv2.sk::ctx::body::*} with args "%{_functionsv2.sk::ctx::proxy}%", "%{_functionsv2.sk::ctx::args::1}%", "%{_functionsv2.sk::ctx::args::2}%" in {_functionsv2.sk::ctx::script} set {_functionsv2.sk::ctx::proxy} to new proxy instance of BiConsumer using {_functionsv2.sk::ctx::functions::*} else: - + set {_functionsv2.sk::ctx::realbody::*} to "return %{_functionsv2.sk::ctx::body::*}%" if size of {_functionsv2.sk::ctx::body::*} is 1 else {_functionsv2.sk::ctx::body::*} if {_functionsv2.sk::ctx::arity} is 0: - set {_functionsv2.sk::ctx::functions::get} to line section "return %{_functionsv2.sk::ctx::body}%" with args {_this} in {_functionsv2.sk::ctx::script} + set {_functionsv2.sk::ctx::functions::get} to line section {_functionsv2.sk::ctx::realbody::*} with args {_this} in {_functionsv2.sk::ctx::script} set {_functionsv2.sk::ctx::proxy} to new proxy instance of Supplier using {_functionsv2.sk::ctx::functions::*} else if {_functionsv2.sk::ctx::arity} is 1: set {_functionsv2.sk::ctx::proxy} to raw expression of {_this} - set {_functionsv2.sk::ctx::functions::apply} to line section "return %{_functionsv2.sk::ctx::body}%" with args "%{_functionsv2.sk::ctx::proxy}%", "%{_functionsv2.sk::ctx::args::1}%" in {_functionsv2.sk::ctx::script} + set {_functionsv2.sk::ctx::functions::apply} to line section {_functionsv2.sk::ctx::realbody::*} with args "%{_functionsv2.sk::ctx::proxy}%", "%{_functionsv2.sk::ctx::args::1}%" in {_functionsv2.sk::ctx::script} set {_functionsv2.sk::ctx::proxy} to new proxy instance of Function using {_functionsv2.sk::ctx::functions::*} else: set {_functionsv2.sk::ctx::proxy} to raw expression of {_this} - set {_functionsv2.sk::ctx::functions::apply} to line section "return %{_functionsv2.sk::ctx::body}%" with args "%{_functionsv2.sk::ctx::proxy}%", "%{_functionsv2.sk::ctx::args::1}%", "%{_functionsv2.sk::ctx::args::2}%" in {_functionsv2.sk::ctx::script} + set {_functionsv2.sk::ctx::functions::apply} to line section {_functionsv2.sk::ctx::realbody::*} with args "%{_functionsv2.sk::ctx::proxy}%", "%{_functionsv2.sk::ctx::args::1}%", "%{_functionsv2.sk::ctx::args::2}%" in {_functionsv2.sk::ctx::script} set {_functionsv2.sk::ctx::proxy} to new proxy instance of BiFunction using {_functionsv2.sk::ctx::functions::*} return {_functionsv2.sk::ctx::proxy} expression: patterns: - lambda[ %-objects%]\:(1: |2:- |3:+ )<(.+)> + [new] lambda[ %-objects%]\:(1: |2:- |3:+ )<(.+)> parse: set {_functionsv2.sk::ctx::vars} to expr-1 set {_functionsv2.sk::ctx::expr} to first element of regex-1 @@ -111,8 +90,8 @@ expression: expression: patterns: - (1:supplier|1:getter|2:runnable|2:runner)[ %-objects%]\: <(.+)> - (3:consumer|3:accepter|4:function|4:applier|5:biconsumer|5:biaccepter|6:bifunction|6:biapplier) %-objects%\: <(.+)> + [new] (1:supplier|1:getter|2:runnable|2:runner)[ %-objects%]\: <(.+)> + [new] (3:consumer|3:accepter|4:function|4:applier|5:biconsumer|5:biaccepter|6:bifunction|6:biapplier) %-objects%\: <(.+)> parse: set {_functionsv2.sk::ctx::vars} to expr-1 set {_functionsv2.sk::ctx::expr} to first element of regex-1 @@ -150,18 +129,131 @@ expression: get: return build lambda args {_functionsv2.sk::ctx::args::*} body {_functionsv2.sk::ctx::expr} noreturn {_functionsv2.sk::ctx::noreturn} script {_functionsv2.sk::ctx::script} +local function walk(n: object, list: object) :: objects: + if {_list} is not set: + set {_list} to new ArrayList() + loop ...{_n}: + {_list}.add(loop-value) + if loop-value is instance of SectionNode: + walk(loop-value, {_list}) + return ...{_list} + +condition set %object% to [new] lambda[ %-objects%](1:|2:-|3:+): + parse: + set {_functionsv2.sk::ctx::origin} to SkriptLogger.getNode() + set {_functionsv2.sk::ctx::script} to script ({_functionsv2.sk::ctx::origin}.getConfig().getFileName()) + if {_functionsv2.sk::ctx::script} is not set: # likely inner lambda + set {_functionsv2.sk::ctx::script} to current script + set {_functionsv2.sk::ctx::vars} to expr-2 + set {_functionsv2.sk::ctx::originindent} to {_functionsv2.sk::ctx::origin}.[Node]getIndentation() + set {_functionsv2.sk::ctx::nodes::*} to walk({_functionsv2.sk::ctx::origin}, {_none}) + loop {_functionsv2.sk::ctx::nodes::*}: + set {_functionsv2.sk::ctx::indent} to loop-value.[Node]getIndentation().substring(length of {_functionsv2.sk::ctx::originindent} + 4) if {_functionsv2.sk::ctx::originindent} starts with " " else loop-value.[Node]getIndentation().substring(length of {_functionsv2.sk::ctx::originindent} + 1) + set {_functionsv2.sk::ctx::key} to "%{_functionsv2.sk::ctx::indent}%%loop-value.getKey()%" + set {_functionsv2.sk::ctx::key} to "%{_functionsv2.sk::ctx::key}%:" if loop-value is instance of SectionNode + add {_functionsv2.sk::ctx::key} to {_functionsv2.sk::ctx::expr::*} + {_functionsv2.sk::ctx::key}.trim() starts with "return" + set {_functionsv2.sk::ctx::noreturn} to false if parse mark is 1 + + set {_functionsv2.sk::ctx::noreturn} to true if parse mark is 2 + set {_functionsv2.sk::ctx::noreturn} to false if parse mark is 3 + set {_functionsv2.sk::ctx::args::*} to FextractArgs({_functionsv2.sk::ctx::vars}) + continue + check: + set raw expression of expr-1 to build lambda args {_functionsv2.sk::ctx::args::*} body {_functionsv2.sk::ctx::expr::*} noreturn {_functionsv2.sk::ctx::noreturn} script {_functionsv2.sk::ctx::script} + +condition set %object% to [new] (1:supplier|1:getter|2:runnable|2:runner|3:consumer|3:accepter|4:function|4:applier|5:biconsumer|5:biaccepter|6:bifunction|6:biapplier)[ %-objects%]: + parse: + set {_functionsv2.sk::ctx::origin} to SkriptLogger.getNode() + set {_functionsv2.sk::ctx::script} to script ({_functionsv2.sk::ctx::origin}.getConfig().getFileName()) + if {_functionsv2.sk::ctx::script} is not set: + set {_functionsv2.sk::ctx::script} to current script + + set {_functionsv2.sk::ctx::vars} to expr-2 + set {_functionsv2.sk::ctx::originindent} to {_functionsv2.sk::ctx::origin}.[Node]getIndentation() + set {_functionsv2.sk::ctx::nodes::*} to walk({_functionsv2.sk::ctx::origin}, {_none}) + + loop {_functionsv2.sk::ctx::nodes::*}: + set {_functionsv2.sk::ctx::indent} to loop-value.[Node]getIndentation().substring(length of {_functionsv2.sk::ctx::originindent} + 4) if {_functionsv2.sk::ctx::originindent} starts with " " else loop-value.[Node]getIndentation().substring(length of {_functionsv2.sk::ctx::originindent} + 1) + set {_functionsv2.sk::ctx::key} to "%{_functionsv2.sk::ctx::indent}%%loop-value.getKey()%" + set {_functionsv2.sk::ctx::key} to "%{_functionsv2.sk::ctx::key}%:" if loop-value is instance of SectionNode + add {_functionsv2.sk::ctx::key} to {_functionsv2.sk::ctx::expr::*} + + set {_functionsv2.sk::ctx::args::*} to FextractArgs({_functionsv2.sk::ctx::vars}) + + set {_functionsv2.sk::ctx::noreturn} to true + if any: + parse mark is 1 + parse mark is 4 + parse mark is 6 + then: + set {_functionsv2.sk::ctx::noreturn} to false + set {_functionsv2.sk::ctx::args::*} to FextractArgs({_functionsv2.sk::ctx::vars}) + if any: + parse mark is 1 + parse mark is 2 + then: + if size of {_functionsv2.sk::ctx::args::*} > 0: + stop + if any: + parse mark is 3 + parse mark is 4 + then: + if size of {_functionsv2.sk::ctx::args::*} is 0: + stop + if any: + parse mark is 5 + parse mark is 6 + then: + if size of {_functionsv2.sk::ctx::args::*} is not 2: + stop + + continue + + check: + set raw expression of expr-1 to build lambda args {_functionsv2.sk::ctx::args::*} body {_functionsv2.sk::ctx::expr::*} noreturn {_functionsv2.sk::ctx::noreturn} script {_functionsv2.sk::ctx::script} + expression run[ lambda] %object%[ with %-objects%]: get: set {_functionsv2.sk::ctx::args::*} to expr-2 expr-1 is instance of Runnable: expr-1.run() + return {_none} else if expr-1 is instance of Consumer: expr-1.accept(expr-2) + return {_none} else if expr-1 is instance of BiConsumer: expr-1.accept({_functionsv2.sk::ctx::args::1}, {_functionsv2.sk::ctx::args::2}) + return {_none} else if expr-1 is instance of Function: return expr-1.apply(expr-2) else if expr-1 is instance of BiFunction: return expr-1.apply({_functionsv2.sk::ctx::args::1}, {_functionsv2.sk::ctx::args::2}) else if expr-1 is instance of Supplier: return expr-1.get() + +effect run[ lambda] %object%[ with %-objects%]: + trigger: + set {_args::*} to expr-2 + if expr-1 is instance of Runnable: + expr-1.run() + else if expr-1 is instance of Consumer: + expr-1.accept({_args::*}) + else if expr-1 is instance of BiConsumer: + expr-1.accept({_args::1}, {_args::2}) + else if expr-1 is instance of Function: + expr-1.apply({_args::*}) + else if expr-1 is instance of BiFunction: + expr-1.apply({_args::1}, {_args::2}) + else if expr-1 is instance of Supplier: + expr-1.get() + +effect return %object%: + parse: + set {_functionsv2.sk::ctx::node} to SkriptLogger.getNode() + if "%{_functionsv2.sk::ctx::node}%" contains "lambda." or "supplier." or "applier." or "getter." or "function" or "bifunction" or "biapplier": + continue + trigger: + broadcast "shouldn't ever run" + + diff --git a/tests/functionsv2.sk b/tests/functionsv2.sk index d65f537..bfd4467 100644 --- a/tests/functionsv2.sk +++ b/tests/functionsv2.sk @@ -108,3 +108,32 @@ test "java call": set {_lam} to lambda:+ new URL("https://www.dummy2.com") set {_val} to {_lam}.get() assert {_val}.getClass().getSimpleName().equalsIgnoreCase("URL") is true + +test "run multiline lambda with return": + set {_sup} to lambda: + set {_x} to "yes" + return {_x} + assert (run lambda {_sup}) is "yes" with "multiline lambda return failed" + +test "multiline lambda multiple runs": + clear {-functions::test::*} + set {-functions::test::varl} to 0 + set {_sup} to lambda: + add 1 to {-functions::test::varl} + return {-functions::test::varl} + assert (run lambda {_sup}) is 1 + assert (run lambda {_sup}) is 2 + +test "multiline lambda with if": + set {_sup} to lambda: + if {_none} is not set: + return "yes" + return "no" + assert (run lambda {_sup}) is "yes" with "if control flow failed" + +test "multiline supplier": + set {_sup} to new supplier: + set {_a} to 1 + return {_a} + assert (run lambda {_sup}) is 1 + diff --git a/tests/singlinesection.sk b/tests/singlinesection.sk index 045d6f7..b9201a9 100644 --- a/tests/singlinesection.sk +++ b/tests/singlinesection.sk @@ -31,3 +31,7 @@ test "new single line section expression": set {_section} to single line section "set {_a} to 2", "set {_c} to 3", "return {_a} + {_b} + {_c}" run section {_section} and store result in {_n} assert {_n} is 5 with error "single line section bug 7" + + set {_section} to single line section "if {_none} is not set:", " return {_x} + {_z}" with args "{_x}", "{_z}" + run section {_section} with 15, 10 and store result in {_n} + assert {_n} is 25 with "single line section bug 8" From d5fbd03bb71debe24c07f29608c38b437cb8a5fb Mon Sep 17 00:00:00 2001 From: devdinc Date: Thu, 5 Feb 2026 16:32:08 +0300 Subject: [PATCH 35/38] remove empty file --- scripts/lang/parseexpressions.sk | 1 - 1 file changed, 1 deletion(-) delete mode 100644 scripts/lang/parseexpressions.sk diff --git a/scripts/lang/parseexpressions.sk b/scripts/lang/parseexpressions.sk deleted file mode 100644 index bb41cdf..0000000 --- a/scripts/lang/parseexpressions.sk +++ /dev/null @@ -1 +0,0 @@ -# todo parse node, parse script From e344590d817240f4ec3ffc9c32662c62e17214f7 Mon Sep 17 00:00:00 2001 From: devdinc Date: Thu, 5 Feb 2026 16:35:24 +0300 Subject: [PATCH 36/38] restructure tests --- .../eventspecifiedvariables.test.sk} | 0 tests/{scopedvariable.sk => lang/scopedvariable.test.sk} | 0 tests/{functionsv2.sk => libs/functionsv2.test.sk} | 0 .../{singlinesection.sk => libs/parser/singlelinesection.test.sk} | 0 tests/{pdc.sk => libs/pdc.test.sk} | 0 tests/{routines.sk => libs/routines.test.sk} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/{eventspecifiedvariables.sk => lang/eventspecifiedvariables.test.sk} (100%) rename tests/{scopedvariable.sk => lang/scopedvariable.test.sk} (100%) rename tests/{functionsv2.sk => libs/functionsv2.test.sk} (100%) rename tests/{singlinesection.sk => libs/parser/singlelinesection.test.sk} (100%) rename tests/{pdc.sk => libs/pdc.test.sk} (100%) rename tests/{routines.sk => libs/routines.test.sk} (100%) diff --git a/tests/eventspecifiedvariables.sk b/tests/lang/eventspecifiedvariables.test.sk similarity index 100% rename from tests/eventspecifiedvariables.sk rename to tests/lang/eventspecifiedvariables.test.sk diff --git a/tests/scopedvariable.sk b/tests/lang/scopedvariable.test.sk similarity index 100% rename from tests/scopedvariable.sk rename to tests/lang/scopedvariable.test.sk diff --git a/tests/functionsv2.sk b/tests/libs/functionsv2.test.sk similarity index 100% rename from tests/functionsv2.sk rename to tests/libs/functionsv2.test.sk diff --git a/tests/singlinesection.sk b/tests/libs/parser/singlelinesection.test.sk similarity index 100% rename from tests/singlinesection.sk rename to tests/libs/parser/singlelinesection.test.sk diff --git a/tests/pdc.sk b/tests/libs/pdc.test.sk similarity index 100% rename from tests/pdc.sk rename to tests/libs/pdc.test.sk diff --git a/tests/routines.sk b/tests/libs/routines.test.sk similarity index 100% rename from tests/routines.sk rename to tests/libs/routines.test.sk From 9525e94d2f506e1738873f76c878942e15836f26 Mon Sep 17 00:00:00 2001 From: devdinc Date: Thu, 5 Feb 2026 19:39:33 +0300 Subject: [PATCH 37/38] update documentation --- docs/functions.md | 97 +++++-- docs/pdc.md | 111 ++++---- docs/scopedvariables.md | 249 +++++++++++++++++ docs/testframework.md | 581 ++++++++++++++++++++-------------------- 4 files changed, 684 insertions(+), 354 deletions(-) create mode 100644 docs/scopedvariables.md diff --git a/docs/functions.md b/docs/functions.md index 073b75f..d2991e0 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -31,16 +31,17 @@ All generated lambdas are real Java proxy instances. --- -## Core Lambda Syntax +## Core Lambda Syntax (Single-Line) ### 1. Returning Lambda ```skript lambda []: -``` +```` * Returns a value * Automatically mapped to a Java functional interface +* Return vs effect behavior is auto-detected --- @@ -66,6 +67,67 @@ lambda []:+ --- +## Multiline Lambdas (Section-Based) + +Lambdas may also be written as **multiline sections**. +These allow more complex logic while still compiling into Java functional interfaces. + +Multiline lambdas are created using **typed aliases** (`new runnable:`, `new function {_x}:`, etc.). + +--- + +### Multiline Non-Returning Lambda + +Maps to: + +* `Runnable` +* `Consumer` +* `BiConsumer` + +depending on arity. + +```skript +set {_task} to new runnable: + broadcast "working" + broadcast "still working" +``` + +Rules: + +* Produces **no return value** +* All lines must be **effects** +* `return` is not allowed + +--- + +### Multiline Returning Lambda + +Multiline returning lambdas **must use the `return` effect**. +The lambda does **not** infer a return value from the last line. + +```skript +set {_double} to new function {_x}: + broadcast "doubling %{_x}%" + return {_x} * 2 +``` + +Rules: + +* `return ` is **mandatory** +* The final line does **not** need to be an expression +* Effects may appear anywhere before `return` +* Multiple control-flow paths may use `return` + +Maps to: + +* `Supplier` +* `Function` +* `BiFunction` + +based on arity. + +--- + ## Java Interface Mapping (Arity-Based) The target Java interface is selected based on **parameter count** and **return behavior**. @@ -93,7 +155,9 @@ The target Java interface is selected based on **parameter count** and **return > Lambdas with more than **2 parameters are not supported**. -### Workaround for Higher Arity +--- + +## Workaround for Higher Arity Pass a single composite object instead: @@ -196,7 +260,8 @@ biaccepter {_a}, {_b}: ```skript set {_f} to function {_x}: {_x} * 2 -set {_r} to runnable: broadcast "hello" +set {_r} to runnable: + broadcast "hello" ``` --- @@ -239,38 +304,38 @@ set {_adder} to lambda {_m}:- {_m}.values().stream().forEach({_inlineforeach}) ## Limitations -* Lambdas are **strictly single-line** -* Returning lambdas (`:` / `:+`) **cannot contain effects** -* Non-returning lambdas (`:-`) **cannot contain expressions** -* Non-returning lambdas **cannot return values** +* Lambdas may be **single-line or multiline** +* Single-line lambdas infer return vs effect behavior +* Multiline returning lambdas **require `return`** +* Multiline non-returning lambdas **must not return** +* Lambdas are limited to **0–2 parameters** * Variable lists as parameters are **not supported** * Arrays are unreliable inside lambdas: * Indexing (`[n]`) does not work * Use `spread(...)` before passing arrays -* Imported classes or complex expressions **may fail inline** - - * Mitigations: +* Imported classes or complex expressions may fail inline - * Explicitly use `:+` or `:-` - * Wrap logic in a normal function and call it + * Prefer multiline lambdas + * Or wrap logic in a normal Skript function --- ## Notes -* Return vs effect behavior is **auto-detected** unless overridden with `:-` or `:+` +* Single-line lambdas auto-detect return vs effect +* Multiline lambdas require explicit intent * Lambdas are real Java proxy objects and fully compatible with: * Java streams * Java APIs * skript-reflect usage +* Experimentally, local values are passed into lambda sections. Use with care. --- -* Experimentally local values are passed into the section. You can use local values, but be careful. - ## Planned * Additional utility expressions (e.g., `for each` helpers) * These will be introduced in **separate files** + diff --git a/docs/pdc.md b/docs/pdc.md index 2adee52..689d457 100644 --- a/docs/pdc.md +++ b/docs/pdc.md @@ -1,8 +1,5 @@ - # Persistent Data Container (PDC) Utility Expressions for Skript -> **WARNING:** Read migration notes carefully before upgrading. - --- ## Overview @@ -11,94 +8,98 @@ This document describes a set of Skript expressions and helpers for interacting It supports: -- Native `PersistentDataType` (PDT) values +- Native `PersistentDataType` (PDT) values - Arbitrary object storage via Java serialization into `BYTE_ARRAY` --- -## Migration Notice (Base64 Removed) +## Requirements -Previous versions serialized arbitrary objects using Base64 encoding and stored them as `STRING` values. +- Paper server +- Skript +- skript-reflect -**This implementation no longer uses Base64.** +--- -Arbitrary objects are now: +## Key Features -- Serialized directly using `BukkitObjectOutputStream` -- Stored natively as `PersistentDataType.BYTE_ARRAY` +### 1. Unified `key … in pdc` Expression -This is a **storage-format change only**. -Arbitrary object support remains fully supported. +- Read, write, and delete PDC entries using a single expression +- Automatically resolves: + - `PersistentDataHolder` → `PersistentDataContainer` -### Required User Action +--- -- Existing Base64-encoded PDC values are **not compatible** -- Stored data must be migrated or deleted +### 2. Native and Arbitrary Object Storage -Scripts do **not** require syntax changes, but values written by older versions will not deserialize correctly. +- When a `PersistentDataType` is provided: + → Values are stored natively using that PDT +- When omitted: + → Values are serialized and stored as `PersistentDataType.BYTE_ARRAY` --- -## Requirements +### 3. Built-in NamespacedKey Handling -- Paper server -- Skript -- skript-reflect +There is **no standalone NamespacedKey expression**. ---- +Instead, namespaced keys are handled directly by the main expression using string input. -## Key Features +Key strings must be in the form: -### 1. Unified `key in pdc` Expression +``` -- Read, write, and delete PDC entries using a single expression -- Automatically resolves: - - `PersistentDataHolder` → `PersistentDataContainer` +namespace:key -### 2. Native and Arbitrary Object Storage +```` -- When a `PersistentDataType` is provided: - → Values are stored natively -- When omitted: - → Values are serialized and stored as `BYTE_ARRAY` +Example: -### 3. NamespacedKey Convenience Expression +```skript +"minecraft:foo" +"myplugin:data" +```` -- Allows creation of `NamespacedKey` from strings such as: - - `plugin:key` +--- ### 4. Safety and Validation -- Runtime validation ensures correct types for: - - Key - - Container - - PersistentDataType -- Invalid expressions fail silently to avoid hard Skript errors +* Runtime validation ensures correct types for: + + * Key string + * Container / holder + * PersistentDataType (if provided) +* Invalid expressions fail silently to avoid hard Skript errors --- ## Serialization Notes -- Arbitrary objects are serialized using `BukkitObjectOutputStream` -- Data is stored directly as `byte[]` via `PersistentDataType.BYTE_ARRAY` -- Objects **must** implement `java.io.Serializable` - -### Compared to Base64 - -- Lower overhead -- No string encoding or decoding -- Direct compatibility with Bukkit PDC APIs +* Arbitrary objects are serialized using `BukkitObjectOutputStream` +* Data is stored directly as `byte[]` via `PersistentDataType.BYTE_ARRAY` +* Objects **must** implement `java.io.Serializable` --- ## Syntax Summary +### Core Expression + +```skript +expression [devdinc] [namespaced]( |-)key %string% [with]in %pdcholder/pdc% [for %-pdt%] +``` + +--- + ### Reading ```skript set {_value} to key "plugin:test" within player's pdc set {_value} to key "plugin:test" in player's pdc for pdt string -```` +``` + +--- ### Writing @@ -107,6 +108,8 @@ set key "plugin:test" within player's pdc to {_object} set key "plugin:test" in player's pdc for pdt integer to 5 ``` +--- + ### Deleting ```skript @@ -115,10 +118,11 @@ delete key "plugin:test" within player's pdc --- -## Notes +## Behavior Notes * If no `PersistentDataType` is specified, `BYTE_ARRAY` storage is used -* Deleting a key or setting its value to `null` removes the entry +* Setting a key to `null` removes the entry +* Deleting a key removes it regardless of storage type --- @@ -126,4 +130,7 @@ delete key "plugin:test" within player's pdc * Deserialization failures will propagate runtime errors * Class definition changes may break previously serialized data -* Old Base64-encoded values must be migrated manually +* Stored objects must remain compatible with Java serialization + +--- + diff --git a/docs/scopedvariables.md b/docs/scopedvariables.md new file mode 100644 index 0000000..d8022cc --- /dev/null +++ b/docs/scopedvariables.md @@ -0,0 +1,249 @@ +# Scoped Variables Utility for Skript + +This module introduces **scoped variables**, allowing variables to be namespaced automatically by +script, folder (package), or a custom scope string. + +It provides: + +- A configurable **default scope per script** +- A `scoped variable` expression that transparently rewrites variable paths +- Full support for **get / set / add / remove / delete / reset** +- Correct handling of **local**, **ephemeral**, and **list** variables + +All scoping is implemented by rewriting variable names internally. + +--- + +## Concepts + +### What Is a Scoped Variable? + +A scoped variable is a normal Skript variable whose **storage key is prefixed with a scope**. + +Instead of: + +```skript +set {count} to 1 +```` + +you get: + +```text +scoped::::count +``` + +Scopes prevent collisions between scripts, folders, or logical modules while still using Skript’s variable system. + +--- + +## Default Scope per Script + +Each script can define a **default scope** that is used whenever no explicit scope is provided. + +### Effect Syntax + +```skript +set default scope for scoped variables in current script to + current folder + current package + current script + folder + package +