├── bun.lockb ├── src ├── utils │ ├── index.ts │ ├── findFunction.ts │ └── parser.ts ├── transformers │ ├── index.ts │ ├── dynamic │ │ ├── dynamic.template.txt │ │ └── index.ts │ └── optional │ │ └── index.ts ├── global.d.ts ├── header.template.txt └── index.ts ├── .github └── workflows │ └── build.yml ├── tsconfig.json ├── package.json ├── LICENSE.txt ├── README.md └── .gitignore /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blade67/GATE/HEAD/bun.lockb -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as findFunction } from "./findFunction"; -------------------------------------------------------------------------------- /src/transformers/index.ts: -------------------------------------------------------------------------------- 1 | export { default as parseDynamic } from './dynamic'; 2 | export { default as parseOptional } from './optional'; -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.template.txt" { 2 | export default string; 3 | }; 4 | 5 | declare module "*.ne" { 6 | export default nearley.Grammar; 7 | } -------------------------------------------------------------------------------- /src/utils/findFunction.ts: -------------------------------------------------------------------------------- 1 | export default function findFunction(input: string, functionName: string) { 2 | console.log(input, functionName); 3 | 4 | 5 | // return input; 6 | } -------------------------------------------------------------------------------- /src/transformers/dynamic/dynamic.template.txt: -------------------------------------------------------------------------------- 1 | signal on_%label%_changed(value%opt_type%) 2 | 3 | var %label%%opt_type% = %value% : 4 | set (v): 5 | %label% = v 6 | on_%label%_changed.emit(%label%) 7 | get: 8 | return %label% -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | jobs: 7 | build: 8 | name: 'Build on Linux' 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macOS-latest] 13 | steps: 14 | - name: 'Checkout' 15 | uses: actions/checkout@v4 16 | 17 | - name: 'Setup Bun' 18 | uses: oven-sh/setup-bun@v1 19 | with: 20 | bun-version: latest 21 | 22 | - name: Install packages 23 | run: bun install 24 | 25 | - name: Build code 26 | run: bun run build 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gate", 3 | "author": "Maxime \"Blade\" G.", 4 | "description": "A Godot Engine GDScript Superset.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/Blade67/GATE" 8 | }, 9 | "version": "0.0.3", 10 | "module": "src/index.ts", 11 | "type": "module", 12 | "scripts": { 13 | "dev": "bun run src/index.ts", 14 | "build": "bun build --compile --minify --sourcemap ./src/index.ts --outfile ./dist/gate", 15 | "build:win": "bun build --compile --minify --sourcemap --target=bun-windows-x64-modern ./src/index.ts --outfile ./dist/gate-windows", 16 | "build:linux": "bun build --compile --minify --sourcemap --target=bun-linux-x64-baseline ./src/index.ts --outfile ./dist/gate-linux", 17 | "build:mac": "bun build --compile --minify --sourcemap --target=bun-darwin-x64 ./src/index.ts --outfile ./dist/gate-macos", 18 | "test": "bun run dev -- -i ./input/test.gate -o ./output" 19 | }, 20 | "devDependencies": { 21 | "@types/bun": "latest" 22 | }, 23 | "peerDependencies": { 24 | "typescript": "^5.0.0" 25 | }, 26 | "dependencies": { 27 | "chalk": "^5.3.0" 28 | } 29 | } -------------------------------------------------------------------------------- /src/transformers/optional/index.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | 3 | // NOTE: Currently only supports variables! 4 | export default function parseOptional(input: string) { 5 | if (!input.trim().startsWith("var")) { 6 | console.log(chalk.yellow("[GATE]"), `The optional operator is only supported for variables!`); 7 | return input; 8 | } 9 | const startTokens = input.slice(0, input.indexOf("=") + 1); 10 | const optTokens = input 11 | .slice(input.indexOf("=") + 1) 12 | .trim() 13 | .split(/(\?\.|\.)/) 14 | .filter(e => e !== "." && e !== "?."); 15 | if (!optTokens) return input; 16 | 17 | 18 | let out = "" 19 | let condition = ""; 20 | for (let i = 1; i < optTokens.length; i++) { 21 | condition += `${optTokens[i]} in ${optTokens.slice(0, i).join(".")}`; 22 | if (i < optTokens.length - 1) condition += " && "; 23 | } 24 | 25 | // TODO: Account for indentation, read from editorconfig 26 | out += `${startTokens} null\nif ${condition}:\n ${input.split(" ")[1]} = ${input 27 | .slice(input.indexOf("=") + 1) 28 | .split("?.") 29 | .join(".") 30 | }`; 31 | 32 | return out; 33 | } -------------------------------------------------------------------------------- /src/transformers/dynamic/index.ts: -------------------------------------------------------------------------------- 1 | import template from "./dynamic.template.txt" with { type: "text" }; 2 | 3 | export default function parseDynamic(input: string) { 4 | let tokens = tokenizeDynamic(input); 5 | 6 | input = template 7 | .replace(/%label%/g, tokens.label) 8 | .replace(/%opt_type%/g, tokens.type ? `: ${tokens.type}` : "") 9 | .replace(/%value%/g, tokens.value); 10 | 11 | return input; 12 | } 13 | 14 | function tokenizeDynamic(input: string): { label: string, type: string | null, value: string } { 15 | let label = "" + input.match(/\w+/); 16 | 17 | input = input.replace(label, ""); 18 | 19 | let type = 20 | input.trim().startsWith(":") && !input.trim().startsWith(":=") 21 | ? "" + input.match(/\w+/) 22 | : null; 23 | let value = input 24 | .replace(type ?? "", "") 25 | .replace(":", "") 26 | .replace("=", "") 27 | .trimStart(); 28 | 29 | // TODO: Refactor 30 | if (!type) { 31 | if ( 32 | value.startsWith("\"") || 33 | value.startsWith("\"\"\"") || 34 | value.startsWith("\`") 35 | ) { 36 | type = "string"; 37 | } else if (!isNaN(Number(value))) { 38 | if (value.includes(".")) { 39 | type = "float"; 40 | } else { 41 | type = "int"; 42 | } 43 | } else if (value === "true" || value === "false") { 44 | type = "bool"; 45 | } 46 | } 47 | 48 | return { 49 | label, 50 | type, 51 | value 52 | }; 53 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # This file is part of: 3 | # GATE 4 | ############################################################################ 5 | # 6 | # Copyright (c) 2024 Maxime "Blade" G. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # and/or distribute copies of the Software, and to permit persons to 13 | # whom the Software is furnished to do so, subject to the following 14 | # conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # This software is provided for free and may not be sold or used in 20 | # any commercial product without prior written permission from the author. 21 | # 22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 26 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 27 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 28 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | # 30 | ############################################################################ -------------------------------------------------------------------------------- /src/header.template.txt: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # %file_name% 3 | ############################################################################ 4 | # This file is part of: 5 | # GATE (v%version%) 6 | # %repository% 7 | ############################################################################ 8 | # 9 | # Copyright (c) %year% %author% 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining 12 | # a copy of this software and associated documentation files (the 13 | # "Software"), to deal in the Software without restriction, including 14 | # without limitation the rights to use, copy, modify, merge, publish, 15 | # and/or distribute copies of the Software, and to permit persons to 16 | # whom the Software is furnished to do so, subject to the following 17 | # conditions: 18 | # 19 | # The above copyright notice and this permission notice shall be 20 | # included in all copies or substantial portions of the Software. 21 | # 22 | # This software is provided for free and may not be sold or used in 23 | # any commercial product without prior written permission from the author. 24 | # 25 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 29 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 30 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | # 33 | ############################################################################ -------------------------------------------------------------------------------- /src/utils/parser.ts: -------------------------------------------------------------------------------- 1 | // FIXME: Remove, debug purposes only 2 | const inputFile = await Bun.file("./test/input.gate").text() 3 | 4 | type Token = { 5 | value: string; 6 | type: string; 7 | children: Token[]; 8 | indent: number; 9 | } 10 | 11 | const tokenables = lex(inputFile) 12 | console.log("tokenables = ", tokenables); 13 | const tokens = tokenize(tokenables) 14 | console.log("tokens = ", tokens); 15 | const AST = toAST(tokens); 16 | 17 | 18 | function lex(input: string): string[] { 19 | return input 20 | .split(/(\w*)/) 21 | .filter(n => n) 22 | .reduce((acc, i, idx, a) => (a[idx - 1] === i || idx === 0) ? acc + i : acc + "|" + i, "") 23 | .split("|") 24 | .reduce((acc: string[], curr: string) => { 25 | acc[acc.length] = (curr === "\n" && acc.length > 0 && acc[acc.length - 1] === "\r") ? "\r\n" : curr; 26 | return acc; 27 | }, []) 28 | .filter(e => e !== "\r") 29 | } 30 | 31 | function tokenize(input: string[]): Token[] { 32 | let tokens: Token[] = [] 33 | let indent = 0 34 | for (let i = 0; i < input.length; i++) { 35 | if (i > 0 && input[i - 1] === "\r\n") { 36 | if (!!input[i].match(/[ \t]+/)) { 37 | // Get indentation depth 38 | indent = input[i].length; 39 | continue; 40 | } else { 41 | // Reset indentation depth 42 | indent = 0; 43 | } 44 | } 45 | if (!!input[i].match(/\s+/)) { 46 | // Remove unnecessary spaces 47 | continue; 48 | } 49 | if (input[i - 1] === "$$") { 50 | // Dynamic operator 51 | tokens[tokens.length - 1].type = "DYN_OP" 52 | tokens[tokens.length - 1].children = [...tokens[tokens.length - 1].children, { 53 | value: `${input[i]}`, 54 | type: "DYN_VAL", 55 | children: [], 56 | indent 57 | }] 58 | continue; 59 | } 60 | tokens.push({ 61 | value: input[i], 62 | type: "", 63 | children: [], 64 | indent 65 | }) 66 | } 67 | return tokens 68 | } 69 | 70 | function toAST(input: Token[]) { 71 | 72 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GATE 2 | ###### ⚠ **Warning**! GATE is currently undergoing heavy development and is not yet production-ready! An engine integration will soon follow. 3 | GATE is a [GDScript](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html) superset for the [Godot Engine](https://godotengine.org/) that adds [extra features](#features) while compiling down to native GDScript. 4 | 5 | ## Table of Content 6 | - [Usage](#usage) 7 | - [Features](#features) 8 | * [Dynamic Variable Decorator `$$`](#dynamic-variable-decorator-) 9 | * [Optional Operator `?.`](#optional-operator) 10 | - [Upcoming features](#upcoming-features) 11 | - [Contributing](#contributing) 12 | 13 | ## Usage 14 | Download the latest build for your OS from [here](https://github.com/Blade67/GATE/releases) and use it as follows: 15 | ```sh 16 | # --input, -i 17 | # Path to the input `.gate` file/directory. 18 | # Note: Only `.gate` files are read. 19 | # 20 | # --output, -i 21 | # Path to the output directory. 22 | # Note: The output directory respects the input layout. 23 | 24 | ./gate --input ./path/to/myFile.gate --output ./output 25 | ``` 26 | 27 | ## Features 28 | ### Dynamic Variable Decorator `$$` 29 | The `$$` decorator automates signal, setter, getter, and connection creation. 30 | ##### Example 31 | ```gdscript 32 | class_name Test extends Label 33 | 34 | $$count = 0 35 | 36 | func _ready() -> void: 37 | count += 1 38 | 39 | func count_changed(value: int) -> void: 40 | text = "Count is %s" % value 41 | ``` 42 | 43 | ### Optional Operator `?.` 44 | The `?.` (optional) operator allows you to check for null in a chain of properties. 45 | Note: This currently only works with variable! 46 | ##### Example 47 | ```gdscript 48 | func _ready() -> void: 49 | # In this case `text` is either `null` or the 50 | # expected value of `myProp` 51 | var text = $MyNode?.myObject?.myProp 52 | ``` 53 | 54 | ## Upcoming features 55 | - [ ] Loop ranges `2..15` 56 | - `for i in 2..15:` 57 | - [ ] Spread operator `...` 58 | - `var arr = [0, 1, 2]`
`var numbers = [...arr, 3, 4, 5]` 59 | - [ ] Rest operator `...` 60 | - `myFunction(a, b, c, d, e)`
`func myFunction(first, second, ...rest)` 61 | - [ ] Destructuring `[_, _]` 62 | - `var [parm1, param2] = myFunction()` 63 | - [ ] Inline templates 64 | - var x = \`${player1} killed ${player2}` 65 | - [x] Optional chaining `?.` 66 | - `var x = $node.?thing.?deeper1.?deeper2` 67 | 68 | ## Contributing 69 | ###### GATE is made using [Bun](https://bun.sh/) with [TypeScript](https://www.typescriptlang.org/). 70 | 71 | To install dependencies: 72 | 73 | ```bash 74 | bun install 75 | ``` 76 | 77 | To run: 78 | 79 | ```bash 80 | bun run dev 81 | ``` 82 | 83 | To build: 84 | ```bash 85 | bun run build 86 | # or 87 | bun run build:win 88 | bun run build:linux 89 | bun run build:mac 90 | ``` 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | .vscode/ 163 | 164 | # yarn v2 165 | 166 | .yarn/cache 167 | .yarn/unplugged 168 | .yarn/build-state.yml 169 | .yarn/install-state.gz 170 | .pnp.* 171 | 172 | # IntelliJ based IDEs 173 | .idea 174 | 175 | # Finder (MacOS) folder config 176 | .DS_Store 177 | 178 | roadmap.md 179 | 180 | # Test folders for debugging 181 | input 182 | output 183 | test -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import template from "./header.template.txt" with { type: "text" }; 2 | import pck from "../package.json" with { type: "json" }; 3 | import { parseDynamic, parseOptional } from "./transformers"; 4 | import { parseArgs } from "util"; 5 | import chalk from "chalk"; 6 | import { Glob } from "bun"; 7 | import { lstatSync } from "node:fs"; 8 | 9 | const header = template 10 | .replace(/%version%/, pck.version) 11 | .replace(/%repository%/, pck.repository.url) 12 | .replace(/%author%/, pck.author) 13 | .replace(/%year%/, new Date().getFullYear().toString()); 14 | let input, output; 15 | 16 | try { 17 | const { values } = parseArgs({ 18 | args: Bun.argv, 19 | options: { 20 | input: { 21 | type: 'string', 22 | short: 'i', 23 | }, 24 | output: { 25 | type: 'string', 26 | short: 'o', 27 | }, 28 | }, 29 | strict: true, 30 | allowPositionals: true, 31 | }); 32 | 33 | input = values.input; 34 | output = values.output; 35 | 36 | if (output && !lstatSync(output).isDirectory()) { 37 | console.error(chalk.red("[GATE]"), "Output should be a directory, not a file."); 38 | process.exit(1); 39 | } 40 | if (!input) { 41 | console.error(chalk.red("[GATE]"), "No input file specified."); 42 | process.exit(1); 43 | } 44 | 45 | input = input.replace(/\\/g, "/"); 46 | 47 | if (output) output = output.replace(/\\/g, "/") 48 | } catch (error) { 49 | console.log(chalk.red("[GATE]"), (error as { message?: string }).message); 50 | process.exit(1); 51 | } 52 | 53 | type InputFile = { path: string, content: string }; 54 | let files: Promise[] = []; 55 | 56 | if (input.endsWith(".gate")) { 57 | let f = Bun.file(input); 58 | 59 | if (!f.exists()) { 60 | console.error(chalk.red("[GATE]"), `File "${input}" does not exist.`); 61 | process.exit(1); 62 | } 63 | 64 | files.push({ 65 | //@ts-ignore this is stupid 66 | path: `${f.name?.replace(/\\/g, "/")}`, 67 | content: await f.text() 68 | }); 69 | } else { 70 | const glob = new Glob(`${input}/**/*.gate`); 71 | const scannedFiles = await Array.fromAsync(glob.scan({ cwd: './' })) 72 | 73 | if (scannedFiles.length === 0) { 74 | console.error(chalk.red("[GATE]"), `No files found in "${input}".`); 75 | process.exit(1); 76 | } 77 | 78 | files.push(...scannedFiles.map(async (f) => { 79 | const file = Bun.file(f); 80 | return { 81 | path: `${file.name?.replace(/\\/g, "/")}`, 82 | content: await file.text() 83 | } 84 | })); 85 | } 86 | 87 | Promise.all(files).then(async (files) => { 88 | for (let file of files) { 89 | let f = file.content.split("\r\n").map(l => l.trimEnd()) 90 | let signalStack: Array = []; 91 | let lastIndent = " "; 92 | 93 | for (let i = 0; i < f.length; i++) { 94 | if (f[i].trimStart().startsWith("$$")) { 95 | const dyn = f[i].trimStart().slice(2); 96 | if (!dyn) continue; 97 | const dynamic = parseDynamic(dyn); 98 | const signal = dyn.match(/\w+/)?.[0]; 99 | if (signal) signalStack.push(signal); 100 | f[i] = dynamic; 101 | } 102 | if (f[i].includes("?.")) { 103 | const optionalOp = parseOptional(f[i]) 104 | // console.log("Contains \"?\":", optionalOp, f[i]); 105 | f[i] = optionalOp; 106 | } 107 | if (f[i].startsWith("func _ready()") && signalStack.length > 0) { 108 | lastIndent = f[i + 1].match(/\s+/)?.[0] ?? " "; 109 | 110 | if (f[i].includes("pass")) f[i] = f[i].replace("pass", ""); 111 | if (f[i + 1].includes("pass")) f[i + 1] = ""; 112 | 113 | f[i] += `\n${lastIndent}` + signalStack.map(s => `on_${s}_changed.connect(\"_on_${s}_changed\");`).join("\n" + lastIndent); 114 | 115 | signalStack = []; 116 | } 117 | } 118 | 119 | file.content = f.join("\r\n"); 120 | 121 | if (signalStack.length > 0) { 122 | file.content += `\n\nfunc _ready() -> void:\n${lastIndent}` + signalStack.map(s => `on_${s}_changed.connect(\"_on_${s}_changed\");`).join("\n" + lastIndent); 123 | } 124 | } 125 | return files; 126 | }).then(files => { 127 | for (let file of files) { 128 | file.path = file.path.replace(".gate", ".gd") 129 | const fileName = file.path.split("/").pop() ?? ""; 130 | 131 | if (input && output) { 132 | file.path = file.path.replace(input, output); 133 | } 134 | 135 | console.log(chalk.blue("[GATE]"), `Writing "${chalk.green(file.path)}"...`); 136 | 137 | let h = header.replace(/%file_name%/g, file.path.split("/").pop()) 138 | 139 | // TODO: Fix this to account for the input/output paths as folders properly 140 | Bun.write( 141 | output + "/" + fileName.replace(".gate", ".gd"), 142 | `${h}\n\n${file.content}` 143 | ); 144 | } 145 | 146 | console.log(chalk.blue("[GATE]"), `Generated ${chalk.green(files.length)} file${files.length > 1 ? "s" : ""}.`); 147 | }); 148 | 149 | 150 | 151 | // Bun.write("./test.gd", input.join("\r\n")); --------------------------------------------------------------------------------