diff --git a/.vscode/settings.json b/.vscode/settings.json index a3f7565..ca72275 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "luau-lsp.require.mode": "relativeToFile", "luau-lsp.require.directoryAliases": { "@lune/": "~/.lune/.typedefs/0.7.11/" - } + }, + "luau-lsp.sourcemap.rojoProjectFile": "default.project.json" } \ No newline at end of file diff --git a/README.md b/README.md index 4959c70..9a5c459 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,5 @@ Head over to the [installation](https://github.com/1Axen/Blink/blob/main/docs/In # Credits Credits to [Zap](https://zap.redblox.dev/) for the range and array syntax Credits to [ArvidSilverlock](https://github.com/ArvidSilverlock) for the float16 implementation +Studio plugin auto completion icons are sourced from [Microsoft](https://github.com/microsoft/vscode-icons) and are under the [CC BY 4.0](https://github.com/microsoft/vscode-icons/blob/main/LICENSE) license. Speed icons created by alkhalifi design - Flaticon diff --git a/build/.darklua.json b/build/.darklua.json index 63559b3..d3c28f4 100644 --- a/build/.darklua.json +++ b/build/.darklua.json @@ -26,7 +26,7 @@ { "identifier": "VERSION", "rule": "inject_global_value", - "value": "0.6.4" + "value": "0.7.0" } ] } \ No newline at end of file diff --git a/build/build.bat b/build/build.bat index b910cfa..720cafc 100644 --- a/build/build.bat +++ b/build/build.bat @@ -27,8 +27,8 @@ echo Zipping files echo Packaging plugin cd ../plugin copy "..\build\.darklua.json" ".\.darklua.json" -darklua process "./src/init.luau" "./bundle/init.server.lua" -rojo build --output "../release/Plugin-%VERSION%.rbxmx" +darklua process "./src/init.server.luau" "./bundle/init.server.lua" +rojo build bundle.project.json --output "../release/Plugin-%VERSION%.rbxmx" cd ../build diff --git a/default.project.json b/default.project.json new file mode 100644 index 0000000..6d46e72 --- /dev/null +++ b/default.project.json @@ -0,0 +1,6 @@ +{ + "name": "Blink", + "tree": { + "$path": "plugin/src" + } + } \ No newline at end of file diff --git a/docs/Using.md b/docs/Using.md index d52f87a..253efe0 100644 --- a/docs/Using.md +++ b/docs/Using.md @@ -49,6 +49,7 @@ Blink supports the following primitives: |CFrame |24 Bytes |No |N/A |N/A | |Color3 |12 Bytes |No |N/A |N/A | |Instance|4 Bytes |No |N/A |N/A | +|unknown |N/A |No |N/A |N/A | Arrays can be defined by appending `[SIZE]` or `[[MIN]..[MAX]]` after the primitive type Primitives can be constrained to ranges by writing `([MIN]..[MAX])` after the primitive type. Ranges are inclusive. diff --git a/plugin/.darklua.json b/plugin/.darklua.json index 63559b3..d3c28f4 100644 --- a/plugin/.darklua.json +++ b/plugin/.darklua.json @@ -26,7 +26,7 @@ { "identifier": "VERSION", "rule": "inject_global_value", - "value": "0.6.4" + "value": "0.7.0" } ] } \ No newline at end of file diff --git a/plugin/build.bat b/plugin/build.bat index 1422a13..d5faaa3 100644 --- a/plugin/build.bat +++ b/plugin/build.bat @@ -1,5 +1,12 @@ @echo Off +copy ".\src\Error.rbxmx" ".\bundle\Error.rbxmx" +copy ".\src\Widget.rbxmx" ".\bundle\Widget.rbxmx" copy "..\build\.darklua.json" ".\.darklua.json" -darklua process "./src/init.luau" "./bundle/init.server.lua" -rojo build --output "%LocalAppData%\Roblox\Plugins\Blink.rbxmx" \ No newline at end of file + +darklua process "./src/init.server.luau" "./bundle/init.server.lua" +rojo build bundle.project.json --output "%LocalAppData%\Roblox\Plugins\Blink.rbxmx" + +cd .. +rojo sourcemap --output sourcemap.json --include-non-scripts +cd plugin \ No newline at end of file diff --git a/plugin/default.project.json b/plugin/bundle.project.json similarity index 100% rename from plugin/default.project.json rename to plugin/bundle.project.json diff --git a/plugin/bundle/Error.rbxm b/plugin/bundle/Error.rbxm deleted file mode 100644 index 5130a73..0000000 Binary files a/plugin/bundle/Error.rbxm and /dev/null differ diff --git a/plugin/bundle/Error.rbxmx b/plugin/bundle/Error.rbxmx new file mode 100644 index 0000000..5617198 --- /dev/null +++ b/plugin/bundle/Error.rbxmx @@ -0,0 +1,188 @@ + + true + null + nil + + + false + + 0 + 0 + + + true + 0 + + 0.156862751 + 0.156862751 + 0.156862751 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Error + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + false + 3 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxasset://fonts/families/Ubuntu.json + 400 + + + true + 0 + 1 + + + -1 + Text + null + null + null + null + + 0 + 0 + 0 + 0 + + true + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + + Label + + 1 + 1 + 1 + + 0 + false + 16 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 0 + true + 1 + + + + + + 0 + false + UIPadding + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + -1 + + + + + \ No newline at end of file diff --git a/plugin/bundle/Widget.rbxm b/plugin/bundle/Widget.rbxm deleted file mode 100644 index e040bb1..0000000 Binary files a/plugin/bundle/Widget.rbxm and /dev/null differ diff --git a/plugin/bundle/Widget.rbxmx b/plugin/bundle/Widget.rbxmx new file mode 100644 index 0000000..92df568 --- /dev/null +++ b/plugin/bundle/Widget.rbxmx @@ -0,0 +1,4013 @@ + + true + null + nil + + + false + + 0 + 0 + + + true + 0 + + 0.156862751 + 0.156862751 + 0.156862751 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + true + 0 + Widget + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 0.176470593 + 0.176470593 + 0.176470593 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Side + null + null + null + null + + 0 + -208 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 250 + 1 + 0 + + 0 + -1 + 0 + + true + 2 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + rbxassetid://5051528605 + + 0 + 0 + 0 + + + 0 + 0 + + + 0 + 0 + + 0.699999988 + true + 0 + Shadow + null + null + null + null + + 1 + 0 + 0 + 0 + + 0 + null + 0 + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 8 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + + 1 + 1 + 1 + + 1 + true + 0 + Content + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + + true + 1 + + + + + 0 + false + Padding + + 0 + 10 + + + 0 + 10 + + + 0 + 10 + + + 0 + 10 + + -1 + + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Top + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Title + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 22 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Search + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 200 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 0 + + 0 + + 0.0980392173 + 0.0980392173 + 0.0980392173 + + false + true + 0 + UIStroke + -1 + + 1 + 0 + + + + + true + + 0.5 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + false + + rbxassetid://12187374954 + 500 + + + true + 0 + 1 + + + -1 + false + Input + null + null + null + null + + 0.43921569 + 0.43921569 + 0.43921569 + + Search + + 0.5 + 0 + 0 + 0 + + false + null + 0 + true + 0 + 0 + 0 + 0 + false + null + 0 + true + + 0.899999976 + 0 + 1 + 0 + + 0 + -1 + + + + 1 + 1 + 1 + + 0 + true + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 1 + true + 1 + + + + + + + + 0 + false + 1 + 0 + 0 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 1 + 1 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + true + 0 + Files + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + 1 + 0 + 0 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 1 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + File + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 24 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 0 + 1 + + + -1 + Title + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + + Example File + + 1 + 1 + 1 + + 0 + false + 16 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 1 + true + 1 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Buttons + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + 0 + 2 + 0 + 0 + UIListLayout + + 0 + 5 + + 2 + -1 + + 1 + 0 + false + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://16483084978 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + false + Delete + null + null + null + null + + 0 + 0 + 0 + 0 + + + 0 + null + 0 + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + 0 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 1 + 0 + + 0 + false + 0 + UIAspectRatioConstraint + -1 + + + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://16483089645 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + false + Edit + null + null + null + null + + 0 + 0 + 0 + 0 + + + 0 + null + 0 + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + 0 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 1 + 0 + + 0 + false + 0 + UIAspectRatioConstraint + -1 + + + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://16494932101 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + false + Generate + null + null + null + null + + 0 + 0 + 0 + 0 + + + 0 + null + 0 + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + 0 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 1 + 0 + + 0 + false + 0 + UIAspectRatioConstraint + -1 + + + + + + + + + + + false + + 0 + 1 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Bottom + null + null + null + null + + 0 + 0 + 1 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 22 + + 0 + -1 + 0 + + true + 2 + + + + + 0 + false + 1 + 0 + 0 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 2 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 2 + Buttons + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + 0 + 1 + 1 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 1 + 0 + false + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 1 + 1 + + + -1 + false + Save + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 0 + 1 + 0 + + 0 + -1 + 0 + + Save + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 2 + 1 + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 0 + 1 + + + -1 + false + Cancel + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 0 + 1 + 0 + + 0 + -1 + 0 + + Cancel + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 2 + 1 + false + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Generate + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 72 + + 0 + -1 + 0 + + false + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 0 + + 0 + + 0.0980392173 + 0.0980392173 + 0.0980392173 + + false + true + 0 + UIStroke + -1 + + 1 + 0 + + + + + + 0 + false + UIPadding + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + -1 + + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 1 + 1 + + + -1 + Hint + null + null + null + null + + 0 + 0 + 0 + 0 + + true + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 30 + + 0 + -1 + + Selected +ReplicatedStroage.bla.bla.bla]]> + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 1 + true + 2 + 1 + true + 1 + + + + + + 0 + false + 1 + 0 + 0 + 0 + UIListLayout + + 0 + 2 + + 2 + -1 + + 0 + 3 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 1 + Buttons + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0.899999976 + 0 + 0 + 18 + + 0 + -1 + 0 + + true + 1 + + + + true + + 0 + 0 + + + true + true + 0 + + 0.196078435 + 0.196078435 + 0.196078435 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 1 + 1 + + + -1 + false + Cancel + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0.899999976 + 0 + 0 + 18 + + 0 + -1 + 0 + + Cancel + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 2 + 1 + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.196078435 + 0.196078435 + 0.196078435 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 1 + 1 + + + -1 + false + Generate + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0.899999976 + 0 + 0 + 18 + + 0 + -1 + 0 + + Generate + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0.5 + 0 + false + 2 + 1 + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + + + 0 + false + 0 + 1 + 1 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 1 + 0 + false + + + + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 1 + Save + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 22 + + 0 + -1 + 0 + + false + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 0 + + 0 + + 0.0980392173 + 0.0980392173 + 0.0980392173 + + false + true + 0 + UIStroke + -1 + + 1 + 0 + + + + + true + + 0.5 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + false + + rbxassetid://12187374954 + 500 + + + true + 0 + 1 + + + -1 + false + Input + null + null + null + null + + 0.43921569 + 0.43921569 + 0.43921569 + + Name + + 0.5 + 0 + 0 + 0 + + false + null + 0 + true + 0 + 0 + 0 + 0 + false + null + 0 + true + + 0.899999976 + 0 + 1 + 0 + + 0 + -1 + + + + 1 + 1 + 1 + + 0 + true + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 1 + true + 1 + + + + + + + + true + + 1 + 0 + + + true + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://16482966093 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + false + Expand + null + null + null + null + + 1 + -10 + 0 + 10 + + + 0 + null + 0 + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 22 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + 0 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + 1 + 0 + + 0 + false + 0 + UIAspectRatioConstraint + -1 + + + + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Editor + null + null + null + null + + 0 + 42 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + -42 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Lines + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 42 + 100 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + UIPadding + + 0 + 8 + + + 0 + 4 + + + 0 + 4 + + + 0 + 8 + + -1 + + + + + + + 0 + false + 1 + 1 + 0 + 0 + UIListLayout + + 0 + 3 + + 2 + -1 + + 1 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxasset://fonts/families/GothamSSm.json + 400 + + + true + 0 + 1 + + + -1 + Line + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 16 + + 0 + -1 + + 000 + + 0.470588267 + 0.470588267 + 0.470588267 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 1 + 1 + true + 1 + + + + + + + 0 + false + 0 + 1 + 1 + 0 + UIListLayout + + 0 + 0 + + 2 + -1 + + 1 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Text + null + null + null + null + + 0 + 20 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 100 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + UIPadding + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + -1 + + + + + + true + + 0 + 0 + + + true + 2 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 0 + 1.20000005 + + + -1 + true + Input + null + null + null + null + + 0.699999988 + 0.699999988 + 0.699999988 + + + + 0 + 0 + 0 + 0 + + true + null + 0 + true + 0 + 0 + 0 + 0 + false + null + 0 + true + + 1 + 0 + 1 + 0 + + 0 + -1 + + + + 1 + 1 + 1 + + 0 + true + false + 16 + + 0 + 0 + 0 + + 1 + 1 + 0 + false + 0 + 0 + true + 2 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Cursor + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 1 + 0 + 14 + + 0 + -1 + 0 + + false + 4 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 0 + 1.20000005 + + + -1 + Display + null + null + null + null + + 0 + 0 + 0 + 0 + + true + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + + + + 1 + 1 + 1 + + 0 + false + 16 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 0 + true + 0 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Selection + null + null + null + null + + 0 + 0 + 0 + -1 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + 1 + 1 + 0 + 0 + UIListLayout + + 0 + 0 + + 2 + -1 + + 1 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 0.200000018 + 0.411764741 + 0.627451003 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Line + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 19 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 0.200000018 + 0.411764741 + 0.627451003 + + 0.600000024 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Fill + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + + + + + false + + 0 + 0 + + + true + 0 + + 0 + 0 + 0 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Overlay + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + false + + 0 + 0 + + + true + 2 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Completion + null + null + null + null + + 0.252777785 + 0 + 0.0250000004 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 200 + 0 + 0 + + 0 + -1 + 0 + + false + 5 + + + + 0 + + 0 + + 0.470588267 + 0.470588267 + 0.470588267 + + false + true + 0 + UIStroke + -1 + + 1 + 0 + + + + + + 0 + + 0 + 2 + + false + UICorner + -1 + + + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Option + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 16 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + rbxassetid://16506695241 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + Icon + null + null + null + null + + 0 + 0 + 0 + 0 + + 1 + null + 0 + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 16 + 0 + 16 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 500 + + + true + 0 + 1 + + + -1 + Text + null + null + null + null + + 0 + 20 + 0 + 0 + + false + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 200 + 1 + 0 + + 0 + -1 + + struct + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 1 + true + 1 + + + + + + 0 + false + 0 + 1 + 1 + 0 + UIListLayout + + 0 + 4 + + 2 + -1 + + 1 + 0 + false + + + + + + 0 + false + UIPadding + + 0 + 0 + + + 0 + 5 + + + 0 + 5 + + + 0 + 0 + + -1 + + + + + + + 0 + + 0 + 4 + + false + UICorner + -1 + + + + + + + + 0 + false + UIPadding + + 0 + 2 + + + 0 + 0 + + + 0 + 0 + + + 0 + 2 + + -1 + + + + + + \ No newline at end of file diff --git a/plugin/bundle/init.server.lua b/plugin/bundle/init.server.lua index dcf7218..283e4bd 100644 --- a/plugin/bundle/init.server.lua +++ b/plugin/bundle/init.server.lua @@ -55,6 +55,48 @@ do return State end function __DARKLUA_BUNDLE_MODULES.b() + local Table = {} + + function Table.MergeArrays(a, b) + local Array = table.create(#a + #b) + + for _, Element in a do + table.insert(Array, Element) + end + for _, Element in b do + table.insert(Array, Element) + end + + return Array + end + function Table.MergeDictionaries(a, b) + local Dictionary = table.clone(a) + + for Key, Value in b do + if Dictionary[Key] then + warn(`Key "{Key}" already exists in the first dictionary.`) + + continue + end + + Dictionary[Key] = Value + end + + return Dictionary + end + function Table.GetDictionaryKeys(Dictionary) + local Keys = {} + + for Key in Dictionary do + table.insert(Keys, Key) + end + + return Keys + end + + return Table + end + function __DARKLUA_BUNDLE_MODULES.c() local stdio if not true then @@ -82,6 +124,7 @@ do yellow = 'rgb(255, 255, 0)', } local Error = { + OnEmit = nil, LexerUnexpectedToken = 1001, ParserUnexpectedEndOfFile = 2001, ParserUnexpectedToken = 2002, @@ -97,6 +140,7 @@ do AnalyzeUnknownReference = 3007, AnalyzeInvalidRangeType = 3008, AnalyzeInvalidRange = 3009, + AnalyzeDuplicateDeclaration = 3010, } Error.__index = Error @@ -122,6 +166,7 @@ do Content ..= `\n{INDENT}\u{250c}\u{2500}{SPACE}input.blink` return setmetatable({ + Labels = {}, Source = string.split(Source, '\n'), Message = Content, }, Error) @@ -155,6 +200,12 @@ do function Error.Primary(self, Span, Text) local Slice = self:Slice(Span) + table.insert(self.Labels, { + Span = Span, + Text = Text, + Type = 'Primary', + }) + self.Message ..= `\n{Color(string.format('%03i', Slice.Line), 'blue')}{SPACE}\u{2502}{SPACE}{Slice.Text}` self.Message ..= `\n{INDENT}\u{2502}{SPACE}{string.rep(SPACE, Slice.Spaces)}{Color(`{string.rep('^', Slice.Underlines)}{SPACE}{Fix(Text)}`, 'red')}` @@ -163,47 +214,67 @@ do function Error.Secondary(self, Span, Text) local Slice = self:Slice(Span) + table.insert(self.Labels, { + Span = Span, + Text = Text, + Type = 'Secondary', + }) + self.Message ..= `\n{Color(string.format('%03i', Slice.Line), 'blue')}{SPACE}\u{2502}{SPACE}{Slice.Text}` self.Message ..= `\n{INDENT}\u{2502}{SPACE}{string.rep(SPACE, Slice.Spaces)}{Color(`{string.rep('^', Slice.Underlines)}{SPACE}{Fix(Text)}`, 'blue')}` return self end function Error.Emit(self) + local OnEmit = Error.OnEmit + + if OnEmit then + OnEmit(self.Labels) + end + error(self.Message, 2) end return Error end - function __DARKLUA_BUNDLE_MODULES.c() - local Error = __DARKLUA_BUNDLE_MODULES.load('b') - local Primitives = { - u8 = true, - u16 = true, - u32 = true, - i8 = true, - i16 = true, - i32 = true, - f16 = true, - f32 = true, - f64 = true, - boolean = true, - string = true, - vector = true, - buffer = true, - Color3 = true, - CFrame = true, - Instance = true, - } - local Keywords = { - map = true, - type = true, - enum = true, - struct = true, - event = true, - ['function'] = true, - scope = true, - option = true, + function __DARKLUA_BUNDLE_MODULES.d() + return { + Keywords = { + map = true, + type = true, + enum = true, + struct = true, + event = true, + ['function'] = true, + scope = true, + option = true, + }, + Primtives = { + u8 = true, + u16 = true, + u32 = true, + i8 = true, + i16 = true, + i32 = true, + f16 = true, + f32 = true, + f64 = true, + boolean = true, + string = true, + vector = true, + buffer = true, + Color3 = true, + CFrame = true, + Instance = true, + unknown = true, + }, } + end + function __DARKLUA_BUNDLE_MODULES.e() + local Error = __DARKLUA_BUNDLE_MODULES.load('c') + local Settings = __DARKLUA_BUNDLE_MODULES.load('d') + local Primitives = Settings.Primtives + local Keywords = Settings.Keywords local Booleans = { ['true'] = true, ['false'] = true, @@ -390,7 +461,7 @@ do return Lexer end - function __DARKLUA_BUNDLE_MODULES.d() + function __DARKLUA_BUNDLE_MODULES.f() return { DeepClone = function(Table) local function Clone(Source) @@ -411,11 +482,10 @@ do end, } end - function __DARKLUA_BUNDLE_MODULES.e() - local Lexer = __DARKLUA_BUNDLE_MODULES.load('c') - local Error = __DARKLUA_BUNDLE_MODULES.load('b') - local Table = __DARKLUA_BUNDLE_MODULES.load('d') - local Errors = {DuplicateDeclaration = 3001} + function __DARKLUA_BUNDLE_MODULES.g() + local Lexer = __DARKLUA_BUNDLE_MODULES.load('e') + local Error = __DARKLUA_BUNDLE_MODULES.load('c') + local Table = __DARKLUA_BUNDLE_MODULES.load('f') local RangePrimitives = { u8 = true, u16 = true, @@ -430,7 +500,26 @@ do vector = true, buffer = true, } + local OptionalPrimitives = { + u8 = true, + u16 = true, + u32 = true, + i8 = true, + i16 = true, + i32 = true, + f16 = true, + f32 = true, + f64 = true, + boolean = true, + string = true, + vector = true, + buffer = true, + Color3 = true, + CFrame = true, + Instance = true, + } local OptionsTypes = { + TypesOutput = 'String', ClientOutput = 'String', ServerOutput = 'String', FutureLibrary = 'String', @@ -511,44 +600,49 @@ do end, } - local function GetAttributeRange(Token, Array, Source) - local Lower, Upper + local function GetTypeRange(Token, Array, Source) + local Value = Token.Value - if Array then - Lower, Upper = string.match(Token.Value, `^%[({Number})..({Number})]`) - Lower = Lower or string.match(Token.Value, `^%[({Number})]`) + local function ThrowMalformedRange() + Error.new(Error.AnalyzeInvalidRange, Source, 'Malformed range'):Primary(Token, 'Unable to parse range'):Emit() + error('Unable to parse range') + end - local Size = tonumber(Lower) or -1 + local Single = Array and string.match(Value, '%[(%d+)%]') or string.match(Value, '%((%d+)%)') - if Size < 0 then - Error.new(Error.AnalyzeInvalidRange, Source, 'Invalid array size'):Primary(Token, 'Array cannot be smaller than 0 elements'):Emit() + if Single then + local Number = (tonumber(Single)) + + if not Number then + ThrowMalformedRange() end - if not Upper then - return (NumberRange.new(Size)) + if Array and Number < 0 then + Error.new(Error.AnalyzeInvalidRange, Source, 'Invalid array size'):Primary(Token, 'Array cannot be smaller than 0 elements'):Emit() end - else - Lower, Upper = string.match(Token.Value, `^%(({Number})..({Number})%)`) - if not Upper then - Lower = string.match(Token.Value, `^%(({Number})%)`) - end - end - if not Lower then - error(`Unexpected error while trying to parse range.`) + return NumberRange.new(Number, Number) end - local Min = tonumber(Lower) or 0 - local Max + local Lower = Array and string.match(Value, '%[(%d+)') or string.match(Value, '%((%d+)') + local Upper = Array and string.match(Value, '(%d+)%]') or string.match(Value, '(%d+)%)') - if Upper then - Max = tonumber(Upper) or 0 + if Lower and Upper then + local Minimum = (tonumber(Lower)) + local Maximum = (tonumber(Upper)) - if Min >= Max then + if not Minimum or not Maximum then + ThrowMalformedRange() + end + if Minimum >= Maximum then Error.new(Error.AnalyzeInvalidRange, Source, 'Invalid range'):Primary(Token, 'Maximum must be greater than minimum'):Emit() end + + return NumberRange.new(Minimum, Maximum) end - return NumberRange.new(Min, Max) + ThrowMalformedRange() + + return NumberRange.new(0, 0) end local Parser = {} @@ -630,6 +724,7 @@ do return { Type = 'Body', Value = self:Declarations(), + Tokens = {}, } end function Parser.Options(self) @@ -679,44 +774,53 @@ do return Declarations end function Parser.Declaration(self) - local Keyword = self:Consume('Keyword') + local Keyword = self:Consume('Keyword').Value local Identifier = self:Consume('Identifier') self:Consume('Assign') - local Type = KeywordTypes[Keyword.Value] + local Type = KeywordTypes[Keyword] local Duplicates = self[Type] local ScopedIdentifier = `{self.Scope and `{self.Scope}.` or ''}{Identifier.Value}` local Duplicate = Duplicates[ScopedIdentifier] if Duplicate then - Error.new(Errors.DuplicateDeclaration, self.Source, `Duplicate {Keyword.Value} "{Identifier.Value}"`):Secondary(Duplicate.Identifier, 'Previously delcared here'):Primary(Identifier, 'Duplicate declaration here'):Emit() + Error.new(Error.AnalyzeDuplicateDeclaration, self.Source, `Duplicate {Keyword} "{Identifier.Value}"`):Secondary(Duplicate.Identifier, 'Previously delcared here'):Primary(Identifier, 'Duplicate declaration here'):Emit() end local Declaration - if Keyword.Value == 'map' then + if Keyword == 'map' then Declaration = self:MapDeclaration(Identifier) - elseif Keyword.Value == 'type' then + elseif Keyword == 'type' then Declaration = self:TypeDeclaration(Identifier) - elseif Keyword.Value == 'enum' then + elseif Keyword == 'enum' then Declaration = self:EnumDeclaration(Identifier) - elseif Keyword.Value == 'struct' then + elseif Keyword == 'struct' then Declaration = self:StructDeclaration(Identifier) - elseif Keyword.Value == 'event' then + elseif Keyword == 'event' then Declaration = self:EventDeclaration(Identifier) - elseif Keyword.Value == 'function' then + elseif Keyword == 'function' then Declaration = self:FunctionDeclaration(Identifier) - elseif Keyword.Value == 'scope' then + elseif Keyword == 'scope' then Declaration = self:ScopeDeclaration(Identifier) end if not Declaration then - error(`{Keyword.Value} has no declaration handler.`) + error(`{Keyword} has no declaration handler.`) end if Duplicates == self.Types and (self:GetSafeLookAhead().Type == 'Optional') then - self:Consume('Optional') + local Optional = self:Consume('Optional') + + if Declaration.Type == 'TypeDeclaration' then + local Primitive = (Declaration).Value.Primitive + + if Primitive == 'unknown' then + Error.new(Error.AnalyzeInvalidOptionalType, self.Source, `Invalid optional type`):Primary(Optional, `"unknown" cannot be optional`):Emit() + end + end Declaration.Value.Optional = true + Declaration.Tokens.Optional = Optional end Duplicates[ScopedIdentifier] = { @@ -743,6 +847,8 @@ do Declaration = Table.DeepClone(Reference) Declaration.Value.Identifier = Identifier.Value + Declaration.Tokens.Identifier = Identifier + Declaration.Tokens.Value = LookAhead else Primitive = self:Consume('Primitive') Declaration = { @@ -753,6 +859,10 @@ do Primitive = Primitive.Value, Optional = false, }, + Tokens = { + Identifier = Identifier, + Value = Primitive, + }, } if Primitive.Value == 'Instance' then @@ -819,6 +929,7 @@ do Enums = Enums, Optional = false, }, + Tokens = {Identifier = Identifier}, } end function Parser.MapDeclaration(self, Identifier) @@ -878,6 +989,11 @@ do Array = Array, Optional = false, }, + Tokens = { + Identifier = Identifier, + Key = Key, + Value = Value, + }, } end function Parser.StructDeclaration(self, Identifier) @@ -926,6 +1042,7 @@ do Fields = Fields, Optional = false, }, + Tokens = {Identifier = Identifier}, } end function Parser.TupleDeclaration(self, Identifier) @@ -961,6 +1078,7 @@ do Values = Values, Optional = false, }, + Tokens = {Identifier = Identifier}, } end function Parser.EventDeclaration(self, Identifier) @@ -981,6 +1099,10 @@ do Data = nil, Optional = false, }, + Tokens = { + Identifier = Identifier, + Fields = {}, + }, } for Index, Entry in EventStructure do @@ -992,9 +1114,10 @@ do local Value local Assign = self:Consume('Assign') + local Token = self:GetSafeLookAhead() if Entry.Key ~= 'Data' then - local Token = self:Consume(Entry.Type) + Token = self:Consume(Entry.Type) if not table.find(Entry.Values, Token.Value) then Error.new(Error.ParserUnexpectedToken, self.Source, `Unexpected token`):Primary(Token, `Expected one of "{table.concat(Entry.Values, '" or "')}", found "{Token.Value}"`):Emit() @@ -1002,9 +1125,7 @@ do Value = Token.Value else - local Token = self.LookAhead - - if not Token then + if not self.LookAhead then Error.new(Error.ParserExpectedExtraToken, self.Source, `Expected a token`):Primary(Assign, `Expected a value to follow after assignment`):Emit() break @@ -1018,6 +1139,11 @@ do Event.Value[Entry.Key] = Value + table.insert(Event.Tokens, { + Field = Key, + Value = Token, + }) + if Index ~= #EventStructure then self:Consume('Comma') end @@ -1044,6 +1170,10 @@ do Data = nil, Return = nil, }, + Tokens = { + Identifier = Identifier, + Fields = {}, + }, } for Index, Entry in FunctionStructure do @@ -1055,9 +1185,10 @@ do local Value local Assign = self:Consume('Assign') + local Token = self:GetSafeLookAhead() if Entry.Key ~= 'Data' and Entry.Key ~= 'Return' then - local Token = self:Consume(Entry.Type) + Token = self:Consume(Entry.Type) if not table.find(Entry.Values, Token.Value) then Error.new(Error.ParserUnexpectedToken, self.Source, `Unexpected token`):Primary(Token, `Expected one of "{table.concat(Entry.Values, '" or "')}", found "{Token.Value}"`):Emit() @@ -1065,9 +1196,7 @@ do Value = Token.Value else - local Token = self.LookAhead - - if not Token then + if not self.LookAhead then Error.new(Error.ParserExpectedExtraToken, self.Source, `Expected a token`):Primary(Assign, `Expected a value to follow after assignment`):Emit() break @@ -1081,6 +1210,11 @@ do Function.Value[Entry.Key] = Value + table.insert(Function.Tokens, { + Field = Key, + Value = Token, + }) + if Index ~= #FunctionStructure then self:Consume('Comma') end @@ -1107,6 +1241,7 @@ do Optional = false, Declarations = Declarations, }, + Tokens = {Identifier = Identifier}, } self.Scope = Scope @@ -1162,9 +1297,9 @@ do Error.new(Error.AnalyzeInvalidRangeType, self.Source, 'Type does not support ranges'):Primary(LookAhead, `"{Primitive.Value}" does not support ranges`):Emit() end - Range = GetAttributeRange(LookAhead, false, self.Source) + Range = GetTypeRange(LookAhead, false, self.Source) elseif Type == 'Array' then - Array = GetAttributeRange(LookAhead, true, self.Source) + Array = GetTypeRange(LookAhead, true, self.Source) end self:Consume(Type) @@ -1188,8 +1323,9 @@ do elseif Token.Type == 'Primitive' then Declaration = self:TypeDeclaration(Identifier) elseif Token.Type == 'Identifier' then - local Type = self:Consume('Identifier') - local Reference = self:GetReference(Type) + self:Consume('Identifier') + + local Reference = self:GetReference(Token) local Array, Range = self:GetTypeAttributes() Declaration = { @@ -1202,6 +1338,7 @@ do Range = Range, Optional = false, }, + Tokens = {Identifier = Identifier}, } end if not Declaration then @@ -1213,6 +1350,14 @@ do if self:GetSafeLookAhead().Type == 'Optional' then Optional = self:Consume('Optional') Declaration.Value.Optional = true + + if Token.Type == 'Identifier' then + local Type = (self:GetReference(Token)) + + if Type.Value.Primitive == 'unknown' then + Error.new(Error.AnalyzeInvalidOptionalType, self.Source, `Invalid optional type`):Secondary(Type.Tokens.Value, 'Declared here'):Primary(Optional, `{Type.Value.Identifier} is of type "unknown", "unknown" cannot be optional`):Emit() + end + end end return Declaration, Optional @@ -1220,7 +1365,459 @@ do return Parser end - function __DARKLUA_BUNDLE_MODULES.f() + function __DARKLUA_BUNDLE_MODULES.h() + local Hook = {} + local IndentKeywords = { + '{\n', + } + + local function ShouldAutoIndent(Text, Cursor) + for Index, Keyword in IndentKeywords do + local Position = (Cursor - #Keyword) + + if Position <= 0 then + continue + end + + local Previous = string.sub(Text, Position, Cursor - 1) + + if Previous == Keyword then + return true + end + end + + return false + end + local function GetLineIndentation(Line) + return #((string.match(Line, '^\t*'))) + end + local function GetCurrentLine(Text, Cursor) + local Line = 0 + local Position = 0 + local Slices = string.split(Text, '\n') + + for Index, Slice in Slices do + Position += (#Slice + 1) + + if Cursor <= Position then + Line = Index + + break + end + end + + return Line, Slices + end + + function Hook.OnSourceChanged(Text, Cursor, Gain) + if Gain ~= 1 then + return Text, Cursor + end + + local CanIndent = false + local AdditionalIndent = 0 + local Line, Lines = GetCurrentLine(Text, Cursor) + local Current = Lines[Line] + local Previous = Lines[Line - 1] + local JustReached = (Previous and Current == '') + + if ShouldAutoIndent(Text, Cursor) then + CanIndent = true + AdditionalIndent = 1 + elseif JustReached then + if GetLineIndentation(Previous) > 0 then + CanIndent = true + end + end + if not CanIndent then + return Text, Cursor + end + + local Indentation = GetLineIndentation(Previous) + AdditionalIndent + + Text = string.sub(Text, 1, Cursor - 1) .. string.rep('\t', Indentation) .. string.sub(Text, Cursor) + + return Text, Cursor + Indentation + end + + return Hook + end + function __DARKLUA_BUNDLE_MODULES.i() + local TextService = game:GetService('TextService') + local RunService = game:GetService('RunService') + local Table = __DARKLUA_BUNDLE_MODULES.load('b') + local State = __DARKLUA_BUNDLE_MODULES.load('a') + local Lexer = __DARKLUA_BUNDLE_MODULES.load('e') + local Parser = __DARKLUA_BUNDLE_MODULES.load('g') + local Settings = __DARKLUA_BUNDLE_MODULES.load('d') + local Error = __DARKLUA_BUNDLE_MODULES.load('c') + local StylingHooks = { + __DARKLUA_BUNDLE_MODULES.load('h'), + } + local ICONS = { + Event = 'rbxassetid://16506730516', + Field = 'rbxassetid://16506725096', + Snippet = 'rbxassetid://16506712161', + Keyword = 'rbxassetid://16506695241', + Variable = 'rbxassetid://16506719167', + Primitive = 'rbxassetid://16506695241', + } + local COLORS = { + Text = Color3 .fromHex('#FFFFFF'), + Keyword = Color3 .fromHex('#6796E6'), + Primitive = Color3 .fromHex('#4EC9B0'), + Identifier = Color3 .fromHex('#9CDCFE'), + Class = Color3 .fromHex('#B5CEA8'), + Number = Color3 .fromHex('#B5CEA8'), + String = Color3 .fromHex('#ADF195'), + Bracket = Color3 .fromHex('#FFFFFF'), + Boolean = Color3 .fromHex('#B5CEA8'), + Error = Color3 .fromHex('#FF5050'), + Complete = Color3 .fromHex('#04385f'), + } + local SYMBOLS = { + Event = {}, + Field = {}, + Snippet = {}, + Keyword = Table.GetDictionaryKeys(Settings.Keywords), + Variable = {}, + Primitive = Table.GetDictionaryKeys(Settings.Primtives), + } + local FIELDS = { + struct = true, + event = true, + ['function'] = true, + } + local BRACKETS = { + OpenBrackets = true, + CloseBrackets = true, + OpenCurlyBrackets = true, + CloseCurlyBrackets = true, + OpenSquareBrackets = true, + CloseSquareBrackets = true, + } + local SCROLL_LINES = 2 + local CURSOR_BLINK_RATE = 0.5 + local Editor = {} + local Container = script.Widget + local EditorContainer = Container.Editor + local CompletionContainer = Container.Completion + local TextContainer = EditorContainer.Text + local LinesContainer = EditorContainer.Lines + local Input = TextContainer.Input + local Cursor = TextContainer.Cursor + local Display = TextContainer.Display + local Selection = TextContainer.Selection + local LineTemplate = LinesContainer.Line:Clone() + local SelectionTemplate = Selection.Line:Clone() + local CompletionTemplate = CompletionContainer.Option:Clone() + local TextSize = Input.TextSize + local TextHeight = (Input.TextSize + 3) + local SourceLexer = Lexer.new('Highlighting') + local SourceParser = Parser.new() + local Lines = State.new(0) + local Scroll = State.new(0) + local Errors = State.new({}) + local CursorTimer = 0 + local PreviousText = Input.Text + + local function ScrollTowards(Direction) + local Value = Scroll:Get() + local Maximum = math.max(1, (Input.TextBounds.Y - EditorContainer.AbsoluteSize.Y) // TextHeight + SCROLL_LINES) + + Scroll:Set(math.clamp(Value + (Direction * SCROLL_LINES), 0, Maximum)) + end + local function WrapColor(Text, Color) + return `{Text}` + end + local function ClearChildrenWhichAre(Parent, Class) + for Index, Child in Parent:GetChildren()do + if Child:IsA(Class) then + Child:Destroy() + end + end + end + local function DoLexerPass() + local Source = Input.Text + local RichText = '' + + SourceLexer:Initialize(Source) + + local Keyword = 'none' + local IsField = false + + while true do + local Success, Error, Token = pcall(function() + return nil, SourceLexer:GetNextToken() + end) + + if not Success then + warn(`Lexer error: {Error}`) + + break + end + if not Token then + break + end + + local Type = Token.Type + local Value = Token.Value + + if Type == 'Keyword' then + Keyword = Value + + RichText ..= WrapColor(Value, COLORS.Keyword) + elseif Type == 'Primitive' then + RichText ..= WrapColor(Value, COLORS.Primitive) + elseif Type == 'Identifier' then + RichText ..= WrapColor(Value, IsField and COLORS.Text or COLORS.Identifier) + elseif Type == 'Array' or Type == 'Range' then + local Single = string.match(Value, '%[(%d+)%]') or string.match(Value, '%((%d+)%)') + + if Single then + RichText ..= string.gsub(Value, Single, WrapColor(Single, COLORS.Number)) + + continue + end + + local Lower = string.match(Value, '%[(%d+)') or string.match(Value, '%((%d+)') + local Upper = string.match(Value, '(%d+)%]') or string.match(Value, '(%d+)%)') + + if Lower and Upper then + RichText ..= `{string.sub(Value, 1, 1)}{WrapColor(Lower, COLORS.Number)}..{WrapColor(Upper, COLORS.Number)}{string.sub(Value, #Value, #Value)}` + + continue + end + + RichText ..= Value + elseif BRACKETS[Type] then + if Type == 'CloseCurlyBrackets' then + IsField = false + end + + RichText ..= WrapColor(Value, COLORS.Bracket) + elseif Type == 'Class' then + RichText ..= `({WrapColor(string.sub(Value, 2, #Value - 1), COLORS.Class)})` + elseif Type == 'String' then + RichText ..= WrapColor(Value, COLORS.String) + elseif Type == 'Boolean' then + RichText ..= WrapColor(Value, COLORS.Boolean) + elseif Type == 'Unknown' then + IsField = false + + RichText ..= WrapColor(Value, COLORS.Error) + else + RichText ..= Value + end + if Type == 'Whitespace' then + continue + end + + IsField = (Type == 'Comma' or Type == 'OpenCurlyBrackets') and FIELDS[Keyword] + end + + Display.Text = RichText + end + local function OnErrorEmitted(Labels) + Errors:Set(Labels) + end + local function OnPreRender(DeltaTime) + if Input.CursorPosition == -1 then + return + end + + CursorTimer += DeltaTime + + if CursorTimer < CURSOR_BLINK_RATE then + return + end + + CursorTimer -= CURSOR_BLINK_RATE + + Cursor.Visible = not Cursor.Visible + end + local function OnLinesChanged(Value) + ClearChildrenWhichAre(LinesContainer, 'TextLabel') + + for Index = 1, Value do + local Line = LineTemplate:Clone() + + Line.Text = tostring(Index) + Line.Parent = LinesContainer + end + end + local function OnScrollChanged(Value) + EditorContainer.Position = UDim2 .fromOffset(EditorContainer.Position.X.Offset, TextHeight * +-Value) + end + local function OnCursorPositionChanged() + local CursorPosition = Input.CursorPosition + + if CursorPosition == -1 then + Cursor.Visible = false + + return + end + + local Text = string.sub(Input.Text, 1, CursorPosition - 1) + local Slices = string.split(Text, '\n') + local Line = Slices[#Slices] or '' + local GetTextBoundsParams = Instance.new('GetTextBoundsParams') + + GetTextBoundsParams.Text = Line + GetTextBoundsParams.Size = TextSize + GetTextBoundsParams.Font = Input.FontFace + + local TextBounds = TextService:GetTextBoundsAsync(GetTextBoundsParams) + + Cursor.Size = UDim2 .fromOffset(2, TextSize) + Cursor.Position = UDim2 .fromOffset(TextBounds.X - 1, TextHeight * (#Slices - 1)) + end + local function OnSelectionChanged() + ClearChildrenWhichAre(Selection, 'Frame') + + local CursorPosition = Input.CursorPosition + local SelectionStart = Input.SelectionStart + + if CursorPosition == -1 or SelectionStart == -1 then + return + end + + local Start = math.min(CursorPosition, SelectionStart) + local Finish = math.max(CursorPosition, SelectionStart) + local Text = string.sub(Input.ContentText, 1, Finish - 1) + local Slices = string.split(Text, '\n') + local Offset = 0 + + for Index, Slice in Slices do + local First = Offset + + Offset += (#Slice + 1) + + local Line = SelectionTemplate:Clone() + local Fill = Line.Fill + + if Offset < Start then + Fill.BackgroundTransparency = 1 + Line.Parent = Selection + + continue + end + + local Substring = Slice + + if First < Start then + Substring = string.sub(Slice, (Start - First), #Slice) + elseif Index == #Slices then + Substring = string.sub(Slice, 1, math.max(1, Finish - First)) + end + if Substring == '' then + Substring = ' ' + end + + local SelectionBoundsParams = Instance.new('GetTextBoundsParams') + + SelectionBoundsParams.Text = Substring + SelectionBoundsParams.Size = TextSize + SelectionBoundsParams.Font = Input.FontFace + + local SelectionBounds = TextService:GetTextBoundsAsync(SelectionBoundsParams) + + Fill.Size = UDim2 .new(0, SelectionBounds.X, 1, 0) + + if Start > First then + local Prefix = string.sub(Slice, 1, (Start - First) - 1) + + if Prefix ~= '' then + local OffsetBoundsParams = Instance.new('GetTextBoundsParams') + + OffsetBoundsParams.Text = Prefix + OffsetBoundsParams.Size = TextSize + OffsetBoundsParams.Font = Input.FontFace + + local OffsetBounds = TextService:GetTextBoundsAsync(OffsetBoundsParams) + + Fill.Position = UDim2 .new(0, OffsetBounds.X, 0, 0) + end + end + + Line.Parent = Selection + end + end + local function OnSourceChanged() + local Text = Input.Text + local Gain = math.sign(#Text - #PreviousText) + local NoReturnCarriage = string.gsub(Text, '\r', '') + + if NoReturnCarriage ~= Text then + Input.Text = NoReturnCarriage + + return + end + + PreviousText = Text + + local SourceLines = #string.split(Text, '\n') + + if SourceLines ~= Lines:Get() then + Lines:Set(SourceLines) + end + + local FinalText, FinalCursor = Text, Input.CursorPosition + + for Index, Hook in StylingHooks do + FinalText, FinalCursor = Hook.OnSourceChanged(FinalText, FinalCursor, Gain) + end + + if FinalText ~= Text then + Input.CursorPosition = -1 + Input.Text = FinalText + Input.CursorPosition = FinalCursor + + return + end + + DoLexerPass() + end + + function Editor.GetSource() + return Input.Text + end + function Editor.SetSource(Source) + Scroll:Set(0) + + Input.Text = Source + end + function Editor.Initialize() + Selection.Line:Destroy() + LinesContainer.Line:Destroy() + CompletionContainer.Option:Destroy() + + for _, Symbols in SYMBOLS do + table.sort(Symbols, function(a, b) + return #a < #b + end) + end + + Error.OnEmit = OnErrorEmitted + + Lines:OnChange(OnLinesChanged) + Scroll:OnChange(OnScrollChanged) + Input:GetPropertyChangedSignal('Text'):Connect(OnSourceChanged) + Input:GetPropertyChangedSignal('SelectionStart'):Connect(OnSelectionChanged) + Input:GetPropertyChangedSignal('CursorPosition'):Connect(OnSelectionChanged) + Input:GetPropertyChangedSignal('CursorPosition'):Connect(OnCursorPositionChanged) + Input.InputChanged:Connect(function(InputObject) + if InputObject.UserInputType == Enum.UserInputType.MouseWheel then + ScrollTowards(-InputObject.Position.Z) + end + end) + RunService.PreRender:Connect(OnPreRender) + end + + return Editor + end + function __DARKLUA_BUNDLE_MODULES.j() local Operators = { Not = '~=', Equals = '==', @@ -1423,7 +2020,7 @@ do Connection = Connection.new, } end - function __DARKLUA_BUNDLE_MODULES.g() + function __DARKLUA_BUNDLE_MODULES.k() local Builder = {} function Builder.new(String, BaseIndentation) @@ -1489,10 +2086,10 @@ do return Builder end - function __DARKLUA_BUNDLE_MODULES.h() - local Blocks = __DARKLUA_BUNDLE_MODULES.load('f') - local Parser = __DARKLUA_BUNDLE_MODULES.load('e') - local Builder = __DARKLUA_BUNDLE_MODULES.load('g') + function __DARKLUA_BUNDLE_MODULES.l() + local Blocks = __DARKLUA_BUNDLE_MODULES.load('j') + local Parser = __DARKLUA_BUNDLE_MODULES.load('g') + local Builder = __DARKLUA_BUNDLE_MODULES.load('k') local SEND_BUFFER = 'SendBuffer' local RECIEVE_BUFFER = 'RecieveBuffer' local SEND_POSITION = 'SendOffset' @@ -1556,7 +2153,10 @@ do local IsVariableSize = (Range and Range.Max ~= Range.Min) local function GenerateValidation(Block, Variable) - if Range and AssertGenerator then + if not AssertGenerator then + return + end + if Range then local Assert = AssertGenerator(Variable, Range.Min, Range.Max) if not IsVariableSize and Assert.Exact then @@ -1565,7 +2165,7 @@ do Block:Line(Assert.Lower) Block:Line(Assert.Upper) end - elseif Primitive == 'Instance' and AssertGenerator then + elseif Primitive == 'Instance' then local Assert = AssertGenerator(Variable, Class) if Value.Optional and Block == Write then @@ -1810,6 +2410,21 @@ do Generate = GeneratePrimitivePrefab(Types.Instance, Asserts.Instance), } end + do + Types.unknown = { + Read = function(Variable, Block) + Block:Line('RecieveInstanceCursor += 1') + Block:Line(`{Variable} = RecieveInstances[RecieveInstanceCursor]`) + end, + Write = function(Value, Block) + Block:Line(`table.insert(SendInstances, {Value})`) + end, + } + Primitives.unknown = { + Type = 'any', + Generate = GeneratePrimitivePrefab(Types.unknown), + } + end return { Types = Types, @@ -1818,24 +2433,24 @@ do Structures = Structures, } end - function __DARKLUA_BUNDLE_MODULES.i() + function __DARKLUA_BUNDLE_MODULES.m() return 'local Invocations = 0\r\n\r\nlocal SendOffset = 0\r\nlocal SendCursor = 0\r\nlocal SendBuffer = buffer.create(64)\r\nlocal SendInstances = {}\r\n\r\nlocal RecieveCursor = 0\r\nlocal RecieveBuffer = buffer.create(64)\r\n\r\nlocal RecieveInstances = {}\r\nlocal RecieveInstanceCursor = 0\r\n\r\ntype BufferSave = {Cursor: number, Buffer: buffer, Instances: {Instance}}\r\n\r\nlocal function Read(Bytes: number)\r\n local Offset = RecieveCursor\r\n RecieveCursor += Bytes\r\n return Offset\r\nend\r\n\r\nlocal function Save(): BufferSave\r\n return {\r\n Cursor = SendCursor,\r\n Buffer = SendBuffer,\r\n Instances = SendInstances\r\n }\r\nend\r\n\r\nlocal function Load(Save: BufferSave?)\r\n if Save then\r\n SendCursor = Save.Cursor\r\n SendOffset = Save.Cursor\r\n SendBuffer = Save.Buffer\r\n SendInstances = Save.Instances\r\n return\r\n end\r\n\r\n SendCursor = 0\r\n SendOffset = 0\r\n SendBuffer = buffer.create(64)\r\n SendInstances = {}\r\nend\r\n\r\nlocal function Invoke()\r\n if Invocations == 255 then\r\n Invocations = 0\r\n end\r\n\r\n local Invocation = Invocations\r\n Invocations += 1\r\n return Invocation\r\nend\r\n\r\nlocal function Allocate(Bytes: number)\r\n local Len = buffer.len(SendBuffer)\r\n\r\n local Size = Len\r\n local InUse = (SendCursor + Bytes)\r\n \r\n if InUse > Size then\r\n --> Avoid resizing the buffer for every write\r\n while InUse > Size do\r\n Size *= 1.5\r\n end\r\n\r\n local Buffer = buffer.create(Size)\r\n buffer.copy(Buffer, 0, SendBuffer, 0, Len)\r\n SendBuffer = Buffer\r\n end\r\n\r\n SendOffset = SendCursor\r\n SendCursor += Bytes\r\n \r\n return SendOffset\r\nend\r\n\r\nlocal Types = {}\r\nlocal Calls = table.create(256)\r\n\r\nlocal Events: any = {\r\n Reliable = table.create(256),\r\n Unreliable = table.create(256)\r\n}\r\n\r\nlocal Queue: any = {\r\n Reliable = table.create(256),\r\n Unreliable = table.create(256)\r\n}\r\n\r\n' end - function __DARKLUA_BUNDLE_MODULES.j() + function __DARKLUA_BUNDLE_MODULES.n() return 'local ReplicatedStorage = game:GetService("ReplicatedStorage")\r\nlocal RunService = game:GetService("RunService")\r\n\r\nif not RunService:IsClient() then\r\n error("Client network module can only be required from the client.")\r\nend\r\n\r\nlocal Reliable: RemoteEvent = ReplicatedStorage:WaitForChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent\r\nlocal Unreliable: UnreliableRemoteEvent = ReplicatedStorage:WaitForChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent\r\n\r\n-- SPLIT --\r\nlocal function StepReplication()\r\n if SendCursor <= 0 then\r\n return\r\n end\r\n\r\n local Buffer = buffer.create(SendCursor)\r\n buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)\r\n Reliable:FireServer(Buffer, SendInstances)\r\n\r\n SendCursor = 0\r\n SendOffset = 0\r\n buffer.fill(SendBuffer, 0, 0)\r\n table.clear(SendInstances)\r\nend\r\n' end - function __DARKLUA_BUNDLE_MODULES.k() + function __DARKLUA_BUNDLE_MODULES.o() return 'local Players = game:GetService("Players")\r\nlocal ReplicatedStorage = game:GetService("ReplicatedStorage")\r\nlocal RunService = game:GetService("RunService")\r\n\r\nif not RunService:IsServer() then\r\n error("Server network module can only be required from the server.")\r\nend\r\n\r\nlocal Reliable: RemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent\r\nif not Reliable then\r\n local RemoteEvent = Instance.new("RemoteEvent")\r\n RemoteEvent.Name = "BLINK_RELIABLE_REMOTE"\r\n RemoteEvent.Parent = ReplicatedStorage\r\n Reliable = RemoteEvent\r\nend\r\n\r\nlocal Unreliable: UnreliableRemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent\r\nif not Unreliable then\r\n local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")\r\n UnreliableRemoteEvent.Name = "BLINK_UNRELIABLE_REMOTE"\r\n UnreliableRemoteEvent.Parent = ReplicatedStorage\r\n Unreliable = UnreliableRemoteEvent\r\nend\r\n\r\n-- SPLIT --\r\nlocal PlayersMap: {[Player]: BufferSave} = {}\r\n\r\nPlayers.PlayerRemoving:Connect(function(Player)\r\n PlayersMap[Player] = nil\r\nend)\r\n\r\nlocal function StepReplication()\r\n for Player, Send in PlayersMap do\r\n if Send.Cursor <= 0 then\r\n continue\r\n end\r\n\r\n local Buffer = buffer.create(Send.Cursor)\r\n buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor)\r\n Reliable:FireClient(Player, Buffer, Send.Instances)\r\n\r\n Send.Cursor = 0\r\n buffer.fill(Send.Buffer, 0, 0)\r\n table.clear(Send.Instances)\r\n end\r\nend\r\n' end - function __DARKLUA_BUNDLE_MODULES.l() - local Blocks = __DARKLUA_BUNDLE_MODULES.load('f') - local Parser = __DARKLUA_BUNDLE_MODULES.load('e') - local Builder = __DARKLUA_BUNDLE_MODULES.load('g') - local Prefabs = __DARKLUA_BUNDLE_MODULES.load('h') + function __DARKLUA_BUNDLE_MODULES.p() + local Blocks = __DARKLUA_BUNDLE_MODULES.load('j') + local Parser = __DARKLUA_BUNDLE_MODULES.load('g') + local Builder = __DARKLUA_BUNDLE_MODULES.load('k') + local Prefabs = __DARKLUA_BUNDLE_MODULES.load('l') local Sources = { - Base = __DARKLUA_BUNDLE_MODULES.load('i'), - Client = string.split(__DARKLUA_BUNDLE_MODULES.load('j'), '-- SPLIT --'), - Server = string.split(__DARKLUA_BUNDLE_MODULES.load('k'), '-- SPLIT --'), + Base = __DARKLUA_BUNDLE_MODULES.load('m'), + Client = string.split(__DARKLUA_BUNDLE_MODULES.load('n'), '-- SPLIT --'), + Server = string.split(__DARKLUA_BUNDLE_MODULES.load('o'), '-- SPLIT --'), } local DIRECTIVES = [[--!strict @@ -1844,7 +2459,7 @@ do --!nolint LocalShadow --#selene: allow(shadowing) ]] - local VERSION_HEADER = `-- File generated by Blink v{'0.6.4' or '0.0.0'} (https://github.com/1Axen/Blink)\n-- This file is not meant to be edited\n\n` + local VERSION_HEADER = `-- File generated by Blink v{'0.7.0' or '0.0.0'} (https://github.com/1Axen/Blink)\n-- This file is not meant to be edited\n\n` local EVENT_BODY = 'RecieveCursor = 0\r\nRecieveBuffer = Buffer\r\nRecieveInstances = Instances\r\nRecieveInstanceCursor = 0\r\nlocal Size = buffer.len(RecieveBuffer)' local RELIABLE_BODY = { Header = @@ -2252,8 +2867,15 @@ do local Value = Declaration.Value local Optional = Value.Optional local Variable = Variable or 'Value' - local IsInstance = (Declaration.Type == 'TypeDeclaration' and (((Value).Primitive)) == 'Instance') + local IsInstance, IsUnknown + + if Declaration.Type == 'TypeDeclaration' then + local Primitive = (Value).Primitive + IsUnknown = (Primitive == 'unknown') + IsInstance = (Primitive == 'Instance') + Optional = if IsUnknown then true else Optional + end if not true then Read:Comment(`{Variable}: {Value.Identifier}`) Write:Comment(`{Variable}: {Value.Identifier}`) @@ -2434,19 +3056,29 @@ do end end - return function(FileContext, AbstractSyntaxTree, UserOptions) - local Imports = Builder.new() + local Generator = {} + function Generator.Reset() Events = Builder.new() UserTypes = Builder.new() LuauTypes = Builder.new() Return = Builder.new() - Context = FileContext - Options = UserOptions Channels.Reliable.Count = 0 Channels.Unreliable.Count = 0 Channels.Reliable.Listening = false Channels.Unreliable.Listening = false + end + function Generator.Generate( + FileContext, + AbstractSyntaxTree, + UserOptions + ) + local Imports = Builder.new() + + Generator.Reset() + + Context = FileContext + Options = UserOptions local Signal = Context == 'Client' and 'OnClientEvent' or 'OnServerEvent' local Arguments = (Context == 'Server' and 'Player: Player, ' or '') .. 'Buffer: buffer, Instances: {Instance}' @@ -2502,66 +3134,62 @@ do return DIRECTIVES .. VERSION_HEADER .. (if not true then string.format(DEBUUG_GLOBALS, string.lower(Context), string.lower(Context))else'') .. Source[1] .. Imports.Dump() .. Sources.Base .. Events.Dump() .. '\n' .. LuauTypes.Dump() .. '\n' .. UserTypes.Dump() .. Source[2] .. '\n' .. Replication.Dump() .. '\n' .. Reliables:Unwrap() .. '\n\n' .. Unreliables:Unwrap() .. '\n' .. `\nreturn \{\n{Return.Dump()}}` end + function Generator.GenerateTypeDefinitions( + FileContext, + AbstractSyntaxTree, + UserOptions + ) + Generator.Reset() + + Context = FileContext + Options = UserOptions + + Generators.AbstractSyntaxTree(AbstractSyntaxTree.Value) + + return LuauTypes.Dump() .. `\nreturn true` + end + + return Generator end end local ServerStorage = game:GetService('ServerStorage') local UserInputService = game:GetService('UserInputService') +local ContextActionService = game:GetService('ContextActionService') local TweenService = game:GetService('TweenService') local TextService = game:GetService('TextService') local RunService = game:GetService('RunService') local Selection = game:GetService('Selection') local State = __DARKLUA_BUNDLE_MODULES.load('a') -local Lexer = __DARKLUA_BUNDLE_MODULES.load('c') -local Parser = __DARKLUA_BUNDLE_MODULES.load('e') -local Generator = __DARKLUA_BUNDLE_MODULES.load('l') -local EDITOR_COLORS = { - Text = Color3 .fromHex('#FFFFFF'), - Keyword = Color3 .fromHex('#6796E6'), - Primitive = Color3 .fromHex('#4EC9B0'), - Identifier = Color3 .fromHex('#9CDCFE'), - Class = Color3 .fromHex('#B5CEA8'), - Number = Color3 .fromHex('#B5CEA8'), - String = Color3 .fromHex('#ADF195'), - Bracket = Color3 .fromHex('#FFFFFF'), - Boolean = Color3 .fromHex('#B5CEA8'), - Error = Color3 .fromHex('#FF5050'), -} +local Editor = __DARKLUA_BUNDLE_MODULES.load('i') +local Parser = __DARKLUA_BUNDLE_MODULES.load('g') +local Generator = __DARKLUA_BUNDLE_MODULES.load('p') local FILES_FOLDER = 'BLINK_CONFIGURATION_FILES' local TEMPLATE_FILE = { Name = 'Template', - Source = 'type Example = u8\r\nevent MyEvent = {\r\n From = Server,\r\n Type = Reliable,\r\n Call = SingleSync,\r\n Data = Example\r\n}', + Source = table.concat({ + 'type Example = u8', + 'event MyEvent = {', + '\tFrom = Server,', + '\tType = Reliable,', + '\tCall = SingleSync,', + '\tData = Example', + '}', + }, '\n'), } local ERROR_WIDGET = DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, false, true, 500, 240, 300, 240) local EDITOR_WIDGET = DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Left, false, false, 360, 400, 300, 400) local SAVE_COLOR = Color3 .fromRGB(0, 100, 0) local BUTTON_COLOR = Color3 .fromRGB(30, 30, 30) local EXPAND_TWEEN = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut) -local SCROLL_LINES = 2 -local CURSOR_BLINK_RATE = 0.5 -local FIELDS_TYPES = { - struct = true, - event = true, - ['function'] = true, -} -local BRACKETS_TYPES = { - OpenBrackets = true, - CloseBrackets = true, - OpenCurlyBrackets = true, - CloseCurlyBrackets = true, - OpenSquareBrackets = true, - CloseSquareBrackets = true, -} local Toolbar = plugin:CreateToolbar('Blink Suite') local EditorButton = Toolbar:CreateButton('Editor', 'Opens the configuration editor', 'rbxassetid://16468561002') EditorButton.ClickableWhenViewportHidden = true local Template = script.Widget -local LineTemplate = Template.Editor.Lines.Line:Clone() local FileTemplate = Template.Side.Content.Top.Files.File:Clone() -Template.Editor.Lines.Line:Destroy() Template.Side.Content.Top.Files.File:Destroy() local ErrorWidget = plugin:CreateDockWidgetPluginGui('Generation Error', ERROR_WIDGET) @@ -2581,13 +3209,12 @@ EditorWidget.Name = 'Blink Editor' EditorWidget.Title = 'Configuration Editor' EditorWidget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling -local EditorInterface = Template:Clone() +local EditorInterface = script.Widget EditorInterface.Size = UDim2 .fromScale(1, 1) EditorInterface.Parent = EditorWidget local Side = EditorInterface.Side -local Editor = EditorInterface.Editor local Overlay = EditorInterface.Overlay local Content = Side.Content local Expand = Side.Expand @@ -2598,18 +3225,13 @@ local Search = Top.Title.Search.Input local Prompt = Bottom.Save local Buttons = Bottom.Buttons local GeneratePrompt = Bottom.Generate -local GenerateButtons = (GeneratePrompt:FindFirstChild('Buttons')) -local Hint = (GeneratePrompt:FindFirstChild('Hint')) -local Generate = (GenerateButtons:FindFirstChild('Generate')) -local CancelGenerate = (GenerateButtons:FindFirstChild('Cancel')) -local Input = (Prompt:FindFirstChild('Input')) +local GenerateButtons = GeneratePrompt.Buttons +local Hint = GeneratePrompt.Hint +local Generate = GenerateButtons.Generate +local CancelGenerate = GenerateButtons.Cancel +local Input = Prompt.Input local Save = Buttons.Save local Cancel = Buttons.Cancel -local TextInput = Editor.Text.Input -local TextCursor = Editor.Text.Cursor -local TextDisplay = Editor.Text.Display -local NumberLines = Editor.Lines -local TextLineHeight = (TextInput.TextSize + 3) local Tweens = { Editor = { Expand = { @@ -2628,16 +3250,12 @@ local Tweens = { }, }, } -local SourceLexer = Lexer.new('Highlighting') local Saving = false -local Scroll = State.new(0) local Expanded = State.new(false) local Selected = State.new(nil) local Generating = State.new(nil) local WasOpened = false -local Lines = 0 local Editing -local CursorTimer = 0 local function ShowError(Error) local Start = string.find(Error, '{Text}` -end -local function GetEditorSource() - return TextInput.ContentText -end -local function DoLexerPass() - local Source = GetEditorSource() - local Display = '' - - SourceLexer:Initialize(Source) - - local Keyword = 'none' - local IsField = false - - while true do - local Success, Error, Token = pcall(function() - return nil, SourceLexer:GetNextToken() - end) - - if not Success then - warn(`Lexer error: {Error}`) - - break - end - if not Token then - break - end - - local Type = Token.Type - local Value = Token.Value - - if Type == 'Keyword' then - Keyword = Value - - Display ..= WrapColor(Value, EDITOR_COLORS.Keyword) - elseif Type == 'Primitive' then - Display ..= WrapColor(Value, EDITOR_COLORS.Primitive) - elseif Type == 'Identifier' then - Display ..= WrapColor(Value, IsField and EDITOR_COLORS.Text or EDITOR_COLORS.Identifier) - elseif Type == 'Array' or Type == 'Range' then - local Single = string.match(Value, '%[(%d+)%]') or string.match(Value, '%((%d+)%)') - - if Single then - Display ..= string.gsub(Value, Single, WrapColor(Single, EDITOR_COLORS.Number)) - - continue - end - - local Lower = string.match(Value, '%[(%d+)') or string.match(Value, '%((%d+)') - local Upper = string.match(Value, '(%d+)%]') or string.match(Value, '(%d+)%)') - - if Lower and Upper then - Display ..= `{string.sub(Value, 1, 1)}{WrapColor(Lower, EDITOR_COLORS.Number)}..{WrapColor(Upper, EDITOR_COLORS.Number)}{string.sub(Value, #Value, #Value)}` - - continue - end - - Display ..= Value - elseif BRACKETS_TYPES[Type] then - if Type == 'CloseCurlyBrackets' then - IsField = false - end - - Display ..= WrapColor(Value, EDITOR_COLORS.Bracket) - elseif Type == 'Class' then - Display ..= `({WrapColor(string.sub(Value, 2, #Value - 1), EDITOR_COLORS.Class)})` - elseif Type == 'String' then - Display ..= WrapColor(Value, EDITOR_COLORS.String) - elseif Type == 'Boolean' then - Display ..= WrapColor(Value, EDITOR_COLORS.Boolean) - elseif Type == 'Unknown' then - IsField = false - - Display ..= WrapColor(Value, EDITOR_COLORS.Error) - else - Display ..= Value - end - if Type == 'Whitespace' then - continue - end - - IsField = (Type == 'Comma' or Type == 'OpenCurlyBrackets') and FIELDS_TYPES[Keyword] - end - - TextDisplay.Text = Display -end -local function ScrollEditor(Direction) - local Value = Scroll:Get() - local Maximum = math.max(1, (TextInput.TextBounds.Y - Editor.AbsoluteSize.Y) // TextLineHeight + SCROLL_LINES) - - Scroll:Set(math.clamp(Value + (Direction * SCROLL_LINES), 0, Maximum)) -end -local function OnScroll(Value) - Editor.Position = UDim2 .fromOffset(Editor.Position.X.Offset, TextLineHeight * --Value) -end local function OnSearch(PressedEnter) if not PressedEnter then return @@ -2906,54 +3428,6 @@ local function OnSearch(PressedEnter) Frame.Visible = (string.find(Frame.Name, Query) ~= nil) end end -local function OnCursorMoved() - local CursorPosition = TextInput.CursorPosition - - if CursorPosition == -1 then - TextCursor.Visible = false - - return - end - - local Size = TextInput.TextSize - local Text = string.sub(TextInput.ContentText, 1, CursorPosition - 1) - local Slices = string.split(Text, '\n') - local NewLines = math.max(#Slices, 1) - local Line = Slices[#Slices] or '' - local GetTextBoundsParams = Instance.new('GetTextBoundsParams') - - GetTextBoundsParams.Text = Line - GetTextBoundsParams.Size = Size - GetTextBoundsParams.Font = TextInput.FontFace - - local TextBounds = TextService:GetTextBoundsAsync(GetTextBoundsParams) - - TextCursor.Size = UDim2 .fromOffset(2, Size) - TextCursor.Position = UDim2 .fromOffset(TextBounds.X - 1, TextLineHeight * (NewLines - 1)) -end -local function OnSourceChanged() - local ContentText = TextInput.ContentText - - ContentText = string.gsub(ContentText, '\r', '') - TextInput.Text = ContentText - - local ContentLines = #string.split(ContentText, '\n') - - if ContentLines ~= Lines then - Lines = ContentLines - - ClearChildrenWhichAre(NumberLines, 'TextLabel') - - for Index = 1, Lines do - local Line = LineTemplate:Clone() - - Line.Text = tostring(Index) - Line.Parent = NumberLines - end - end - - DoLexerPass() -end local function OnSaveCompleted() Saving = false Prompt.Visible = false @@ -2968,7 +3442,7 @@ local function OnSaveActivated() return end - SaveFile(Name, GetEditorSource()) + SaveFile(Name, Editor.GetSource()) LoadFiles() OnSaveCompleted() @@ -2994,23 +3468,8 @@ end local function OnEditorButtonClicked() EditorWidget.Enabled = not EditorWidget.Enabled end -local function OnPreRender(DeltaTime) - if TextInput.CursorPosition == -1 then - return - end - CursorTimer += DeltaTime - - if CursorTimer < CURSOR_BLINK_RATE then - return - end - - CursorTimer -= CURSOR_BLINK_RATE - - TextCursor.Visible = not TextCursor.Visible -end - -Scroll:OnChange(OnScroll) +Editor.Initialize() Expanded:OnChange(function(Value) PlayTweens(Value and Tweens.Editor.Expand or Tweens.Editor.Retract) end) @@ -3051,14 +3510,3 @@ Save.Activated:Connect(OnSaveActivated) Cancel.Activated:Connect(OnSaveCompleted) Expand.Activated:Connect(OnExpandActivated) EditorButton.Click:Connect(OnEditorButtonClicked) -TextInput:GetPropertyChangedSignal('Text'):Connect(OnSourceChanged) -TextInput:GetPropertyChangedSignal('CursorPosition'):Connect(OnCursorMoved) -TextInput.InputChanged:Connect(function(InputObject) - if not Editing then - return - end - if InputObject.UserInputType == Enum.UserInputType.MouseWheel then - ScrollEditor(-InputObject.Position.Z) - end -end) -RunService.PreRender:Connect(OnPreRender) diff --git a/plugin/src/Editor/Styling/AutoIndent.luau b/plugin/src/Editor/Styling/AutoIndent.luau new file mode 100644 index 0000000..caf6933 --- /dev/null +++ b/plugin/src/Editor/Styling/AutoIndent.luau @@ -0,0 +1,76 @@ +local Hook = {} + +local IndentKeywords = { + "{\n" +} + +local function ShouldAutoIndent(Text: string, Cursor: number): boolean + for Index, Keyword in IndentKeywords do + local Position = (Cursor - #Keyword) + if Position <= 0 then + continue + end + + local Previous = string.sub(Text, Position, Cursor - 1) + if Previous == Keyword then + return true + end + end + + return false +end + +local function GetLineIndentation(Line: string): number + return #(string.match(Line, "^\t*") :: string) +end + +local function GetCurrentLine(Text: string, Cursor: number): (number, {string}) + local Line = 0 + local Position = 0 + local Slices = string.split(Text, "\n") + + for Index, Slice in Slices do + Position += (#Slice + 1) + if Cursor <= Position then + Line = Index + break + end + end + + return Line, Slices +end + +function Hook.OnSourceChanged(Text: string, Cursor: number, Gain: number): (string, number) + if Gain ~= 1 then + return Text, Cursor + end + + local CanIndent = false + local AdditionalIndent = 0 + local Line, Lines = GetCurrentLine(Text, Cursor) + + local Current = Lines[Line] + local Previous = Lines[Line - 1] + local JustReached = (Previous and Current == "") + + if ShouldAutoIndent(Text, Cursor) then + CanIndent = true + AdditionalIndent = 1 + elseif JustReached then + if GetLineIndentation(Previous) > 0 then + CanIndent = true + end + end + + if not CanIndent then + return Text, Cursor + end + + --> Update text and cursor + local Indentation = GetLineIndentation(Previous) + AdditionalIndent + Text = string.sub(Text, 1, Cursor - 1) .. string.rep("\t", Indentation) .. string.sub(Text, Cursor) + + return Text, Cursor + Indentation +end + +return Hook \ No newline at end of file diff --git a/plugin/src/Editor/init.luau b/plugin/src/Editor/init.luau new file mode 100644 index 0000000..3eaa34e --- /dev/null +++ b/plugin/src/Editor/init.luau @@ -0,0 +1,548 @@ +--!strict + +-- ******************************* -- +-- AX3NX / AXEN -- +-- ******************************* -- + +---- Services ---- + +local TextService = game:GetService("TextService") +local RunService = game:GetService("RunService") + +---- Imports ---- + +local Table = require("../Table") +local State = require("../State") + +local Lexer = require("../../../src/Lexer") +local Parser = require("../../../src/Parser") +local Settings = require("../../../src/Settings") + +local Error = require("../../../src/Modules/Error") + +local StylingHooks: {{OnSourceChanged: (Text: string, Cursor: number, Gain: number) -> (string, number)}} = { + require("./Styling/AutoIndent"), +} + +---- Settings ---- + +local ICONS = { + Event = "rbxassetid://16506730516", + Field = "rbxassetid://16506725096", + Snippet = "rbxassetid://16506712161", + Keyword = "rbxassetid://16506695241", + Variable = "rbxassetid://16506719167", + Primitive = "rbxassetid://16506695241" +} + +local COLORS = { + Text = Color3.fromHex("#FFFFFF"), + Keyword = Color3.fromHex("#6796E6"), + Primitive = Color3.fromHex("#4EC9B0"), + Identifier = Color3.fromHex("#9CDCFE"), + + Class = Color3.fromHex("#B5CEA8"), + Number = Color3.fromHex("#B5CEA8"), + String = Color3.fromHex("#ADF195"), + Bracket = Color3.fromHex("#FFFFFF"), + Boolean = Color3.fromHex("#B5CEA8"), + + Error = Color3.fromHex("#FF5050"), + + Complete = Color3.fromHex("#04385f") +} + +local SYMBOLS: {[string]: {string}} = { + Event = {}, + Field = {}, + Snippet = {}, + Keyword = Table.GetDictionaryKeys(Settings.Keywords), + Variable = {}, + Primitive = Table.GetDictionaryKeys(Settings.Primtives) +} + +local FIELDS = { + struct = true, + event = true, + ["function"] = true, +} + +local BRACKETS = { + OpenBrackets = true, + CloseBrackets = true, + OpenCurlyBrackets = true, + CloseCurlyBrackets = true, + OpenSquareBrackets = true, + CloseSquareBrackets = true +} + +local SCROLL_LINES = 2 +local CURSOR_BLINK_RATE = 0.5 + +---- Constants ---- + +local Editor = {} + +--> Interface Instances +local Container: typeof(script.Parent.Widget) = script.Widget +local EditorContainer = Container.Editor +local CompletionContainer = Container.Completion + +local TextContainer = EditorContainer.Text +local LinesContainer = EditorContainer.Lines + +local Input = TextContainer.Input +local Cursor = TextContainer.Cursor +local Display = TextContainer.Display +local Selection = TextContainer.Selection + +local LineTemplate = LinesContainer.Line:Clone() +local SelectionTemplate = Selection.Line:Clone() +local CompletionTemplate = CompletionContainer.Option:Clone() + +local TextSize = Input.TextSize +local TextHeight = (Input.TextSize + 3) + +--> Objects +local SourceLexer = Lexer.new("Highlighting") +local SourceParser = Parser.new() + +---- Variables ---- + +local Lines = State.new(0) +local Scroll = State.new(0) +local Errors: State.Class<{Error.Label}> = State.new({}) + +local CursorTimer = 0 +local PreviousText = Input.Text + +---- Private Functions ---- + +local function ScrollTowards(Direction: number) + local Value = Scroll:Get() + local Maximum = math.max(1, (Input.TextBounds.Y - EditorContainer.AbsoluteSize.Y) // TextHeight + SCROLL_LINES) + Scroll:Set(math.clamp(Value + (Direction * SCROLL_LINES), 0, Maximum)) +end + +local function WrapColor(Text: string, Color: Color3): string + return `{Text}` +end + +local function ClearChildrenWhichAre(Parent: Instance, Class: string) + for Index, Child in Parent:GetChildren() do + if Child:IsA(Class) then + Child:Destroy() + end + end +end + +local function DoLexerPass() + local Source = Input.Text + local RichText = "" + + --> Initiialize lexer + SourceLexer:Initialize(Source) + + --> Highlight state + local Keyword = "none" + local IsField = false + --local IsAssigning = false + + while true do + local Success, Error, Token = pcall(function() + return nil, SourceLexer:GetNextToken() + end) + + if not Success then + warn(`Lexer error: {Error}`) + break + end + + if not Token then + break + end + + local Type: Lexer.Types = Token.Type + local Value = Token.Value + + if Type == "Keyword" then + Keyword = Value + RichText ..= WrapColor(Value, COLORS.Keyword) + elseif Type == "Primitive" then + RichText ..= WrapColor(Value, COLORS.Primitive) + elseif Type == "Identifier" then + RichText ..= WrapColor(Value, IsField and COLORS.Text or COLORS.Identifier) + elseif Type == "Array" or Type == "Range" then + --> Exact size array/range + local Single = string.match(Value, "%[(%d+)%]") or string.match(Value, "%((%d+)%)") + if Single then + RichText ..= string.gsub(Value, Single, WrapColor(Single, COLORS.Number)) + continue + end + + --> Variable size array/range + local Lower = string.match(Value, "%[(%d+)") or string.match(Value, "%((%d+)") + local Upper = string.match(Value, "(%d+)%]") or string.match(Value, "(%d+)%)") + if Lower and Upper then + RichText ..= `{string.sub(Value, 1, 1)}{WrapColor(Lower, COLORS.Number)}..{WrapColor(Upper, COLORS.Number)}{string.sub(Value, #Value, #Value)}` + continue + end + + RichText ..= Value + elseif BRACKETS[Type] then + if Type == "CloseCurlyBrackets" then + IsField = false + end + + RichText ..= WrapColor(Value, COLORS.Bracket) + elseif Type == "Class" then + RichText ..= `({WrapColor(string.sub(Value, 2, #Value - 1), COLORS.Class)})` + elseif Type == "String" then + RichText ..= WrapColor(Value, COLORS.String) + elseif Type == "Boolean" then + RichText ..= WrapColor(Value, COLORS.Boolean) + elseif Type == "Unknown" then + --> Reset all state + IsField = false + RichText ..= WrapColor(Value, COLORS.Error) + else + RichText ..= Value + end + + if Type == "Whitespace" then + continue + end + + IsField = (Type == "Comma" or Type == "OpenCurlyBrackets") and FIELDS[Keyword] + --IsAssigning = (Type == "Assign") + end + + Display.Text = RichText +end + +--[[local function DoCompletionPass() + local Text = TextInput.ContentText + local Lines = string.split(Text, "\n") + local CursorPosition = TextInput.CursorPosition + + if CursorPosition == -1 then + return + end + + local Offset = 0 + for _, Line in Lines do + local Start = Offset + Offset += (#Line + 1) + if Offset < CursorPosition then + continue + end + + local Substring = string.sub(Line, 1, math.max(1, CursorPosition - Start)) + local Words = string.split(Substring, " ") + + local Size = #Words + local Token = Words[Size] + local Previous = Words[Size - 1] + + print(`Token: {Token}`) + + --> Match categories + local Items: {Prediction} = {} + for Category, Symbols in EDITOR_AUTOCOMPLETE do + if Token == "" then + break + end + + local Result: string?; + for _, Symbol in Symbols do + if Token == Symbol then + break + end + + if string.find(Symbol, Token, 1, true) then + Result = Symbol + break + end + end + + --> Keywords should not follow assignment + if Category == "Keyword" and Previous == "=" then + continue + end + + --> Primitives should only follow assignment + if Category == "Primitive" and Previous ~= "=" then + continue + end + + if Result then + table.insert(Items, { + Icon = EDITOR_ICONS[Category], + Prediction = Result + }) + end + + print(`{Category}: {Result}`) + end + + Predictions:Set({ + Item = math.min( + Predictions:Get().Item, + math.max(1, #Items) + ), + Predictions = Items + }) + + break + end +end]] + +---- Event Functions ----- + +local function OnErrorEmitted(Labels) + Errors:Set(Labels) +end + +local function OnPreRender(DeltaTime: number) + if Input.CursorPosition == -1 then + return + end + + CursorTimer += DeltaTime + if CursorTimer < CURSOR_BLINK_RATE then + return + end + + CursorTimer -= CURSOR_BLINK_RATE + Cursor.Visible = not Cursor.Visible +end + +--> State functions +local function OnLinesChanged(Value: number) + ClearChildrenWhichAre(LinesContainer, "TextLabel") + for Index = 1, Value do + local Line = LineTemplate:Clone() + Line.Text = tostring(Index) + Line.Parent = LinesContainer + end +end + +local function OnScrollChanged(Value: number) + EditorContainer.Position = UDim2.fromOffset(EditorContainer.Position.X.Offset, TextHeight * -Value) +end + +--[[local function OnPredictionsChanged() + local Value = Predictions:Get() + if #Value.Predictions == 0 then + Completion.Visible = false + return + end + + Completion.Visible = true + ClearChildrenWhichAre(Completion, "Frame") + + for Index, Item in Value.Predictions do + local Frame = CompletionItem:Clone() + Frame.Icon.Image = Item.Icon + Frame.Text.Text = Item.Prediction + + if Index == Value.Item then + Frame.BackgroundColor3 = EDITOR_COLORS.Complete + end + + Frame.Parent = Completion + end +end]] + +--> RBXScriptSignal functions +local function OnCursorPositionChanged() + local CursorPosition = Input.CursorPosition + if CursorPosition == -1 then + Cursor.Visible = false + return + end + + local Text = string.sub(Input.Text, 1, CursorPosition - 1) + local Slices = string.split(Text, "\n") + + --> Calculate current end line position + local Line = Slices[#Slices] or "" + local GetTextBoundsParams = Instance.new("GetTextBoundsParams") + GetTextBoundsParams.Text = Line + GetTextBoundsParams.Size = TextSize + GetTextBoundsParams.Font = Input.FontFace + local TextBounds = TextService:GetTextBoundsAsync(GetTextBoundsParams) + + --> Update cursor size and position + Cursor.Size = UDim2.fromOffset(2, TextSize) + Cursor.Position = UDim2.fromOffset(TextBounds.X - 1, TextHeight * (#Slices - 1)) + + --> Perform auto completion pass + --[[DoCompletionPass() + + --> Update completion position + local AbsoluteSize = EditorInterface.AbsoluteSize + local AbsolutePosition = TextCursor.AbsolutePosition + + Completion.Position = UDim2.fromOffset( + math.clamp(AbsolutePosition.X, 0, (AbsoluteSize.X - Completion.Size.X.Offset) - 10), + AbsolutePosition.Y + TextSize + )]] +end + +local function OnSelectionChanged() + --> Clear previous selection + ClearChildrenWhichAre(Selection, "Frame") + + --> Nothing is selected + local CursorPosition = Input.CursorPosition + local SelectionStart = Input.SelectionStart + if CursorPosition == -1 or SelectionStart == -1 then + return + end + + local Start = math.min(CursorPosition, SelectionStart) + local Finish = math.max(CursorPosition, SelectionStart) + + local Text = string.sub(Input.ContentText, 1, Finish - 1) + local Slices = string.split(Text, "\n") + + local Offset = 0 + for Index, Slice in Slices do + local First = Offset + Offset += (#Slice + 1) + + local Line = SelectionTemplate:Clone() + local Fill = Line.Fill + + if Offset < Start then + Fill.BackgroundTransparency = 1 + Line.Parent = Selection + continue + end + + --> Calculate selection bounds + local Substring = Slice + if First < Start then + Substring = string.sub(Slice, (Start - First), #Slice) + elseif Index == #Slices then + Substring = string.sub(Slice, 1, math.max(1, Finish - First)) + end + + --> Empty lines should appear as one space thick selections + if Substring == "" then + Substring = " " + end + + local SelectionBoundsParams = Instance.new("GetTextBoundsParams") + SelectionBoundsParams.Text = Substring + SelectionBoundsParams.Size = TextSize + SelectionBoundsParams.Font = Input.FontFace + + local SelectionBounds = TextService:GetTextBoundsAsync(SelectionBoundsParams) + Fill.Size = UDim2.new(0, SelectionBounds.X, 1, 0) + + --> Calculate selection offset + if Start > First then + local Prefix = string.sub(Slice, 1, (Start - First) - 1) + if Prefix ~= "" then + local OffsetBoundsParams = Instance.new("GetTextBoundsParams") + OffsetBoundsParams.Text = Prefix + OffsetBoundsParams.Size = TextSize + OffsetBoundsParams.Font = Input.FontFace + + local OffsetBounds = TextService:GetTextBoundsAsync(OffsetBoundsParams) + Fill.Position = UDim2.new(0, OffsetBounds.X, 0, 0) + end + end + + Line.Parent = Selection + end +end + +local function OnSourceChanged() + --> Pressing enter inserts a carriage return (ROBLOX please fix) + local Text = Input.Text + local Gain = math.sign(#Text - #PreviousText) + + local NoReturnCarriage = string.gsub(Text, "\r", "") + if NoReturnCarriage ~= Text then + Input.Text = NoReturnCarriage + return + end + + PreviousText = Text + + --> Update line counter + local SourceLines = #string.split(Text, "\n") + if SourceLines ~= Lines:Get() then + Lines:Set(SourceLines) + end + + --> Run styling hooks + local FinalText, FinalCursor = Text, Input.CursorPosition + for Index, Hook in StylingHooks do + FinalText, FinalCursor = Hook.OnSourceChanged(FinalText, FinalCursor, Gain) + end + + --> If a styling hook altered the final text then don't do a lexer pass as OnSourcChanged will be called again + if FinalText ~= Text then + Input.CursorPosition = -1 + Input.Text = FinalText + Input.CursorPosition = FinalCursor + + return + end + + --> Perform lexer pass + DoLexerPass() +end + +---- Public Functions ---- + +function Editor.GetSource(): string + return Input.Text +end + +function Editor.SetSource(Source: string) + Scroll:Set(0) + Input.Text = Source +end + +---- Initialization ---- + +function Editor.Initialize() + --> Remove templates + Selection.Line:Destroy() + LinesContainer.Line:Destroy() + CompletionContainer.Option:Destroy() + + --> Sort auto complete symbols + for _, Symbols in SYMBOLS do + table.sort(Symbols, function(a, b) + return #a < #b + end) + end + + --> Connections + Error.OnEmit = OnErrorEmitted + + Lines:OnChange(OnLinesChanged) + Scroll:OnChange(OnScrollChanged) + + Input:GetPropertyChangedSignal("Text"):Connect(OnSourceChanged) + Input:GetPropertyChangedSignal("SelectionStart"):Connect(OnSelectionChanged) + Input:GetPropertyChangedSignal("CursorPosition"):Connect(OnSelectionChanged) + Input:GetPropertyChangedSignal("CursorPosition"):Connect(OnCursorPositionChanged) + Input.InputChanged:Connect(function(InputObject) + if InputObject.UserInputType == Enum.UserInputType.MouseWheel then + ScrollTowards(-InputObject.Position.Z) + end + end) + + RunService.PreRender:Connect(OnPreRender) +end + +---- Connections ---- + +return Editor \ No newline at end of file diff --git a/plugin/src/Error.rbxmx b/plugin/src/Error.rbxmx new file mode 100644 index 0000000..5617198 --- /dev/null +++ b/plugin/src/Error.rbxmx @@ -0,0 +1,188 @@ + + true + null + nil + + + false + + 0 + 0 + + + true + 0 + + 0.156862751 + 0.156862751 + 0.156862751 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Error + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + false + 3 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxasset://fonts/families/Ubuntu.json + 400 + + + true + 0 + 1 + + + -1 + Text + null + null + null + null + + 0 + 0 + 0 + 0 + + true + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + + Label + + 1 + 1 + 1 + + 0 + false + 16 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 0 + true + 1 + + + + + + 0 + false + UIPadding + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + -1 + + + + + \ No newline at end of file diff --git a/plugin/src/Table.luau b/plugin/src/Table.luau new file mode 100644 index 0000000..7b56e1b --- /dev/null +++ b/plugin/src/Table.luau @@ -0,0 +1,38 @@ +local Table = {} + +function Table.MergeArrays(a: {any}, b: {any}): {any} + local Array = table.create(#a + #b) + + for _, Element in a do + table.insert(Array, Element) + end + + for _, Element in b do + table.insert(Array, Element) + end + + return Array +end + +function Table.MergeDictionaries(a: {[any]: any}, b: {[any]: any}): {[any]: any} + local Dictionary = table.clone(a) + for Key, Value in b do + if Dictionary[Key] then + warn(`Key "{Key}" already exists in the first dictionary.`) + continue + end + + Dictionary[Key] = Value + end + return Dictionary +end + +function Table.GetDictionaryKeys(Dictionary: {[any]: any}): {any} + local Keys = {} + for Key in Dictionary do + table.insert(Keys, Key) + end + return Keys +end + +return Table \ No newline at end of file diff --git a/plugin/src/Widget.rbxmx b/plugin/src/Widget.rbxmx new file mode 100644 index 0000000..92df568 --- /dev/null +++ b/plugin/src/Widget.rbxmx @@ -0,0 +1,4013 @@ + + true + null + nil + + + false + + 0 + 0 + + + true + 0 + + 0.156862751 + 0.156862751 + 0.156862751 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + true + 0 + Widget + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 0.176470593 + 0.176470593 + 0.176470593 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Side + null + null + null + null + + 0 + -208 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 250 + 1 + 0 + + 0 + -1 + 0 + + true + 2 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + rbxassetid://5051528605 + + 0 + 0 + 0 + + + 0 + 0 + + + 0 + 0 + + 0.699999988 + true + 0 + Shadow + null + null + null + null + + 1 + 0 + 0 + 0 + + 0 + null + 0 + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 8 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + + 1 + 1 + 1 + + 1 + true + 0 + Content + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + + true + 1 + + + + + 0 + false + Padding + + 0 + 10 + + + 0 + 10 + + + 0 + 10 + + + 0 + 10 + + -1 + + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Top + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Title + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 22 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Search + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 200 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 0 + + 0 + + 0.0980392173 + 0.0980392173 + 0.0980392173 + + false + true + 0 + UIStroke + -1 + + 1 + 0 + + + + + true + + 0.5 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + false + + rbxassetid://12187374954 + 500 + + + true + 0 + 1 + + + -1 + false + Input + null + null + null + null + + 0.43921569 + 0.43921569 + 0.43921569 + + Search + + 0.5 + 0 + 0 + 0 + + false + null + 0 + true + 0 + 0 + 0 + 0 + false + null + 0 + true + + 0.899999976 + 0 + 1 + 0 + + 0 + -1 + + + + 1 + 1 + 1 + + 0 + true + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 1 + true + 1 + + + + + + + + 0 + false + 1 + 0 + 0 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 1 + 1 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + true + 0 + Files + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + 1 + 0 + 0 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 1 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + File + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 24 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 0 + 1 + + + -1 + Title + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + + Example File + + 1 + 1 + 1 + + 0 + false + 16 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 1 + true + 1 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Buttons + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + 0 + 2 + 0 + 0 + UIListLayout + + 0 + 5 + + 2 + -1 + + 1 + 0 + false + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://16483084978 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + false + Delete + null + null + null + null + + 0 + 0 + 0 + 0 + + + 0 + null + 0 + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + 0 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 1 + 0 + + 0 + false + 0 + UIAspectRatioConstraint + -1 + + + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://16483089645 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + false + Edit + null + null + null + null + + 0 + 0 + 0 + 0 + + + 0 + null + 0 + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + 0 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 1 + 0 + + 0 + false + 0 + UIAspectRatioConstraint + -1 + + + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://16494932101 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + false + Generate + null + null + null + null + + 0 + 0 + 0 + 0 + + + 0 + null + 0 + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + 0 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 1 + 0 + + 0 + false + 0 + UIAspectRatioConstraint + -1 + + + + + + + + + + + false + + 0 + 1 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Bottom + null + null + null + null + + 0 + 0 + 1 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 22 + + 0 + -1 + 0 + + true + 2 + + + + + 0 + false + 1 + 0 + 0 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 2 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 2 + Buttons + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + 0 + 1 + 1 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 1 + 0 + false + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 1 + 1 + + + -1 + false + Save + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 0 + 1 + 0 + + 0 + -1 + 0 + + Save + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 2 + 1 + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.117647059 + 0.117647059 + 0.117647059 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 0 + 1 + + + -1 + false + Cancel + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 0 + 1 + 0 + + 0 + -1 + 0 + + Cancel + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 2 + 1 + false + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Generate + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 72 + + 0 + -1 + 0 + + false + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 0 + + 0 + + 0.0980392173 + 0.0980392173 + 0.0980392173 + + false + true + 0 + UIStroke + -1 + + 1 + 0 + + + + + + 0 + false + UIPadding + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + -1 + + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 1 + 1 + + + -1 + Hint + null + null + null + null + + 0 + 0 + 0 + 0 + + true + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 30 + + 0 + -1 + + Selected +ReplicatedStroage.bla.bla.bla]]> + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 1 + true + 2 + 1 + true + 1 + + + + + + 0 + false + 1 + 0 + 0 + 0 + UIListLayout + + 0 + 2 + + 2 + -1 + + 0 + 3 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 1 + Buttons + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0.899999976 + 0 + 0 + 18 + + 0 + -1 + 0 + + true + 1 + + + + true + + 0 + 0 + + + true + true + 0 + + 0.196078435 + 0.196078435 + 0.196078435 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 1 + 1 + + + -1 + false + Cancel + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0.899999976 + 0 + 0 + 18 + + 0 + -1 + 0 + + Cancel + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 2 + 1 + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + + true + + 0 + 0 + + + true + true + 0 + + 0.196078435 + 0.196078435 + 0.196078435 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 1 + 1 + + + -1 + false + Generate + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0.899999976 + 0 + 0 + 18 + + 0 + -1 + 0 + + Generate + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0.5 + 0 + false + 2 + 1 + true + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + + + 0 + false + 0 + 1 + 1 + 0 + UIListLayout + + 0 + 10 + + 2 + -1 + + 1 + 0 + false + + + + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 1 + Save + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 22 + + 0 + -1 + 0 + + false + 1 + + + + + 0 + + 0 + 6 + + false + UICorner + -1 + + + + + + 0 + + 0 + + 0.0980392173 + 0.0980392173 + 0.0980392173 + + false + true + 0 + UIStroke + -1 + + 1 + 0 + + + + + true + + 0.5 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + true + false + false + false + + rbxassetid://12187374954 + 500 + + + true + 0 + 1 + + + -1 + false + Input + null + null + null + null + + 0.43921569 + 0.43921569 + 0.43921569 + + Name + + 0.5 + 0 + 0 + 0 + + false + null + 0 + true + 0 + 0 + 0 + 0 + false + null + 0 + true + + 0.899999976 + 0 + 1 + 0 + + 0 + -1 + + + + 1 + 1 + 1 + + 0 + true + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 1 + true + 1 + + + + + + + + true + + 1 + 0 + + + true + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://16482966093 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + false + Expand + null + null + null + null + + 1 + -10 + 0 + 10 + + + 0 + null + 0 + 0 + true + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 22 + 1 + 0 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + 0 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + 1 + 0 + + 0 + false + 0 + UIAspectRatioConstraint + -1 + + + + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Editor + null + null + null + null + + 0 + 42 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + -42 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Lines + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 42 + 100 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + UIPadding + + 0 + 8 + + + 0 + 4 + + + 0 + 4 + + + 0 + 8 + + -1 + + + + + + + 0 + false + 1 + 1 + 0 + 0 + UIListLayout + + 0 + 3 + + 2 + -1 + + 1 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxasset://fonts/families/GothamSSm.json + 400 + + + true + 0 + 1 + + + -1 + Line + null + null + null + null + + 0 + 0 + 0 + 0 + + false + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 16 + + 0 + -1 + + 000 + + 0.470588267 + 0.470588267 + 0.470588267 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 1 + 1 + true + 1 + + + + + + + 0 + false + 0 + 1 + 1 + 0 + UIListLayout + + 0 + 0 + + 2 + -1 + + 1 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Text + null + null + null + null + + 0 + 20 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 100 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + UIPadding + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + + 0 + 8 + + -1 + + + + + + true + + 0 + 0 + + + true + 2 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 0 + 1.20000005 + + + -1 + true + Input + null + null + null + null + + 0.699999988 + 0.699999988 + 0.699999988 + + + + 0 + 0 + 0 + 0 + + true + null + 0 + true + 0 + 0 + 0 + 0 + false + null + 0 + true + + 1 + 0 + 1 + 0 + + 0 + -1 + + + + 1 + 1 + 1 + + 0 + true + false + 16 + + 0 + 0 + 0 + + 1 + 1 + 0 + false + 0 + 0 + true + 2 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Cursor + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 1 + 0 + 14 + + 0 + -1 + 0 + + false + 4 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 400 + + + true + 0 + 1.20000005 + + + -1 + Display + null + null + null + null + + 0 + 0 + 0 + 0 + + true + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + + + + 1 + 1 + 1 + + 0 + false + 16 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 0 + true + 0 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Selection + null + null + null + null + + 0 + 0 + 0 + -1 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + 0 + false + 1 + 1 + 0 + 0 + UIListLayout + + 0 + 0 + + 2 + -1 + + 1 + 0 + false + + + + + false + + 0 + 0 + + + true + 0 + + 0.200000018 + 0.411764741 + 0.627451003 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Line + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 19 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 0.200000018 + 0.411764741 + 0.627451003 + + 0.600000024 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Fill + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + + + + + false + + 0 + 0 + + + true + 0 + + 0 + 0 + 0 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Overlay + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 1 + 0 + + 0 + -1 + 0 + + true + 1 + + + + + false + + 0 + 0 + + + true + 2 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Completion + null + null + null + null + + 0.252777785 + 0 + 0.0250000004 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 200 + 0 + 0 + + 0 + -1 + 0 + + false + 5 + + + + 0 + + 0 + + 0.470588267 + 0.470588267 + 0.470588267 + + false + true + 0 + UIStroke + -1 + + 1 + 0 + + + + + + 0 + + 0 + 2 + + false + UICorner + -1 + + + + + + false + + 0 + 0 + + + true + 0 + + 0.137254909 + 0.137254909 + 0.137254909 + + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + true + 0 + Option + null + null + null + null + + 0 + 0 + 0 + 0 + + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 1 + 0 + 0 + 16 + + 0 + -1 + 0 + + true + 1 + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + rbxassetid://16506695241 + + 1 + 1 + 1 + + + 0 + 0 + + + 0 + 0 + + 0 + true + 0 + Icon + null + null + null + null + + 0 + 0 + 0 + 0 + + 1 + null + 0 + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 16 + 0 + 16 + + 0 + + + 0 + 0 + + + 0 + 0 + + + 1 + -1 + + + 1 + 0 + 1 + 0 + + true + 1 + + + + + false + + 0 + 0 + + + true + 0 + + 1 + 1 + 1 + + 1 + + 0 + 0 + 0 + + 0 + 0 + 0 + false + false + false + + rbxassetid://12187374954 + 500 + + + true + 0 + 1 + + + -1 + Text + null + null + null + null + + 0 + 20 + 0 + 0 + + false + null + 0 + false + 0 + 0 + 0 + 0 + false + null + 0 + + 0 + 200 + 1 + 0 + + 0 + -1 + + struct + + 1 + 1 + 1 + + 0 + false + 14 + + 0 + 0 + 0 + + 1 + 0 + 0 + false + 0 + 1 + true + 1 + + + + + + 0 + false + 0 + 1 + 1 + 0 + UIListLayout + + 0 + 4 + + 2 + -1 + + 1 + 0 + false + + + + + + 0 + false + UIPadding + + 0 + 0 + + + 0 + 5 + + + 0 + 5 + + + 0 + 0 + + -1 + + + + + + + 0 + + 0 + 4 + + false + UICorner + -1 + + + + + + + + 0 + false + UIPadding + + 0 + 2 + + + 0 + 0 + + + 0 + 0 + + + 0 + 2 + + -1 + + + + + + \ No newline at end of file diff --git a/plugin/src/init.luau b/plugin/src/init.server.luau similarity index 51% rename from plugin/src/init.luau rename to plugin/src/init.server.luau index 7038c28..e56c39a 100644 --- a/plugin/src/init.luau +++ b/plugin/src/init.server.luau @@ -8,6 +8,7 @@ local ServerStorage = game:GetService("ServerStorage") local UserInputService = game:GetService("UserInputService") +local ContextActionService = game:GetService("ContextActionService") local TweenService = game:GetService("TweenService") local TextService = game:GetService("TextService") local RunService = game:GetService("RunService") @@ -16,41 +17,25 @@ local Selection = game:GetService("Selection") ---- Imports ---- local State = require("./State") -local Lexer = require("../../src/Lexer") +local Editor = require("./Editor") + local Parser = require("../../src/Parser") local Generator = require("../../src/Generator") ---- Settings ---- -type A = { - Identifier: number, -} - -local EDITOR_COLORS = { - Text = Color3.fromHex("#FFFFFF"), - Keyword = Color3.fromHex("#6796E6"), - Primitive = Color3.fromHex("#4EC9B0"), - Identifier = Color3.fromHex("#9CDCFE"), - - Class = Color3.fromHex("#B5CEA8"), - Number = Color3.fromHex("#B5CEA8"), - String = Color3.fromHex("#ADF195"), - Bracket = Color3.fromHex("#FFFFFF"), - Boolean = Color3.fromHex("#B5CEA8"), - - Error = Color3.fromHex("#FF5050"), -} - local FILES_FOLDER = "BLINK_CONFIGURATION_FILES" local TEMPLATE_FILE = { Name = "Template", - Source = [[type Example = u8 -event MyEvent = { - From = Server, - Type = Reliable, - Call = SingleSync, - Data = Example -}]] + Source = table.concat({ + "type Example = u8", + "event MyEvent = {", + "\tFrom = Server,", + "\tType = Reliable,", + "\tCall = SingleSync,", + "\tData = Example", + "}" + }, "\n") } local ERROR_WIDGET = DockWidgetPluginGuiInfo.new( @@ -77,24 +62,6 @@ local SAVE_COLOR = Color3.fromRGB(0, 100, 0) local BUTTON_COLOR = Color3.fromRGB(30, 30, 30) local EXPAND_TWEEN = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut) -local SCROLL_LINES = 2 -local CURSOR_BLINK_RATE = 0.5 - -local FIELDS_TYPES = { - struct = true, - event = true, - ["function"] = true, -} - -local BRACKETS_TYPES = { - OpenBrackets = true, - CloseBrackets = true, - OpenCurlyBrackets = true, - CloseCurlyBrackets = true, - OpenSquareBrackets = true, - CloseSquareBrackets = true -} - ---- Constants ---- local Toolbar = plugin:CreateToolbar("Blink Suite") @@ -102,12 +69,10 @@ local EditorButton = Toolbar:CreateButton("Editor", "Opens the configuration edi EditorButton.ClickableWhenViewportHidden = true local Template = script.Widget -local LineTemplate: TextLabel = Template.Editor.Lines.Line:Clone() local FileTemplate = Template.Side.Content.Top.Files.File:Clone() --> Remove templates --Template.Editor.Text.Line:Destroy() -Template.Editor.Lines.Line:Destroy() Template.Side.Content.Top.Files.File:Destroy() local ErrorWidget = plugin:CreateDockWidgetPluginGui("Generation Error", ERROR_WIDGET) @@ -125,42 +90,34 @@ EditorWidget.Name = "Blink Editor" EditorWidget.Title = "Configuration Editor" EditorWidget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling -local EditorInterface = Template:Clone() +local EditorInterface = script.Widget EditorInterface.Size = UDim2.fromScale(1, 1) EditorInterface.Parent = EditorWidget local Side = EditorInterface.Side -local Editor = EditorInterface.Editor -local Overlay: Frame = EditorInterface.Overlay +local Overlay = EditorInterface.Overlay local Content = Side.Content -local Expand: ImageButton = Side.Expand +local Expand = Side.Expand local Top = Content.Top local Bottom = Content.Bottom -local Files: Frame = Top.Files -local Search: TextBox = Top.Title.Search.Input +local Files = Top.Files +local Search = Top.Title.Search.Input -local Prompt: Frame = Bottom.Save +local Prompt = Bottom.Save local Buttons = Bottom.Buttons -local GeneratePrompt: Frame = Bottom.Generate -local GenerateButtons: Frame = GeneratePrompt:FindFirstChild("Buttons") :: Frame -local Hint: TextLabel = GeneratePrompt:FindFirstChild("Hint") :: TextLabel -local Generate: TextButton = GenerateButtons:FindFirstChild("Generate") :: TextButton -local CancelGenerate: TextButton = GenerateButtons:FindFirstChild("Cancel") :: TextButton - -local Input: TextBox = Prompt:FindFirstChild("Input") :: TextBox -local Save: TextButton = Buttons.Save -local Cancel: TextButton = Buttons.Cancel +local GeneratePrompt = Bottom.Generate +local GenerateButtons = GeneratePrompt.Buttons +local Hint = GeneratePrompt.Hint +local Generate = GenerateButtons.Generate +local CancelGenerate = GenerateButtons.Cancel -local TextInput: TextBox = Editor.Text.Input -local TextCursor: Frame = Editor.Text.Cursor -local TextDisplay: TextLabel = Editor.Text.Display -local NumberLines: Frame = Editor.Lines - -local TextLineHeight = (TextInput.TextSize + 3) +local Input = Prompt.Input +local Save = Buttons.Save +local Cancel = Buttons.Cancel --> Tweens local Tweens = { @@ -179,23 +136,17 @@ local Tweens = { } } -local SourceLexer = Lexer.new("Highlighting") - ---- Variables ---- local Saving = false -local Scroll = State.new(0) + local Expanded = State.new(false) local Selected: State.Class = State.new(nil :: any) local Generating: State.Class = State.new(nil :: any) local WasOpened = false - -local Lines = 0 local Editing: string?; -local CursorTimer = 0 - ---- Private Functions ---- --> Interface utility functions @@ -212,12 +163,11 @@ local function PlayTweens(Tweens: {[string]: Tween}) end end -local function CreateScript(Name: string, Source: string, RunContent: Enum.RunContext, Parent: Instance) - local Script = Instance.new("Script") - Script.Name = Name - Script.Source = Source - Script.RunContext = RunContent - Script.Parent = Parent +local function CreateModuleScript(Name: string, Source: string, Parent: Instance) + local ModuleScript = Instance.new("ModuleScript") + ModuleScript.Name = Name + ModuleScript.Source = Source + ModuleScript.Parent = Parent end local function RequestScriptPermissions(): boolean @@ -268,10 +218,11 @@ local function GenerateFile(File: StringValue, Directory: Instance) return end - local ServerOutput = Generator("Server", AbstractSyntaxTree, UserOptions) - local ClientOutput = Generator("Client", AbstractSyntaxTree, UserOptions) + local ServerSource = Generator.Generate("Server", AbstractSyntaxTree, UserOptions) + local ClientSource = Generator.Generate("Client", AbstractSyntaxTree, UserOptions) + local TypesSource = Generator.GenerateTypeDefinitions("Server", AbstractSyntaxTree, UserOptions) - local BlinkFolder: Folder = Directory:FindFirstChild("Folder") :: Folder + local BlinkFolder: Folder = Directory:FindFirstChild("Blink") :: Folder if not BlinkFolder then local Folder = Instance.new("Folder") Folder.Name = "Blink" @@ -279,16 +230,19 @@ local function GenerateFile(File: StringValue, Directory: Instance) BlinkFolder = Folder end + --> Clear previous files BlinkFolder:ClearAllChildren() - CreateScript("Server", ServerOutput, Enum.RunContext.Server, BlinkFolder) - CreateScript("Client", ClientOutput, Enum.RunContext.Client, BlinkFolder) + + --> Generate output files + CreateModuleScript("Types", TypesSource, BlinkFolder) + CreateModuleScript("Server", ServerSource, BlinkFolder) + CreateModuleScript("Client", ClientSource, BlinkFolder) end local function LoadFile(File: StringValue) - Scroll:Set(0) Expanded:Set(false) Editing = File.Name - TextInput.Text = File.Value + Editor.SetSource(File.Value) end local function LoadFiles() @@ -354,111 +308,8 @@ local function CreateTemplateFile() LoadFile(GetSaveFolder():FindFirstChild(TEMPLATE_FILE.Name) :: StringValue) end ---> Editor utility functions -local function WrapColor(Text: string, Color: Color3): string - return `{Text}` -end - -local function GetEditorSource(): string - return TextInput.ContentText -end - -local function DoLexerPass() - local Source = GetEditorSource() - local Display = "" - - --> Initiialize lexer - SourceLexer:Initialize(Source) - - --> Highlight state - local Keyword = "none" - local IsField = false - --local IsAssigning = false - - while true do - local Success, Error, Token = pcall(function() - return nil, SourceLexer:GetNextToken() - end) - - if not Success then - warn(`Lexer error: {Error}`) - break - end - - if not Token then - break - end - - local Type: Lexer.Types = Token.Type - local Value = Token.Value - - if Type == "Keyword" then - Keyword = Value - Display ..= WrapColor(Value, EDITOR_COLORS.Keyword) - elseif Type == "Primitive" then - Display ..= WrapColor(Value, EDITOR_COLORS.Primitive) - elseif Type == "Identifier" then - Display ..= WrapColor(Value, IsField and EDITOR_COLORS.Text or EDITOR_COLORS.Identifier) - elseif Type == "Array" or Type == "Range" then - --> Exact size array/range - local Single = string.match(Value, "%[(%d+)%]") or string.match(Value, "%((%d+)%)") - if Single then - Display ..= string.gsub(Value, Single, WrapColor(Single, EDITOR_COLORS.Number)) - continue - end - - --> Variable size array/range - local Lower = string.match(Value, "%[(%d+)") or string.match(Value, "%((%d+)") - local Upper = string.match(Value, "(%d+)%]") or string.match(Value, "(%d+)%)") - if Lower and Upper then - Display ..= `{string.sub(Value, 1, 1)}{WrapColor(Lower, EDITOR_COLORS.Number)}..{WrapColor(Upper, EDITOR_COLORS.Number)}{string.sub(Value, #Value, #Value)}` - continue - end - - Display ..= Value - elseif BRACKETS_TYPES[Type] then - if Type == "CloseCurlyBrackets" then - IsField = false - end - - Display ..= WrapColor(Value, EDITOR_COLORS.Bracket) - elseif Type == "Class" then - Display ..= `({WrapColor(string.sub(Value, 2, #Value - 1), EDITOR_COLORS.Class)})` - elseif Type == "String" then - Display ..= WrapColor(Value, EDITOR_COLORS.String) - elseif Type == "Boolean" then - Display ..= WrapColor(Value, EDITOR_COLORS.Boolean) - elseif Type == "Unknown" then - --> Reset all state - IsField = false - Display ..= WrapColor(Value, EDITOR_COLORS.Error) - else - Display ..= Value - end - - if Type == "Whitespace" then - continue - end - - IsField = (Type == "Comma" or Type == "OpenCurlyBrackets") and FIELDS_TYPES[Keyword] - --IsAssigning = (Type == "Assign") - end - - TextDisplay.Text = Display -end - -local function ScrollEditor(Direction: number) - local Value = Scroll:Get() - local Maximum = math.max(1, (TextInput.TextBounds.Y - Editor.AbsoluteSize.Y) // TextLineHeight + SCROLL_LINES) - Scroll:Set(math.clamp(Value + (Direction * SCROLL_LINES), 0, Maximum)) -end - ---- Public Functions ---- -local function OnScroll(Value: number) - Editor.Position = UDim2.fromOffset(Editor.Position.X.Offset, TextLineHeight * -Value) -end - local function OnSearch(PressedEnter: boolean) if not PressedEnter then return @@ -479,55 +330,6 @@ local function OnSearch(PressedEnter: boolean) end end -local function OnCursorMoved() - local CursorPosition = TextInput.CursorPosition - if CursorPosition == -1 then - TextCursor.Visible = false - return - end - - local Size = TextInput.TextSize - local Text = string.sub(TextInput.ContentText, 1, CursorPosition - 1) - local Slices = string.split(Text, "\n") - local NewLines = math.max(#Slices, 1) - - --> Calculate current end line position - local Line = Slices[#Slices] or "" - local GetTextBoundsParams = Instance.new("GetTextBoundsParams") - GetTextBoundsParams.Text = Line - GetTextBoundsParams.Size = Size - GetTextBoundsParams.Font = TextInput.FontFace - local TextBounds = TextService:GetTextBoundsAsync(GetTextBoundsParams) - - --> Update cursor size and position - --TextCursor.Visible = true - TextCursor.Size = UDim2.fromOffset(2, Size) - TextCursor.Position = UDim2.fromOffset(TextBounds.X - 1, TextLineHeight * (NewLines - 1)) -end - -local function OnSourceChanged() - --> Pressing enter inserts a carriage return (ROBLOX please fix) - local ContentText = TextInput.ContentText - ContentText = string.gsub(ContentText, "\r", "") - TextInput.Text = ContentText - - --> Update line counter - local ContentLines = #string.split(ContentText, "\n") - if ContentLines ~= Lines then - Lines = ContentLines - ClearChildrenWhichAre(NumberLines, "TextLabel") - - for Index = 1, Lines do - local Line = LineTemplate:Clone() - Line.Text = tostring(Index) - Line.Parent = NumberLines - end - end - - --> Perform lexer pass - DoLexerPass() -end - local function OnSaveCompleted() Saving = false Prompt.Visible = false @@ -542,7 +344,7 @@ local function OnSaveActivated() return end - SaveFile(Name, GetEditorSource()) + SaveFile(Name, Editor.GetSource()) LoadFiles() OnSaveCompleted() @@ -570,25 +372,11 @@ local function OnEditorButtonClicked() EditorWidget.Enabled = not EditorWidget.Enabled end -local function OnPreRender(DeltaTime: number) - if TextInput.CursorPosition == -1 then - return - end - - CursorTimer += DeltaTime - if CursorTimer < CURSOR_BLINK_RATE then - return - end - - CursorTimer -= CURSOR_BLINK_RATE - TextCursor.Visible = not TextCursor.Visible -end - ---- Initialization ---- ----- Connections ---- +Editor.Initialize() -Scroll:OnChange(OnScroll) +---- Connections ---- Expanded:OnChange(function(Value) PlayTweens(Value and Tweens.Editor.Expand or Tweens.Editor.Retract) @@ -633,18 +421,4 @@ Search.FocusLost:Connect(OnSearch) Save.Activated:Connect(OnSaveActivated) Cancel.Activated:Connect(OnSaveCompleted) Expand.Activated:Connect(OnExpandActivated) -EditorButton.Click:Connect(OnEditorButtonClicked) - -TextInput:GetPropertyChangedSignal("Text"):Connect(OnSourceChanged) -TextInput:GetPropertyChangedSignal("CursorPosition"):Connect(OnCursorMoved) -TextInput.InputChanged:Connect(function(InputObject) - if not Editing then - return - end - - if InputObject.UserInputType == Enum.UserInputType.MouseWheel then - ScrollEditor(-InputObject.Position.Z) - end -end) - -RunService.PreRender:Connect(OnPreRender) +EditorButton.Click:Connect(OnEditorButtonClicked) \ No newline at end of file diff --git a/src/Generator/Prefabs.luau b/src/Generator/Prefabs.luau index 1e3fd6b..c276350 100644 --- a/src/Generator/Prefabs.luau +++ b/src/Generator/Prefabs.luau @@ -74,7 +74,7 @@ local Asserts = { Upper = `error("Something has gone wrong")`, Exact = `if (not {Variable}) or typeof({Variable}) ~= "Instance" then error(\`Expected an Instance, got \{typeof({Variable})} instead.\`) end\nif not {Variable}:IsA("{Class}") then error(\`Expected an Instance of type "{Class}", got "\{{Variable}.ClassName}" instead.\`) end`, } - end + end, } local Structures = { Array = nil :: any @@ -99,7 +99,11 @@ local function GeneratePrimitivePrefab(Prefab: TypePrefab, AssertGenerator: Asse local IsVariableSize = (Range and Range.Max ~= Range.Min) local function GenerateValidation(Block: Block, Variable: string) - if Range and AssertGenerator then + if not AssertGenerator then + return + end + + if Range then local Assert = AssertGenerator(Variable, Range.Min, Range.Max) if not IsVariableSize and Assert.Exact then Block:Line(Assert.Exact) @@ -107,7 +111,7 @@ local function GeneratePrimitivePrefab(Prefab: TypePrefab, AssertGenerator: Asse Block:Line(Assert.Lower) Block:Line(Assert.Upper) end - elseif Primitive == "Instance" and AssertGenerator then + elseif Primitive == "Instance" then local Assert = AssertGenerator(Variable, Class) if Value.Optional and Block == Write then Block:Compare(Variable, "nil", "Not") @@ -432,6 +436,24 @@ do } end +--> unknown +do + Types.unknown = { + Read = function(Variable: string, Block: Block) + Block:Line("RecieveInstanceCursor += 1") + Block:Line(`{Variable} = RecieveInstances[RecieveInstanceCursor]`) + end, + Write = function(Value: string, Block: Block) + Block:Line(`table.insert(SendInstances, {Value})`) + end, + } + + Primitives.unknown = { + Type = "any", + Generate = GeneratePrimitivePrefab(Types.unknown) + } +end + return { Types = Types, Asserts = Asserts, diff --git a/src/Generator/init.luau b/src/Generator/init.luau index e1b4183..42f3acb 100644 --- a/src/Generator/init.luau +++ b/src/Generator/init.luau @@ -573,7 +573,14 @@ function Generators.UserType(Declaration: Type, Read: Blocks.Block, Write: Block local Value = Declaration.Value local Optional = Value.Optional local Variable = Variable or "Value" - local IsInstance = (Declaration.Type == "TypeDeclaration" and ((Value :: any).Primitive :: string) == "Instance") + local IsInstance, IsUnknown; + + if Declaration.Type == "TypeDeclaration" then + local Primitive: string = (Value :: any).Primitive + IsUnknown = (Primitive == "unknown") + IsInstance = (Primitive == "Instance") + Optional = if IsUnknown then true else Optional + end if not _G.BUNDLED then Read:Comment(`{Variable}: {Value.Identifier}`) diff --git a/src/Lexer.luau b/src/Lexer.luau index 6681ff8..441a15b 100644 --- a/src/Lexer.luau +++ b/src/Lexer.luau @@ -2,6 +2,7 @@ --!optimize 2 local Error = require("./Modules/Error") +local Settings = require("./Settings") export type Types = "Comma" | "OpenBrackets" | "CloseBrackets" | "OpenCurlyBrackets" | "CloseCurlyBrackets" | "OpenSquareBrackets" | "CloseSquareBrackets" --> Structs & enums @@ -32,37 +33,8 @@ export type Lexer = { export type Mode = "Parsing" | "Highlighting" -local Primitives = { - u8 = true, - u16 = true, - u32 = true, - i8 = true, - i16 = true, - i32 = true, - f16 = true, - f32 = true, - f64 = true, - boolean = true, - string = true, - vector = true, - buffer = true, - Color3 = true, - CFrame = true, - Instance = true, -} - -local Keywords = { - map = true, - type = true, - enum = true, - struct = true, - - event = true, - ["function"] = true, - - scope = true, - option = true, -} +local Primitives = Settings.Primtives +local Keywords = Settings.Keywords local Booleans = { ["true"] = true, diff --git a/src/Modules/Error.luau b/src/Modules/Error.luau index c71d4a7..85e6092 100644 --- a/src/Modules/Error.luau +++ b/src/Modules/Error.luau @@ -24,6 +24,12 @@ type Slice = { Underlines: number, } +export type Label = { + Span: Span, + Text: string, + Type: "Primary" | "Secondary" +} + type Color = "red" | "green" | "blue" | "black" | "white" | "cyan" | "purple" | "yellow" | "reset" export type Span = { @@ -43,6 +49,8 @@ local Colors = { } local Error = { + OnEmit = nil, + LexerUnexpectedToken = 1001, ParserUnexpectedEndOfFile = 2001, @@ -59,12 +67,14 @@ local Error = { AnalyzeNestedScope = 3006, AnalyzeUnknownReference = 3007, AnalyzeInvalidRangeType = 3008, - AnalyzeInvalidRange = 3009 + AnalyzeInvalidRange = 3009, + AnalyzeDuplicateDeclaration = 3010 } Error.__index = Error export type Class = typeof(setmetatable({} :: { + Labels: {Label}, Source: {string}, Message: string, }, Error)) @@ -89,6 +99,7 @@ function Error.new(Code: number, Source: string, Message: string): Class Content ..= `\n{INDENT}┌─{SPACE}input.blink` return setmetatable({ + Labels = {}, Source = string.split(Source, "\n"), Message = Content }, Error) @@ -125,19 +136,44 @@ end function Error.Primary(self: Class, Span: Span, Text: string): Class local Slice = self:Slice(Span) + + --> Analyze hook + table.insert(self.Labels, { + Span = Span, + Text = Text, + Type = "Primary" + }) + + --> Construct message self.Message ..= `\n{Color(string.format("%03i", Slice.Line), "blue")}{SPACE}│{SPACE}{Slice.Text}` self.Message ..= `\n{INDENT}│{SPACE}{string.rep(SPACE, Slice.Spaces)}{Color(`{string.rep("^", Slice.Underlines)}{SPACE}{Fix(Text)}`, "red")}` + return self end function Error.Secondary(self: Class, Span: Span, Text: string): Class local Slice = self:Slice(Span) + + --> Analyze hook + table.insert(self.Labels, { + Span = Span, + Text = Text, + Type = "Secondary" + }) + + --> Construct message self.Message ..= `\n{Color(string.format("%03i", Slice.Line), "blue")}{SPACE}│{SPACE}{Slice.Text}` self.Message ..= `\n{INDENT}│{SPACE}{string.rep(SPACE, Slice.Spaces)}{Color(`{string.rep("^", Slice.Underlines)}{SPACE}{Fix(Text)}`, "blue")}` + return self end function Error.Emit(self: Class): never + local OnEmit: (({Label}) -> ())? = Error.OnEmit + if OnEmit then + OnEmit(self.Labels) + end + error(self.Message, 2) end diff --git a/src/Parser.luau b/src/Parser.luau index 729910d..9ac2f8f 100644 --- a/src/Parser.luau +++ b/src/Parser.luau @@ -6,73 +6,96 @@ local Lexer = require("./Lexer") local Error = require("./Modules/Error") local Table = require("./Modules/Table") +type Token = Lexer.Token type NumberRange = {Min: number, Max: number} type Declarations = "TypeReference" | "MapDeclaration" | "TypeDeclaration" | "EnumDeclaration" | "StructDeclaration" | "TupleDeclaration" | "EventDeclaration" | "FunctionDeclaration" | "ScopeDeclaration" -type Node = { +type Node = { Type: Type, - Value: Value + Value: Value, + Tokens: Tokens } -export type Body = Node<"Body", {Declaration}> +export type Body = Node<"Body", {Declaration}, {}> export type Declaration = Node export type TypeReference = Node<"TypeReference", { Scope: string?, Identifier: string, + Optional: boolean, + Reference: string, - + Array: NumberRange?, Range: NumberRange?, - Optional: boolean, +}, { + Identifier: Token, }> -export type MapDeclaration = Node<"MapDeclaration", { +export type TypeDeclaration = Node<"TypeDeclaration", { Scope: string?, Identifier: string, + Optional: boolean, - Key: Declaration, - Value: Declaration, + Primitive: string, + Class: string?, Array: NumberRange?, - Optional: boolean, + Range: NumberRange?, +}, { + Identifier: Token, + Value: Token, }> -export type TypeDeclaration = Node<"TypeDeclaration", { +export type MapDeclaration = Node<"MapDeclaration", { Scope: string?, Identifier: string, - Primitive: string, + Optional: boolean, + + Key: Declaration, + Value: Declaration, - Class: string?, Array: NumberRange?, - Range: NumberRange?, - Optional: boolean, +}, { + Identifier: Token, }> export type EnumDeclaration = Node<"EnumDeclaration", { Scope: string?, Identifier: string, - Enums: {string}, Optional: boolean, + + Enums: {string}, +}, { + Identifier: Token, }> export type StructDeclaration = Node<"StructDeclaration", { Scope: string?, Identifier: string, - Fields: {Declaration}, Optional: boolean, + + Fields: {Declaration}, +}, { + Identifier: Token, }> export type TupleDeclaration = Node<"TupleDeclaration", { + Scope: string?, Identifier: string, + Optional: boolean, Values: {Declaration}, - Optional: boolean +}, { + Identifier: Token }> export type EventDeclaration = Node<"EventDeclaration", { @@ -83,6 +106,14 @@ export type EventDeclaration = Node<"EventDeclaration", { Type: "Reliable" | "Unreliable", Call: "SingleSync" | "ManySync" | "SingleAsync" | "ManyAsync", Data: Declaration +}, { + Identifier: Token, + Fields: { + { + Field: Token, + Value: Token + } + }, }> export type FunctionDeclaration = Node<"FunctionDeclaration", { @@ -92,12 +123,23 @@ export type FunctionDeclaration = Node<"FunctionDeclaration", { Yield: "Future" | "Promise" | "Coroutine", Data: Declaration, Return: Declaration +}, { + Identifier: Token, + Fields: { + { + Field: Token, + Value: Token + } + }, }> export type ScopeDeclaration = Node<"ScopeDeclaration", { + Scope: string?, Identifier: string, - Declarations: {Declaration}, Optional: boolean, + Declarations: {Declaration}, +}, { + Identifier: Token, }> export type Options = { @@ -111,7 +153,7 @@ export type Options = { export type Duplicate = { Type: string, - Identifier: Lexer.Token, + Identifier: Token, Declaration: Declaration } @@ -122,12 +164,12 @@ export type Parser = { Scope: string?, Types: {[string]: Duplicate}, Events: {[string]: Duplicate}, - LookAhead: Lexer.Token?, + LookAhead: Token?, Parse: (self: Parser, Source: string) -> (Body, Options), - Consume: (self: Parser, Type: Lexer.Types) -> Lexer.Token, - ConsumeAny: (self: Parser, Types: {Lexer.Types}) -> Lexer.Token, - GetSafeLookAhead: (self: Parser) -> Lexer.Token, + Consume: (self: Parser, Type: Lexer.Types) -> Token, + ConsumeAny: (self: Parser, Types: {Lexer.Types}) -> Token, + GetSafeLookAhead: (self: Parser) -> Token, --> Parsable types Body: (self: Parser) -> Body, @@ -136,25 +178,36 @@ export type Parser = { Declaration: (self: Parser) -> Declaration, Declarations: (self: Parser) -> {Declaration}, - MapDeclaration: (self: Parser, Identifier: Lexer.Token) -> MapDeclaration, - TypeDeclaration: (self: Parser, Identifier: Lexer.Token) -> TypeDeclaration, - EnumDeclaration: (self: Parser, Identifier: Lexer.Token) -> EnumDeclaration, - TupleDeclaration: (self: Parser, Identifier: Lexer.Token) -> TupleDeclaration, - StructDeclaration: (self: Parser, Identifier: Lexer.Token) -> StructDeclaration, - EventDeclaration: (self: Parser, Identifier: Lexer.Token) -> EventDeclaration, - FunctionDeclaration: (self: Parser, Identifier: Lexer.Token) -> FunctionDeclaration, - ScopeDeclaration: (self: Parser, Identifier: Lexer.Token) -> ScopeDeclaration, - - GetReference: (self: Parser, Reference: Lexer.Token) -> Declaration, - GetTypeAttributes: (self: Parser, Primitive: Lexer.Token?) -> (NumberRange?, NumberRange?), - GetDeclarationFromToken: (self: Parser, Token: Lexer.Token, Identifier: Lexer.Token) -> (Declaration, Lexer.Token?), + MapDeclaration: (self: Parser, Identifier: Token) -> MapDeclaration, + TypeDeclaration: (self: Parser, Identifier: Token) -> TypeDeclaration, + EnumDeclaration: (self: Parser, Identifier: Token) -> EnumDeclaration, + TupleDeclaration: (self: Parser, Identifier: Token) -> TupleDeclaration, + StructDeclaration: (self: Parser, Identifier: Token) -> StructDeclaration, + EventDeclaration: (self: Parser, Identifier: Token) -> EventDeclaration, + FunctionDeclaration: (self: Parser, Identifier: Token) -> FunctionDeclaration, + ScopeDeclaration: (self: Parser, Identifier: Token) -> ScopeDeclaration, + + GetReference: (self: Parser, Reference: Token) -> Declaration, + GetTypeAttributes: (self: Parser, Primitive: Token?) -> (NumberRange?, NumberRange?), + GetDeclarationFromToken: (self: Parser, Token: Token, Identifier: Token) -> (Declaration, Token?), } -local Errors = { - DuplicateDeclaration = 3001 +local RangePrimitives = { + u8 = true, + u16 = true, + u32 = true, + i8 = true, + i16 = true, + i32 = true, + f16 = true, + f32 = true, + f64 = true, + string = true, + vector = true, + buffer = true, } -local RangePrimitives = { +local OptionalPrimitives = { u8 = true, u16 = true, u32 = true, @@ -164,9 +217,13 @@ local RangePrimitives = { f16 = true, f32 = true, f64 = true, + boolean = true, string = true, vector = true, buffer = true, + Color3 = true, + CFrame = true, + Instance = true, } local OptionsTypes: {[string]: Lexer.Types} = { @@ -216,52 +273,63 @@ local NumberRange = { end } -local function GetAttributeRange(Token: Lexer.Token, Array: boolean, Source: string): NumberRange - local Lower, Upper; - if Array then - Lower, Upper = string.match(Token.Value, `^%[({Number})..({Number})]`) - Lower = Lower or string.match(Token.Value, `^%[({Number})]`) +local function GetTypeRange(Token: Token, Array: boolean, Source: string): NumberRange + local Value = Token.Value + local function ThrowMalformedRange(): never + Error.new(Error.AnalyzeInvalidRange, Source, "Malformed range") + :Primary(Token, "Unable to parse range") + :Emit() + error("Unable to parse range") + end + + --> Exact size array/range + local Single = Array + and string.match(Value, "%[(%d+)%]") + or string.match(Value, "%((%d+)%)") + + if Single then + local Number: number = tonumber(Single) :: number + if not Number then + ThrowMalformedRange() + end - local Size = tonumber(Lower) or -1 - if Size < 0 then + if Array and Number < 0 then Error.new(Error.AnalyzeInvalidRange, Source, "Invalid array size") :Primary(Token, "Array cannot be smaller than 0 elements") :Emit() end - if not Upper then - return NumberRange.new(Size) :: NumberRange - end - else - Lower, Upper = string.match(Token.Value, `^%(({Number})..({Number})%)`) - if not Upper then - Lower = string.match(Token.Value, `^%(({Number})%)`) - end + return NumberRange.new(Number, Number) end - if not Lower then - error(`Unexpected error while trying to parse range.`) - end + --> Variable size array/range + local Lower = Array and + string.match(Value, "%[(%d+)") + or string.match(Value, "%((%d+)") - local Min = tonumber(Lower) or 0 - local Max; + local Upper = Array + and string.match(Value, "(%d+)%]") + or string.match(Value, "(%d+)%)") - --[[if Min < 0 then - Error.new(Error.AnalyzeInvalidRange, Source, "Invalid range") - :Primary(Token, "Minimum cannot be negative") - :Emit() - end]] + if Lower and Upper then + local Minimum: number = tonumber(Lower) :: number + local Maximum: number = tonumber(Upper) :: number + + if not Minimum or not Maximum then + ThrowMalformedRange() + end - if Upper then - Max = tonumber(Upper) or 0 - if Min >= Max then + if Minimum >= Maximum then Error.new(Error.AnalyzeInvalidRange, Source, "Invalid range") :Primary(Token, "Maximum must be greater than minimum") :Emit() end + + return NumberRange.new(Minimum, Maximum) end - return NumberRange.new(Min, Max) + ThrowMalformedRange() + return NumberRange.new(0, 0) end local Parser = {} @@ -295,7 +363,7 @@ function Parser.Parse(self: Parser, Source: string): (Body, Options) return Body, Options end -function Parser.Consume(self: Parser, Type: string): Lexer.Token +function Parser.Consume(self: Parser, Type: string): Token local Token = self.LookAhead if not Token then Error.new(Error.ParserUnexpectedEndOfFile, self.Source, "Unexpected end of file") @@ -318,7 +386,7 @@ function Parser.Consume(self: Parser, Type: string): Lexer.Token return Token end -function Parser.ConsumeAny(self: Parser, Types: {Lexer.Types}): Lexer.Token +function Parser.ConsumeAny(self: Parser, Types: {Lexer.Types}): Token local Token = self.LookAhead if not Token then Error.new(Error.ParserUnexpectedEndOfFile, self.Source, "Unexpected end of file") @@ -341,7 +409,7 @@ function Parser.ConsumeAny(self: Parser, Types: {Lexer.Types}): Lexer.Token return Token end -function Parser.GetSafeLookAhead(self: Parser): Lexer.Token +function Parser.GetSafeLookAhead(self: Parser): Token if self.LookAhead then return self.LookAhead end @@ -357,7 +425,8 @@ end function Parser.Body(self: Parser): Body return { Type = "Body", - Value = self:Declarations() + Value = self:Declarations(), + Tokens = {} } end @@ -401,18 +470,18 @@ function Parser.Declarations(self: Parser): {Declaration} end function Parser.Declaration(self: Parser): Declaration - local Keyword = self:Consume("Keyword") + local Keyword = self:Consume("Keyword").Value local Identifier = self:Consume("Identifier") self:Consume("Assign") --> Handle duplicate declarations - local Type = KeywordTypes[Keyword.Value] + local Type = KeywordTypes[Keyword] local Duplicates: {[string]: Duplicate} = self[Type] local ScopedIdentifier = `{self.Scope and `{self.Scope}.` or ""}{Identifier.Value}` local Duplicate = Duplicates[ScopedIdentifier] if Duplicate then - Error.new(Errors.DuplicateDeclaration, self.Source, `Duplicate {Keyword.Value} "{Identifier.Value}"`) + Error.new(Error.AnalyzeDuplicateDeclaration, self.Source, `Duplicate {Keyword} "{Identifier.Value}"`) :Secondary(Duplicate.Identifier, "Previously delcared here") :Primary(Identifier, "Duplicate declaration here") :Emit() @@ -420,32 +489,42 @@ function Parser.Declaration(self: Parser): Declaration --> Parse declaration local Declaration: Declaration?; - if Keyword.Value == "map" then + if Keyword == "map" then Declaration = self:MapDeclaration(Identifier) - elseif Keyword.Value == "type" then + elseif Keyword == "type" then Declaration = self:TypeDeclaration(Identifier) - elseif Keyword.Value == "enum" then + elseif Keyword == "enum" then Declaration = self:EnumDeclaration(Identifier) - elseif Keyword.Value == "struct" then + elseif Keyword == "struct" then Declaration = self:StructDeclaration(Identifier) - elseif Keyword.Value == "event" then + elseif Keyword == "event" then Declaration = self:EventDeclaration(Identifier) - elseif Keyword.Value == "function" then + elseif Keyword == "function" then Declaration = self:FunctionDeclaration(Identifier) - elseif Keyword.Value == "scope" then + elseif Keyword == "scope" then Declaration = self:ScopeDeclaration(Identifier) end if not Declaration then - error(`{Keyword.Value} has no declaration handler.`) + error(`{Keyword} has no declaration handler.`) end if Duplicates == self.Types and (self:GetSafeLookAhead().Type == "Optional") then - self:Consume("Optional") + local Optional = self:Consume("Optional") + if Declaration.Type == "TypeDeclaration" then + local Primitive = (Declaration :: TypeDeclaration).Value.Primitive + if Primitive == "unknown" then + Error.new(Error.AnalyzeInvalidOptionalType, self.Source, `Invalid optional type`) + :Primary(Optional, `"unknown" cannot be optional`) + :Emit() + end + end + Declaration.Value.Optional = true + Declaration.Tokens.Optional = Optional end --> Save declaration to duplicates @@ -458,10 +537,10 @@ function Parser.Declaration(self: Parser): Declaration return Declaration end -function Parser.TypeDeclaration(self: Parser, Identifier: Lexer.Token): TypeDeclaration +function Parser.TypeDeclaration(self: Parser, Identifier: Token): TypeDeclaration local LookAhead = self:GetSafeLookAhead() - local Primitive: Lexer.Token; + local Primitive: Token; local Declaration: TypeDeclaration; if LookAhead.Type == "Identifier" then @@ -475,6 +554,8 @@ function Parser.TypeDeclaration(self: Parser, Identifier: Lexer.Token): TypeDecl self:Consume("Identifier") Declaration = Table.DeepClone(Reference) Declaration.Value.Identifier = Identifier.Value + Declaration.Tokens.Identifier = Identifier + Declaration.Tokens.Value = LookAhead else Primitive = self:Consume("Primitive") Declaration = { @@ -484,6 +565,10 @@ function Parser.TypeDeclaration(self: Parser, Identifier: Lexer.Token): TypeDecl Identifier = Identifier.Value, Primitive = Primitive.Value, Optional = false + }, + Tokens = { + Identifier = Identifier, + Value = Primitive, } } @@ -510,7 +595,7 @@ function Parser.TypeDeclaration(self: Parser, Identifier: Lexer.Token): TypeDecl return Declaration end -function Parser.EnumDeclaration(self: Parser, Identifier: Lexer.Token): EnumDeclaration +function Parser.EnumDeclaration(self: Parser, Identifier: Token): EnumDeclaration self:Consume("OpenBrackets") local Enums: {string} = {} @@ -548,11 +633,14 @@ function Parser.EnumDeclaration(self: Parser, Identifier: Lexer.Token): EnumDecl Identifier = Identifier.Value, Enums = Enums, Optional = false + }, + Tokens = { + Identifier = Identifier, } } end -function Parser.MapDeclaration(self: Parser, Identifier: Lexer.Token): MapDeclaration +function Parser.MapDeclaration(self: Parser, Identifier: Token): MapDeclaration self:Consume("OpenCurlyBrackets") self:Consume("OpenSquareBrackets") @@ -625,14 +713,19 @@ function Parser.MapDeclaration(self: Parser, Identifier: Lexer.Token): MapDeclar Array = Array, Optional = false, + }, + Tokens = { + Identifier = Identifier, + Key = Key, + Value = Value, } } end -function Parser.StructDeclaration(self: Parser, Identifier: Lexer.Token): StructDeclaration +function Parser.StructDeclaration(self: Parser, Identifier: Token): StructDeclaration self:Consume("OpenCurlyBrackets") - local Keys: {[string]: Lexer.Token} = {} + local Keys: {[string]: Token} = {} local Fields = {} while (self.LookAhead) do @@ -673,11 +766,14 @@ function Parser.StructDeclaration(self: Parser, Identifier: Lexer.Token): Struct Identifier = Identifier.Value, Fields = Fields, Optional = false, + }, + Tokens = { + Identifier = Identifier, } } end -function Parser.TupleDeclaration(self: Parser, Identifier: Lexer.Token): TupleDeclaration +function Parser.TupleDeclaration(self: Parser, Identifier: Token): TupleDeclaration self:Consume("OpenSquareBrackets") local Values: {Declaration} = {} @@ -706,11 +802,14 @@ function Parser.TupleDeclaration(self: Parser, Identifier: Lexer.Token): TupleDe Identifier = Identifier.Value, Values = Values, Optional = false + }, + Tokens = { + Identifier = Identifier } } end -function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDeclaration +function Parser.EventDeclaration(self: Parser, Identifier: Token): EventDeclaration self:Consume("OpenCurlyBrackets") if ReservedMembers[Identifier.Value] then @@ -729,6 +828,10 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe Call = "SingleSync", Data = nil :: any, Optional = false + }, + Tokens = { + Identifier = Identifier, + Fields = {} } } @@ -740,11 +843,12 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe :Emit() end - local Value: unknown; + local Value: any; local Assign = self:Consume("Assign") + local Token = self:GetSafeLookAhead() if Entry.Key ~= "Data" then - local Token = self:Consume(Entry.Type) + Token = self:Consume(Entry.Type) if not table.find(Entry.Values, Token.Value) then Error.new(Error.ParserUnexpectedToken, self.Source, `Unexpected token`) :Primary(Token, `Expected one of "{table.concat(Entry.Values, "\" or \"")}", found "{Token.Value}"`) @@ -753,8 +857,7 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe Value = Token.Value else - local Token = self.LookAhead - if not Token then + if not self.LookAhead then Error.new(Error.ParserExpectedExtraToken, self.Source, `Expected a token`) :Primary(Assign, `Expected a value to follow after assignment`) :Emit() @@ -769,6 +872,10 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe end Event.Value[Entry.Key] = Value + table.insert(Event.Tokens, { + Field = Key, + Value = Token + }) if Index ~= #EventStructure then self:Consume("Comma") @@ -780,7 +887,7 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe return Event end -function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): FunctionDeclaration +function Parser.FunctionDeclaration(self: Parser, Identifier: Token): FunctionDeclaration self:Consume("OpenCurlyBrackets") if ReservedMembers[Identifier.Value] then @@ -798,6 +905,10 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func Yield = "Coroutine", Data = nil :: any, Return = nil :: any + }, + Tokens = { + Identifier = Identifier, + Fields = {} } } @@ -811,9 +922,10 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func local Value: unknown; local Assign = self:Consume("Assign") + local Token = self:GetSafeLookAhead() if Entry.Key ~= "Data" and Entry.Key ~= "Return" then - local Token = self:Consume(Entry.Type) + Token = self:Consume(Entry.Type) if not table.find(Entry.Values, Token.Value) then Error.new(Error.ParserUnexpectedToken, self.Source, `Unexpected token`) :Primary(Token, `Expected one of "{table.concat(Entry.Values, "\" or \"")}", found "{Token.Value}"`) @@ -822,8 +934,7 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func Value = Token.Value else - local Token = self.LookAhead - if not Token then + if not self.LookAhead then Error.new(Error.ParserExpectedExtraToken, self.Source, `Expected a token`) :Primary(Assign, `Expected a value to follow after assignment`) :Emit() @@ -838,6 +949,10 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func end Function.Value[Entry.Key] = Value + table.insert(Function.Tokens, { + Field = Key, + Value = Token + }) if Index ~= #FunctionStructure then self:Consume("Comma") @@ -849,7 +964,7 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func return Function end -function Parser.ScopeDeclaration(self: Parser, Identifier: Lexer.Token): ScopeDeclaration +function Parser.ScopeDeclaration(self: Parser, Identifier: Token): ScopeDeclaration if self.Scope then Error.new(Error.AnalyzeNestedScope, self.Source, "Nested scope") :Primary(Identifier, `Blink doesn't currently support declaring scopes within scopes`) @@ -870,6 +985,9 @@ function Parser.ScopeDeclaration(self: Parser, Identifier: Lexer.Token): ScopeDe Identifier = Scope, Optional = false, Declarations = Declarations + }, + Tokens = { + Identifier = Identifier } } @@ -890,7 +1008,7 @@ function Parser.ScopeDeclaration(self: Parser, Identifier: Lexer.Token): ScopeDe return Declaration end -function Parser.GetReference(self: Parser, Token: Lexer.Token): Declaration +function Parser.GetReference(self: Parser, Token: Token): Declaration local Identifier = `{self.Scope and `{self.Scope}.` or ""}{Token.Value}` local Reference = self.Types[Identifier] or self.Types[Token.Value] if not Reference then @@ -902,7 +1020,7 @@ function Parser.GetReference(self: Parser, Token: Lexer.Token): Declaration return Reference.Declaration end -function Parser.GetTypeAttributes(self: Parser, Primitive: Lexer.Token?): (NumberRange?, NumberRange?) +function Parser.GetTypeAttributes(self: Parser, Primitive: Token?): (NumberRange?, NumberRange?) local Array, Range; for _, Expected in {"Range", "Array"} do local LookAhead = self.LookAhead @@ -926,9 +1044,9 @@ function Parser.GetTypeAttributes(self: Parser, Primitive: Lexer.Token?): (Numbe :Emit() end - Range = GetAttributeRange(LookAhead, false, self.Source) + Range = GetTypeRange(LookAhead, false, self.Source) elseif Type == "Array" then - Array = GetAttributeRange(LookAhead, true, self.Source) + Array = GetTypeRange(LookAhead, true, self.Source) end self:Consume(Type) @@ -937,7 +1055,7 @@ function Parser.GetTypeAttributes(self: Parser, Primitive: Lexer.Token?): (Numbe return Array, Range end -function Parser.GetDeclarationFromToken(self: Parser, Token: Lexer.Token, Identifier: Lexer.Token): (Declaration, Lexer.Token?) +function Parser.GetDeclarationFromToken(self: Parser, Token: Token, Identifier: Token): (Declaration, Token?) local Declaration: Declaration; if Token.Type == "OpenCurlyBrackets" then local NextToken = self.Lexer:GetNextToken(true) @@ -951,8 +1069,8 @@ function Parser.GetDeclarationFromToken(self: Parser, Token: Lexer.Token, Identi elseif Token.Type == "Primitive" then Declaration = self:TypeDeclaration(Identifier) elseif Token.Type == "Identifier" then - local Type = self:Consume("Identifier") - local Reference = self:GetReference(Type) + self:Consume("Identifier") + local Reference = self:GetReference(Token) local Array, Range = self:GetTypeAttributes() Declaration = { @@ -965,6 +1083,9 @@ function Parser.GetDeclarationFromToken(self: Parser, Token: Lexer.Token, Identi Array = Array, Range = Range, Optional = false + }, + Tokens = { + Identifier = Identifier } } end @@ -979,6 +1100,16 @@ function Parser.GetDeclarationFromToken(self: Parser, Token: Lexer.Token, Identi if self:GetSafeLookAhead().Type == "Optional" then Optional = self:Consume("Optional") Declaration.Value.Optional = true + + if Token.Type == "Identifier" then + local Type: TypeDeclaration = self:GetReference(Token) :: TypeDeclaration + if Type.Value.Primitive == "unknown" then + Error.new(Error.AnalyzeInvalidOptionalType, self.Source, `Invalid optional type`) + :Secondary(Type.Tokens.Value, "Declared here") + :Primary(Optional, `{Type.Value.Identifier} is of type "unknown", "unknown" cannot be optional`) + :Emit() + end + end end return Declaration, Optional diff --git a/src/Settings.luau b/src/Settings.luau new file mode 100644 index 0000000..e919352 --- /dev/null +++ b/src/Settings.luau @@ -0,0 +1,34 @@ +return { + Keywords = { + map = true, + type = true, + enum = true, + struct = true, + + event = true, + ["function"] = true, + + scope = true, + option = true, + }, + + Primtives = { + u8 = true, + u16 = true, + u32 = true, + i8 = true, + i16 = true, + i32 = true, + f16 = true, + f32 = true, + f64 = true, + boolean = true, + string = true, + vector = true, + buffer = true, + Color3 = true, + CFrame = true, + Instance = true, + unknown = true + } +} \ No newline at end of file diff --git a/test/Sources/Conflicts.txt b/test/Sources/Conflicts.txt index 075dfff..18c1f52 100644 --- a/test/Sources/Conflicts.txt +++ b/test/Sources/Conflicts.txt @@ -3,13 +3,13 @@ option ClientOutput = "../Network/ClientConflict.luau" option ServerOutput = "../Network/ServerConflict.luau" type Name = u8? -type Unknown = i8[1] +type Variable = unknown event Name = { From = Server, Type = Reliable, Call = SingleSync, - Data = u8 + Data = Variable } scope s = { diff --git a/test/Sources/Test.txt b/test/Sources/Test.txt index 23d0362..966d155 100644 --- a/test/Sources/Test.txt +++ b/test/Sources/Test.txt @@ -31,6 +31,13 @@ struct MapStruct = { Map = {[string] = u8} } +event Unknown = { + From = Server, + Type = Reliable, + Call = SingleSync, + Data = unknown +} + event MapEvent = { From = Server, Type = Reliable, diff --git a/test/Test.luau b/test/Test.luau index b8b546d..88f477f 100644 --- a/test/Test.luau +++ b/test/Test.luau @@ -403,6 +403,8 @@ FireAndExpect("Unreliable Server", Server.UnreliableServer, Client.UnreliableSer FireAndExpect("Reliable Client", Client.ReliableClient, Server.ReliableClient, 1) FireAndExpect("Unreliable Client", Client.UnreliableClient, Server.UnreliableClient, 1) +FireAndExpect("Unknown", Server.Unknown, Client.Unknown, newproxy()) + FireAndExpect("Map", Server.MapEvent, Client.MapEvent, {["string"] = 1}) FireAndExpect("Map Struct", Server.MapStructEvent, Client.MapStructEvent, {Map = {string = 1}}) FireAndExpect("Map Complex", Server.MapComplexEvent, Client.MapComplexEvent, {string = table.create(8, 1)})