@scaffscript/core 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +329 -0
- package/dist/index.cjs +2325 -0
- package/dist/index.mjs +2297 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# ScaffScript
|
|
2
|
+
|
|
3
|
+
A superset language of **GameMaker Language** (GML) for creating module-based GameMaker source codes. This minimal language is mainly used for developing GML libraries, but can also be used for other purposes.
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> This project is still in early development. The syntax and features are subject to change. Use at your own risk.
|
|
7
|
+
|
|
8
|
+
## Key Features
|
|
9
|
+
|
|
10
|
+
- Unify multiple source files into a single source file.
|
|
11
|
+
- TypeScript-like syntax and module system.
|
|
12
|
+
- Flexible configuration options.
|
|
13
|
+
- Dev-friendly CLI interface.
|
|
14
|
+
- Togglable integration (auto/manual) to your GameMaker project.
|
|
15
|
+
- And more... (WIP)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
1. Install [Bun](https://bun.sh) (if not already installed).
|
|
20
|
+
2. Clone this repository.
|
|
21
|
+
3. Install dependencies:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
4. CLI usage:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
scaff <command> [args]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Use `*.ss` files to mark a file as a Scaff file. Normal `*.gml` files are still supported, but they are not processed by Scaff.
|
|
36
|
+
|
|
37
|
+
### Export Module
|
|
38
|
+
|
|
39
|
+
1. Use `export` statement to export types (a variable, function, class, interface, type, enum, or arrow function) from a Scaff file.
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
// my_file.ss
|
|
43
|
+
|
|
44
|
+
export var my_var = 1;
|
|
45
|
+
export let my_let = 2; // `let` will be removed, so it'll become an instance variable
|
|
46
|
+
export const MY_CONST = "Hello, World!"; // `const` will be converted to `#macro`
|
|
47
|
+
|
|
48
|
+
export function my_func() {
|
|
49
|
+
show_debug_message("Hello, World!");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// arrow functions will be converted to function expression or method
|
|
53
|
+
export const my_method = (name: string, age: number) => {
|
|
54
|
+
show_debug_message(`Hello, ${name}! You are ${age} years old.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export enum MY_ENUM {
|
|
58
|
+
A,
|
|
59
|
+
B,
|
|
60
|
+
C
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// classes will be converted to struct constructor
|
|
64
|
+
export class MyClass {
|
|
65
|
+
constructor(name: string, age: number)
|
|
66
|
+
|
|
67
|
+
name: string = "";
|
|
68
|
+
age: number = 0;
|
|
69
|
+
|
|
70
|
+
show_name() {
|
|
71
|
+
show_debug_message(this.name);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// interfaces and types will be converted to struct
|
|
76
|
+
export interface MyInterface {
|
|
77
|
+
name: string;
|
|
78
|
+
age: number = 0; // default value is allowed, which not allowed in TypeScript
|
|
79
|
+
isActive?: boolean; // optional property is allowed
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// interfaces can be extended
|
|
83
|
+
export interface MyExtInterface extends MyInterface {
|
|
84
|
+
address: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type MyType = {
|
|
88
|
+
name: string = ""; // default value is allowed, which not allowed in TypeScript
|
|
89
|
+
age: number;
|
|
90
|
+
isActive?: boolean; // optional property is allowed
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// types intersection is allowed
|
|
94
|
+
export type MyIntersectedType = MyType & {
|
|
95
|
+
address: string;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
You can do a barrel export (`export * from "<path>"`) as well, so that you can import modules from the parent directory:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
// index.ss
|
|
103
|
+
|
|
104
|
+
export * from "my_file"; // export all types from "my_file"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. Use `impl` statement to add implementation to a class.
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
// my_file.ss
|
|
111
|
+
|
|
112
|
+
export class MyClass {
|
|
113
|
+
constructor(name: string, age?: number)
|
|
114
|
+
|
|
115
|
+
name: string = "";
|
|
116
|
+
age: number = 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
impl MyClass {
|
|
120
|
+
show_age() {
|
|
121
|
+
show_debug_message(age);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
You can split the implementation into multiple files as well:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
// set.ss
|
|
130
|
+
|
|
131
|
+
impl MyClass {
|
|
132
|
+
set_age(age: number) {
|
|
133
|
+
age = age;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// static method
|
|
137
|
+
static static_method = (name: string) => {
|
|
138
|
+
show_debug_message($"Hello, {name}!");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Import Module
|
|
144
|
+
|
|
145
|
+
1. Use `include` statement to import a module from another Scaff file, and replace the import statement with the actual content of the exported types.
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
// my_other_file.ss
|
|
149
|
+
|
|
150
|
+
include { my_var, my_func, MyClass } from "my_file"
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The generated source code will replace the include statement with the actual content of the exported types.
|
|
154
|
+
*
|
|
155
|
+
* For example, the above include statement will be replaced with:
|
|
156
|
+
|
|
157
|
+
var my_var = 1;
|
|
158
|
+
function my_func() {
|
|
159
|
+
show_debug_message("Hello, World!");
|
|
160
|
+
}
|
|
161
|
+
function MyClass(name, age) constructor {
|
|
162
|
+
// ...
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
// non-included lines will be left as is
|
|
168
|
+
show_debug_message("This line will be left as is.");
|
|
169
|
+
|
|
170
|
+
// you can include multiple modules in a single file
|
|
171
|
+
include my_enum from "my_file"
|
|
172
|
+
/**
|
|
173
|
+
enum MY_ENUM {
|
|
174
|
+
A,
|
|
175
|
+
B,
|
|
176
|
+
C
|
|
177
|
+
}
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
// you can include normal GML files as well
|
|
181
|
+
include { "some_script.gml", "another_gml" } from "./scripts"
|
|
182
|
+
// must be in curly braces and double quotes, the order of the files will be preserved, the `.gml` extension is optional
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
2. Use `import` statement to import a module from another Scaff file, and then use `@<keyword>` statements to load and replace the content of a Scaff file to certain places in the code.
|
|
186
|
+
|
|
187
|
+
| Keyword | Description | Example |
|
|
188
|
+
| --- | --- | --- |
|
|
189
|
+
| `@content` | Replace the statement with the actual content of the exported type. | `@content my_var` -> `var my_var = 1;` |
|
|
190
|
+
| `@nameof` | Replace the statement with the **name** of the exported type. | `@nameof my_var` -> `"my_var"` |
|
|
191
|
+
| `@valueof` or `@:` | Replace the statement with the **value** of the exported type. | `@valueof my_var` -> `1`, `@:my_var` -> `1` |
|
|
192
|
+
| `@typeof` | Replace the statement with the **type** of the exported type. | `@typeof my_var` -> `"number"` |
|
|
193
|
+
| `@use` | Replace the statement with the object shape of the exported type. Only works with `interface` or `type`. | `var obj = @use MyInterface { name: "John" }` -> `var obj = { name: "John", age: 0, isActive: false };` |
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
// my_import.ss
|
|
197
|
+
|
|
198
|
+
import { my_var } from "my_file"
|
|
199
|
+
|
|
200
|
+
show_debug_message(@valueof my_var); // 1
|
|
201
|
+
show_debug_message(@:my_var); // 1
|
|
202
|
+
show_debug_message(@typeof my_var); // "number"
|
|
203
|
+
show_debug_message(@nameof my_var); // "my_var"
|
|
204
|
+
show_debug_message(@content my_var); // var my_var = 1;
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
// my_other_file.ss
|
|
209
|
+
|
|
210
|
+
import * from "my_file"
|
|
211
|
+
|
|
212
|
+
my_method = function() {
|
|
213
|
+
@content my_var; // replace with `var my_var = 1;`
|
|
214
|
+
var hello = @valueof MY_CONST; // replace with `var hello = "Hello, World!";`
|
|
215
|
+
var obj = @use MyInterface { // replace with `var obj = { name: "John", age: 0, isActive: false };`
|
|
216
|
+
name: "John"
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
show_debug_message(my_var); // 1
|
|
220
|
+
show_debug_message(obj.name); // John
|
|
221
|
+
|
|
222
|
+
var inst = new MyClass("John", 20);
|
|
223
|
+
inst.show_age(); // 20
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
> [!WARNING]
|
|
228
|
+
> If you use any of the `@` statements without importing the module first, the statement will be left as is, which won't be processed by Scaff, and won't be accepted by GameMaker. Even so, there are some exceptions for non-module `@` statements. Check the special `@` statements below (WIP).
|
|
229
|
+
|
|
230
|
+
| Keyword | Description | Example |
|
|
231
|
+
| --- | --- | --- |
|
|
232
|
+
| `@now` | Replace the statement with the current timestamp in ISO 8601 format. | `var now = "@now";` -> `var now = "2021-01-01T00:00:00.000Z";` |
|
|
233
|
+
| `@today` | Replace the statement with the current date in ISO 8601 format. | `var today = "@today";` -> `var today = "2021-01-01";` |
|
|
234
|
+
| `@version` | Replace the statement with the current version of Scaff. | `var version = "@version";` -> `var version = "0.1.0";` |
|
|
235
|
+
| `@file` | Replace the statement with the current file name. | `var file = "@file";` -> `var file = "my_file";` |
|
|
236
|
+
| `@line` | Replace the statement with the current line number. | `var line = @line;` -> `var line = 1;` |
|
|
237
|
+
| `@counter` | Replace the statement with an increasing number. | `var counter = @counter;` -> `var counter = 1;`, `var counter = @counter;` -> `var counter = 2;`, etc. |
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
### GameMaker Integration
|
|
241
|
+
|
|
242
|
+
Use `intg` statement to mark this file as an integration target, and `#[<name_or_event>]` statement to mark a block of code as a write target. The content of the file will be written to the actual GameMaker project.
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
// my_file.ss
|
|
246
|
+
|
|
247
|
+
intg { main, some_mod } to "./scripts/my_script" // integrate to `scripts/my_script/my_script.gml`
|
|
248
|
+
|
|
249
|
+
#[main]
|
|
250
|
+
show_debug_message("Hello, from my_script!");
|
|
251
|
+
|
|
252
|
+
#[some_mod]
|
|
253
|
+
show_debug_message("Hello, from my_script (some_mod)!");
|
|
254
|
+
|
|
255
|
+
#[some_mod -- prod]
|
|
256
|
+
show_debug_message("Hello, only in production!");
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
// my_other_file.ss
|
|
261
|
+
|
|
262
|
+
intg * to "objects/my_object" // integrate to `objects/my_object/*`
|
|
263
|
+
intg { Step, keydown:keyboard_d } to "objects/my_other_object"
|
|
264
|
+
|
|
265
|
+
// method 1
|
|
266
|
+
#[main as create] // integrate to `objects/my_object/Create_0.gml`
|
|
267
|
+
show_debug_message("Hello, from my_object create event!");
|
|
268
|
+
|
|
269
|
+
// method 2
|
|
270
|
+
#[StepEvent] // <event_name>Event is also supported
|
|
271
|
+
show_debug_message("Hello, from my_object step event!");
|
|
272
|
+
|
|
273
|
+
// method 3
|
|
274
|
+
#[key as KeyPress:KEYBOARD_ENTER] // use `:` to specify the event number KEYBOARD_ENTER in this case)
|
|
275
|
+
show_debug_message("Hello, from my_object keypress - enter event!");
|
|
276
|
+
|
|
277
|
+
// method 4
|
|
278
|
+
#[keydown:keyboard_d event] // the `event`, event type, and event number are case insensitive, add `event` suffix to mark as event (like in method 2 example)
|
|
279
|
+
show_debug_message("Hello, from my_object keydown - d event!");
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
> [!NOTE]
|
|
283
|
+
> If you're using method 2 or 4, the `event` keyword is required to mark the block as an event.
|
|
284
|
+
> The `event` keyword will be omitted in the block name, so `#[StepEvent]` will become `Step` in the integration block (example: `intg { Step } to "objects/my_object"`, notice no `Event` suffix).
|
|
285
|
+
> If you're creating (not modifying) a new **collision** event, you need to specify the name of the other object (case sensitive, exact name of the object in the IDE, such as `obj_player`). And you need to reopen the IDE if you're creating a new collision event using Scaff integration.
|
|
286
|
+
|
|
287
|
+
### Configuration
|
|
288
|
+
|
|
289
|
+
Create a `scaff.config.<ts\|mjs\|cjs|json>` file in the root of your project with the following content:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
// scaff.config.ts
|
|
293
|
+
|
|
294
|
+
export default {
|
|
295
|
+
// ...
|
|
296
|
+
};
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
```js
|
|
300
|
+
// scaff.config.mjs
|
|
301
|
+
|
|
302
|
+
export default {
|
|
303
|
+
// ...
|
|
304
|
+
};
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
// scaff.config.cjs
|
|
309
|
+
|
|
310
|
+
module.exports = {
|
|
311
|
+
// ...
|
|
312
|
+
};
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
| Option | Type | Default | Description |
|
|
316
|
+
| --- | --- | --- | --- |
|
|
317
|
+
| `acceptAllIntegrations` | `boolean` | `false` | Accept all generated files to be integrated without manual confirmation. |
|
|
318
|
+
| `clearOutputDir` | `boolean` | `false` | Clear the output directory before generating source code. |
|
|
319
|
+
| `counterStart` | `number` | `1` | Starting value for the counter special value. |
|
|
320
|
+
| `debugLevel` | `0 \| 1 \| 2` | `0` | Debug level. `0` = no debug, `1` = basic debug, `2` = verbose debug. |
|
|
321
|
+
| `integrationOption` | `ScaffIntegrationOptions` | `{}` | Integration options. |
|
|
322
|
+
| `noBackup` | `boolean` | `false` | Don't backup the original files before integration. |
|
|
323
|
+
| `noIntegration` | `boolean` | `false` | Don't integrate the files to GM project. |
|
|
324
|
+
| `onNotFound` | `"error" \| "ignore"` | `"error"` | What to do when something is not found. |
|
|
325
|
+
| `path` | `Record<string, string>` | `{}` | Path aliases. |
|
|
326
|
+
| `production` | `boolean` | `false` | Whether the script is running in production mode. |
|
|
327
|
+
| `tabType` | `"1t" \| "2s" \| "4s"` | `"1t"` | Tab type to use when generating source code. |
|
|
328
|
+
| `targetPlatform` | `ScaffIntegrationTargetPlatform` | `"all"` | Target platform for the generated code. |
|
|
329
|
+
| `useGmAssetPath` | `boolean` | `false` | Whether to use GM asset path when integrating files. |
|