├── 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"));
--------------------------------------------------------------------------------