lt-script 1.0.1 → 1.0.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 +256 -15
- package/dist/cli/ltc.js +44 -34
- package/dist/cli/utils.d.ts +40 -0
- package/dist/cli/utils.js +40 -0
- package/dist/compiler/codegen/LuaEmitter.js +19 -3
- package/dist/compiler/lexer/Lexer.js +7 -2
- package/dist/compiler/lexer/Token.d.ts +2 -1
- package/dist/compiler/lexer/Token.js +6 -2
- package/dist/compiler/parser/AST.d.ts +12 -0
- package/dist/compiler/parser/AST.js +2 -0
- package/dist/compiler/parser/Parser.d.ts +4 -0
- package/dist/compiler/parser/Parser.js +141 -26
- package/dist/compiler/semantics/SemanticAnalyzer.d.ts +23 -0
- package/dist/compiler/semantics/SemanticAnalyzer.js +411 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +9 -1
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -1,33 +1,274 @@
|
|
|
1
|
-
# LT Language
|
|
1
|
+
# LT Language
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/npm/v/lt-script?color=blue&label=npm" alt="npm version">
|
|
5
|
+
<img src="https://img.shields.io/github/license/laot7490/lt-script" alt="license">
|
|
6
|
+
<img src="https://img.shields.io/badge/FiveM-Ready-green" alt="FiveM Ready">
|
|
7
|
+
</p>
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
**LT** is a modern, developer-friendly language that compiles to FiveM Lua. It brings TypeScript-like syntax, modern JavaScript features, and FiveM-specific sugar to the Lua ecosystem — making your scripts cleaner, safer, and more enjoyable to write.
|
|
10
|
+
|
|
11
|
+
## ✨ Why LT?
|
|
12
|
+
|
|
13
|
+
Writing vanilla Lua for FiveM can be tedious. LT fixes that with:
|
|
14
|
+
|
|
15
|
+
- **Modern Syntax**: Arrow functions, destructuring, spread operator, optional chaining
|
|
16
|
+
- **FiveM Sugar**: Built-in `thread`, `wait`, `netevent`, `addcmd`, `export` keywords
|
|
17
|
+
- **Type Safety**: Static type checking catches errors at compile time
|
|
18
|
+
- **Clean Code**: No more boilerplate — just write what you mean
|
|
19
|
+
|
|
20
|
+
## 📦 Installation
|
|
6
21
|
|
|
7
22
|
```bash
|
|
8
23
|
npm install -g lt-script
|
|
9
24
|
```
|
|
10
25
|
|
|
11
|
-
##
|
|
26
|
+
## 🚀 Quick Start
|
|
12
27
|
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
Create a file called `main.lt`:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Variables with modern scoping
|
|
32
|
+
let health: number = 100
|
|
33
|
+
const MAX_HEALTH = 200
|
|
34
|
+
|
|
35
|
+
// Arrow functions
|
|
36
|
+
let double = (x) => x * 2
|
|
37
|
+
|
|
38
|
+
// String interpolation
|
|
39
|
+
print("Health: $health / $MAX_HEALTH")
|
|
40
|
+
|
|
41
|
+
// FiveM thread with wait
|
|
42
|
+
thread
|
|
43
|
+
loop (true)
|
|
44
|
+
wait 1000
|
|
45
|
+
print("Tick...")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
// Register a command easily
|
|
50
|
+
addcmd "heal" (source, args)
|
|
51
|
+
health = MAX_HEALTH
|
|
52
|
+
print("Player healed!")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
// Network events without boilerplate
|
|
56
|
+
netevent "player:spawn" (data)
|
|
57
|
+
print("Player spawned: $data")
|
|
58
|
+
end
|
|
15
59
|
```
|
|
16
60
|
|
|
17
|
-
|
|
61
|
+
Compile it:
|
|
18
62
|
|
|
19
63
|
```bash
|
|
20
|
-
ltc build
|
|
64
|
+
ltc build .
|
|
21
65
|
```
|
|
22
66
|
|
|
23
|
-
|
|
67
|
+
Output `main.lua`:
|
|
68
|
+
```lua
|
|
69
|
+
local health = 100
|
|
70
|
+
local MAX_HEALTH <const> = 200
|
|
24
71
|
|
|
25
|
-
|
|
26
|
-
|
|
72
|
+
local double = function(x) return x * 2 end
|
|
73
|
+
|
|
74
|
+
print("Health: " .. tostring(health) .. " / " .. tostring(MAX_HEALTH))
|
|
75
|
+
|
|
76
|
+
CreateThread(function()
|
|
77
|
+
while true do
|
|
78
|
+
Wait(1000)
|
|
79
|
+
print("Tick...")
|
|
80
|
+
end
|
|
81
|
+
end)
|
|
82
|
+
|
|
83
|
+
RegisterCommand("heal", function(source, args)
|
|
84
|
+
health = MAX_HEALTH
|
|
85
|
+
print("Player healed!")
|
|
86
|
+
end, false)
|
|
87
|
+
|
|
88
|
+
RegisterNetEvent("player:spawn")
|
|
89
|
+
AddEventHandler("player:spawn", function(data)
|
|
90
|
+
print("Player spawned: " .. tostring(data))
|
|
91
|
+
end)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 🛠️ CLI Commands
|
|
95
|
+
|
|
96
|
+
| Command | Description |
|
|
97
|
+
|---------|-------------|
|
|
98
|
+
| `ltc build <dir>` | Compile all `.lt` files in directory |
|
|
99
|
+
| `ltc watch <dir>` | Watch mode — auto-compile on save |
|
|
100
|
+
|
|
101
|
+
## 🎯 Feature Highlights
|
|
102
|
+
|
|
103
|
+
### Variables & Constants
|
|
104
|
+
```typescript
|
|
105
|
+
var globalVar = "I am global" // Global variable
|
|
106
|
+
let localVar = 10 // Local variable
|
|
107
|
+
const MAX = 100 // Constant (Lua 5.4 <const>)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Type System
|
|
111
|
+
```typescript
|
|
112
|
+
// Explicit types
|
|
113
|
+
let name: string = "John"
|
|
114
|
+
let age: number = 25
|
|
115
|
+
let active: boolean = true
|
|
116
|
+
|
|
117
|
+
// Type Aliases
|
|
118
|
+
interface PlayerData = {
|
|
119
|
+
name: string,
|
|
120
|
+
level: number,
|
|
121
|
+
isVip: boolean
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func Calculate(a: number, b: number)
|
|
125
|
+
return a + b
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Arrow Functions
|
|
130
|
+
```typescript
|
|
131
|
+
let add = (a, b) => a + b
|
|
132
|
+
let greet = (name) => print("Hello, $name!")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Destructuring
|
|
136
|
+
```typescript
|
|
137
|
+
let player = { name: "Alex", level: 50 }
|
|
138
|
+
let { name, level } = player
|
|
139
|
+
|
|
140
|
+
let coords = [100, 200, 300]
|
|
141
|
+
let [x, y, z] = coords
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Optional Chaining & Null Coalescing
|
|
145
|
+
```typescript
|
|
146
|
+
let job = player?.metadata?.job?.label ?? "Unemployed"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Vector Literals
|
|
150
|
+
```typescript
|
|
151
|
+
let pos = <100.0, 50.0, 20.0> // vector3
|
|
152
|
+
let rot = <0.0, 90.0> // vector2
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Switch/Case
|
|
156
|
+
```typescript
|
|
157
|
+
switch weather
|
|
158
|
+
case "RAIN", "THUNDER"
|
|
159
|
+
print("Stay inside!")
|
|
160
|
+
case "CLEAR"
|
|
161
|
+
print("Nice day!")
|
|
162
|
+
default
|
|
163
|
+
print("Unknown weather")
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### For Loops
|
|
168
|
+
```typescript
|
|
169
|
+
// Range loop
|
|
170
|
+
for i in 1..10 do
|
|
171
|
+
print(i)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
// Range with step
|
|
175
|
+
for i in 10..1 by -1 do
|
|
176
|
+
print(i)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
// C-style loop
|
|
180
|
+
for i = 0, 100, 5 do
|
|
181
|
+
print(i)
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### FiveM Specific
|
|
186
|
+
|
|
187
|
+
#### Threads & Wait
|
|
188
|
+
```typescript
|
|
189
|
+
thread
|
|
190
|
+
wait 1000
|
|
191
|
+
print("After 1 second")
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Events
|
|
196
|
+
```typescript
|
|
197
|
+
netevent "myResource:sync" (data)
|
|
198
|
+
-- RegisterNetEvent + AddEventHandler
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
event "localEvent" (data)
|
|
202
|
+
-- AddEventHandler only
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### Commands
|
|
207
|
+
```typescript
|
|
208
|
+
addcmd "teleport" (source, args)
|
|
209
|
+
print("Teleporting...")
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Exports
|
|
214
|
+
```typescript
|
|
215
|
+
export func GetPlayerData(id)
|
|
216
|
+
return players[id]
|
|
217
|
+
end
|
|
27
218
|
```
|
|
28
219
|
|
|
29
|
-
|
|
220
|
+
#### Guard Statements
|
|
221
|
+
```typescript
|
|
222
|
+
guard player return // Early return if nil/false
|
|
223
|
+
guard player.job == "police" return print("Access denied")
|
|
224
|
+
|
|
225
|
+
// Guard with else block
|
|
226
|
+
guard isValid else
|
|
227
|
+
print("Invalid state")
|
|
228
|
+
return
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### Try/Catch
|
|
233
|
+
```typescript
|
|
234
|
+
try
|
|
235
|
+
riskyOperation()
|
|
236
|
+
catch err
|
|
237
|
+
print("Error: $err")
|
|
238
|
+
end
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## 📁 Project Structure
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
my-resource/
|
|
245
|
+
├── src/
|
|
246
|
+
│ ├── client.lt
|
|
247
|
+
│ └── server.lt
|
|
248
|
+
├── client.lua (generated)
|
|
249
|
+
├── server.lua (generated)
|
|
250
|
+
└── fxmanifest.lua
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Run `ltc watch .` in your resource folder for automatic compilation.
|
|
254
|
+
|
|
255
|
+
## 🔧 VS Code Extension
|
|
256
|
+
|
|
257
|
+
Get syntax highlighting and IntelliSense for `.lt` files:
|
|
258
|
+
|
|
259
|
+
**[LT Language Extension](https://marketplace.visualstudio.com/items?itemName=laot.lt-language)**
|
|
260
|
+
|
|
261
|
+
- Syntax Highlighting
|
|
262
|
+
- IntelliSense for Variables & Functions
|
|
263
|
+
- **Native FiveM Autocompletion** (GetHashKey, CreateVehicle, etc.)
|
|
264
|
+
- Snippets for common patterns
|
|
265
|
+
|
|
266
|
+
## 📜 License
|
|
267
|
+
|
|
268
|
+
MIT © [LaotScripts](https://github.com/laot7490)
|
|
269
|
+
|
|
270
|
+
---
|
|
30
271
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
272
|
+
<p align="center">
|
|
273
|
+
Made with ❤️ for the FiveM community
|
|
274
|
+
</p>
|
package/dist/cli/ltc.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { compile } from '../index.js';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import { getAllFiles, ensureDirectoryExistence } from './utils.js';
|
|
5
|
+
import { getAllFiles, ensureDirectoryExistence, style } from './utils.js';
|
|
6
6
|
const args = process.argv.slice(2);
|
|
7
7
|
const command = args[0];
|
|
8
8
|
if (!command) {
|
|
@@ -17,52 +17,59 @@ switch (command) {
|
|
|
17
17
|
handleBuild(args.slice(1));
|
|
18
18
|
break;
|
|
19
19
|
default:
|
|
20
|
-
console.error(
|
|
20
|
+
console.error(style.red(`\n[LT] Error: Unknown command '${command}'`));
|
|
21
21
|
printUsage();
|
|
22
22
|
process.exit(1);
|
|
23
23
|
}
|
|
24
24
|
function printUsage() {
|
|
25
25
|
console.log(`
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
ltc
|
|
26
|
+
${style.bold('LT Language Compiler')} ${style.gray('(v1.0.0)')}
|
|
27
|
+
${style.gray('----------------------------------------')}
|
|
28
|
+
|
|
29
|
+
${style.bold('Usage:')}
|
|
30
|
+
${style.cyan('ltc watch')} ${style.yellow('<targetDir>')}
|
|
31
|
+
${style.cyan('ltc watch')} ${style.yellow('<srcDir> <outDir>')}
|
|
32
|
+
${style.cyan('ltc build')} ${style.yellow('<targetDir>')}
|
|
33
|
+
${style.cyan('ltc build')} ${style.yellow('<srcDir> <outDir>')}
|
|
34
|
+
|
|
35
|
+
${style.bold('Examples:')}
|
|
36
|
+
${style.gray('$')} ltc watch lt-fuel
|
|
37
|
+
${style.gray('$')} ltc build src dist
|
|
31
38
|
`);
|
|
32
39
|
}
|
|
33
40
|
function parsePaths(cmdArgs) {
|
|
34
|
-
if (cmdArgs.length === 0)
|
|
41
|
+
if (cmdArgs.length === 0) {
|
|
42
|
+
console.error(style.red(`\n[LT] Error: Missing arguments.`));
|
|
43
|
+
printUsage();
|
|
35
44
|
return null;
|
|
45
|
+
}
|
|
36
46
|
let srcDir = '';
|
|
37
47
|
let outDir = '';
|
|
38
48
|
const target = cmdArgs[0];
|
|
39
49
|
if (cmdArgs.length === 1) {
|
|
40
|
-
// Convention mode: ltc watch lt-fuel
|
|
41
50
|
const potentialSrc = path.join(target, 'src');
|
|
42
51
|
if (fs.existsSync(potentialSrc) && fs.statSync(potentialSrc).isDirectory()) {
|
|
43
52
|
srcDir = potentialSrc;
|
|
44
|
-
outDir =
|
|
45
|
-
console.log(
|
|
53
|
+
outDir = target;
|
|
54
|
+
console.log(`${style.blue('[LT]')} Auto-detected project structure:`);
|
|
55
|
+
console.log(` ${style.gray('Source:')} ${srcDir}`);
|
|
56
|
+
console.log(` ${style.gray('Output:')} ${outDir}`);
|
|
46
57
|
}
|
|
47
58
|
else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
console.error(`[LT] Error: Could not find 'src' directory in '${target}'.`);
|
|
52
|
-
console.error(` Please use 'ltc ${command} <sourceDir> <outDir>' for explicit paths.`);
|
|
59
|
+
console.error(style.red(`\n[LT] Error: Could not find 'src' directory in '${target}'.`));
|
|
60
|
+
console.error(` Expected: ${potentialSrc}`);
|
|
61
|
+
console.error(` To specify custom paths, use: ${style.cyan('ltc ' + command + ' <srcDir> <outDir>')}`);
|
|
53
62
|
return null;
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
else {
|
|
57
|
-
// Explicit mode
|
|
58
66
|
srcDir = cmdArgs[0];
|
|
59
67
|
outDir = cmdArgs[1];
|
|
60
68
|
}
|
|
61
|
-
// Resolve to absolute
|
|
62
69
|
srcDir = path.resolve(srcDir);
|
|
63
70
|
outDir = path.resolve(outDir);
|
|
64
71
|
if (!fs.existsSync(srcDir)) {
|
|
65
|
-
console.error(
|
|
72
|
+
console.error(style.red(`\n[LT] Error: Source directory '${srcDir}' does not exist.`));
|
|
66
73
|
return null;
|
|
67
74
|
}
|
|
68
75
|
return { srcDir, outDir };
|
|
@@ -71,64 +78,67 @@ function compileFile(srcPath, srcRoot, outRoot) {
|
|
|
71
78
|
try {
|
|
72
79
|
const relative = path.relative(srcRoot, srcPath);
|
|
73
80
|
const outPath = path.join(outRoot, relative.replace(/\.lt$/, '.lua'));
|
|
74
|
-
|
|
81
|
+
process.stdout.write(`${style.blue('[LT]')} Compiling ${style.yellow(relative)}... `);
|
|
75
82
|
const content = fs.readFileSync(srcPath, 'utf-8');
|
|
76
83
|
const start = performance.now();
|
|
77
84
|
const lua = compile(content);
|
|
78
85
|
const end = performance.now();
|
|
79
86
|
ensureDirectoryExistence(outPath);
|
|
80
87
|
fs.writeFileSync(outPath, lua, 'utf8');
|
|
81
|
-
console.log(
|
|
88
|
+
console.log(style.green(`✓ ${(end - start).toFixed(0)}ms`));
|
|
82
89
|
}
|
|
83
90
|
catch (e) {
|
|
84
|
-
console.
|
|
85
|
-
console.error(
|
|
91
|
+
console.log(style.red('✗'));
|
|
92
|
+
console.error(`${style.red('[LT] Error compiling')} ${style.bold(srcPath)}:`);
|
|
93
|
+
console.error(style.yellow(e.message));
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
96
|
function handleBuild(cmdArgs) {
|
|
97
|
+
console.log(style.bold(`\nStarting Build...`));
|
|
89
98
|
const paths = parsePaths(cmdArgs);
|
|
90
99
|
if (!paths)
|
|
91
100
|
process.exit(1);
|
|
92
101
|
const { srcDir, outDir } = paths;
|
|
93
|
-
console.log(`[LT] Building from '${srcDir}' to '${outDir}'...`);
|
|
94
102
|
const files = getAllFiles(srcDir, '.lt');
|
|
103
|
+
if (files.length === 0) {
|
|
104
|
+
console.log(style.yellow(`[LT] No .lt files found in ${srcDir}`));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
95
107
|
files.forEach(file => compileFile(file, srcDir, outDir));
|
|
96
|
-
console.log(
|
|
108
|
+
console.log(style.green(`\n[LT] Build complete. ${files.length} files processed.`));
|
|
97
109
|
}
|
|
98
110
|
function handleWatch(cmdArgs) {
|
|
111
|
+
console.log(style.bold(`\nStarting Watch Mode...`));
|
|
99
112
|
const paths = parsePaths(cmdArgs);
|
|
100
113
|
if (!paths)
|
|
101
114
|
process.exit(1);
|
|
102
115
|
const { srcDir, outDir } = paths;
|
|
103
|
-
// Initial build
|
|
104
116
|
handleBuild(cmdArgs);
|
|
105
|
-
console.log(
|
|
117
|
+
console.log(style.blue(`\n[LT] Watching for changes in '${srcDir}'...`));
|
|
118
|
+
console.log(style.gray(` (Press Ctrl+C to stop)`));
|
|
106
119
|
let fsWait = null;
|
|
107
|
-
// Recursive watch
|
|
108
120
|
fs.watch(srcDir, { recursive: true }, (event, filename) => {
|
|
109
121
|
if (!filename)
|
|
110
122
|
return;
|
|
111
|
-
// Windows/Mac returning filename
|
|
112
123
|
if (!filename.endsWith('.lt'))
|
|
113
|
-
return;
|
|
124
|
+
return;
|
|
114
125
|
const fullPath = path.join(srcDir, filename);
|
|
115
126
|
if (fsWait)
|
|
116
127
|
clearTimeout(fsWait);
|
|
117
128
|
fsWait = setTimeout(() => {
|
|
118
129
|
fsWait = null;
|
|
119
130
|
if (fs.existsSync(fullPath)) {
|
|
120
|
-
|
|
131
|
+
console.log(style.gray(`\n[LT] File changed: ${filename}`));
|
|
121
132
|
compileFile(fullPath, srcDir, outDir);
|
|
122
133
|
}
|
|
123
134
|
else {
|
|
124
|
-
// Deleted
|
|
125
135
|
const relative = path.relative(srcDir, fullPath);
|
|
126
136
|
const outPath = path.join(outDir, relative.replace(/\.lt$/, '.lua'));
|
|
127
137
|
if (fs.existsSync(outPath)) {
|
|
128
138
|
fs.unlinkSync(outPath);
|
|
129
|
-
console.log(`[LT] Removed: ${relative.replace(/\.lt$/, '.lua')}`);
|
|
139
|
+
console.log(style.red(`[LT] Removed: ${relative.replace(/\.lt$/, '.lua')}`));
|
|
130
140
|
}
|
|
131
141
|
}
|
|
132
|
-
}, 100);
|
|
142
|
+
}, 100);
|
|
133
143
|
});
|
|
134
144
|
}
|
package/dist/cli/utils.d.ts
CHANGED
|
@@ -1,2 +1,42 @@
|
|
|
1
1
|
export declare function getAllFiles(dir: string, extension: string, fileList?: string[]): string[];
|
|
2
2
|
export declare function ensureDirectoryExistence(filePath: string): void;
|
|
3
|
+
export declare const colors: {
|
|
4
|
+
reset: string;
|
|
5
|
+
bright: string;
|
|
6
|
+
dim: string;
|
|
7
|
+
underscore: string;
|
|
8
|
+
blink: string;
|
|
9
|
+
reverse: string;
|
|
10
|
+
hidden: string;
|
|
11
|
+
fg: {
|
|
12
|
+
black: string;
|
|
13
|
+
red: string;
|
|
14
|
+
green: string;
|
|
15
|
+
yellow: string;
|
|
16
|
+
blue: string;
|
|
17
|
+
magenta: string;
|
|
18
|
+
cyan: string;
|
|
19
|
+
white: string;
|
|
20
|
+
gray: string;
|
|
21
|
+
};
|
|
22
|
+
bg: {
|
|
23
|
+
black: string;
|
|
24
|
+
red: string;
|
|
25
|
+
green: string;
|
|
26
|
+
yellow: string;
|
|
27
|
+
blue: string;
|
|
28
|
+
magenta: string;
|
|
29
|
+
cyan: string;
|
|
30
|
+
white: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export declare const style: {
|
|
34
|
+
green: (text: string) => string;
|
|
35
|
+
red: (text: string) => string;
|
|
36
|
+
yellow: (text: string) => string;
|
|
37
|
+
blue: (text: string) => string;
|
|
38
|
+
cyan: (text: string) => string;
|
|
39
|
+
gray: (text: string) => string;
|
|
40
|
+
bold: (text: string) => string;
|
|
41
|
+
white: (text: string) => string;
|
|
42
|
+
};
|
package/dist/cli/utils.js
CHANGED
|
@@ -24,3 +24,43 @@ export function ensureDirectoryExistence(filePath) {
|
|
|
24
24
|
ensureDirectoryExistence(dirname);
|
|
25
25
|
fs.mkdirSync(dirname);
|
|
26
26
|
}
|
|
27
|
+
export const colors = {
|
|
28
|
+
reset: "\x1b[0m",
|
|
29
|
+
bright: "\x1b[1m",
|
|
30
|
+
dim: "\x1b[2m",
|
|
31
|
+
underscore: "\x1b[4m",
|
|
32
|
+
blink: "\x1b[5m",
|
|
33
|
+
reverse: "\x1b[7m",
|
|
34
|
+
hidden: "\x1b[8m",
|
|
35
|
+
fg: {
|
|
36
|
+
black: "\x1b[30m",
|
|
37
|
+
red: "\x1b[31m",
|
|
38
|
+
green: "\x1b[32m",
|
|
39
|
+
yellow: "\x1b[33m",
|
|
40
|
+
blue: "\x1b[34m",
|
|
41
|
+
magenta: "\x1b[35m",
|
|
42
|
+
cyan: "\x1b[36m",
|
|
43
|
+
white: "\x1b[37m",
|
|
44
|
+
gray: "\x1b[90m",
|
|
45
|
+
},
|
|
46
|
+
bg: {
|
|
47
|
+
black: "\x1b[40m",
|
|
48
|
+
red: "\x1b[41m",
|
|
49
|
+
green: "\x1b[42m",
|
|
50
|
+
yellow: "\x1b[43m",
|
|
51
|
+
blue: "\x1b[44m",
|
|
52
|
+
magenta: "\x1b[45m",
|
|
53
|
+
cyan: "\x1b[46m",
|
|
54
|
+
white: "\x1b[47m",
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export const style = {
|
|
58
|
+
green: (text) => `${colors.fg.green}${text}${colors.reset}`,
|
|
59
|
+
red: (text) => `${colors.fg.red}${text}${colors.reset}`,
|
|
60
|
+
yellow: (text) => `${colors.fg.yellow}${text}${colors.reset}`,
|
|
61
|
+
blue: (text) => `${colors.fg.blue}${text}${colors.reset}`,
|
|
62
|
+
cyan: (text) => `${colors.fg.cyan}${text}${colors.reset}`,
|
|
63
|
+
gray: (text) => `${colors.fg.gray}${text}${colors.reset}`,
|
|
64
|
+
bold: (text) => `${colors.bright}${text}${colors.reset}`,
|
|
65
|
+
white: (text) => `${colors.fg.white}${text}${colors.reset}`,
|
|
66
|
+
};
|
|
@@ -148,6 +148,9 @@ export class LuaEmitter {
|
|
|
148
148
|
case AST.NodeType.CommandStmt:
|
|
149
149
|
this.emitCommandStmt(stmt);
|
|
150
150
|
break;
|
|
151
|
+
case AST.NodeType.TypeDecl:
|
|
152
|
+
// Type declarations are compile-time only, no Lua output
|
|
153
|
+
break;
|
|
151
154
|
default:
|
|
152
155
|
// Expression statement
|
|
153
156
|
// Optimize UpdateExpr used as statement (i++)
|
|
@@ -299,7 +302,13 @@ export class LuaEmitter {
|
|
|
299
302
|
this.emitStatement(s);
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
|
-
|
|
305
|
+
// Emit return with optional value
|
|
306
|
+
if (stmt.returnValue) {
|
|
307
|
+
this.line(`return ${this.emitExpr(stmt.returnValue)}`);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
this.line('return');
|
|
311
|
+
}
|
|
303
312
|
this.indent--;
|
|
304
313
|
this.line('end');
|
|
305
314
|
}
|
|
@@ -509,11 +518,18 @@ export class LuaEmitter {
|
|
|
509
518
|
let op = expr.operator;
|
|
510
519
|
if (op === '!=')
|
|
511
520
|
op = '~=';
|
|
521
|
+
if (op === '&&')
|
|
522
|
+
op = 'and';
|
|
523
|
+
if (op === '||')
|
|
524
|
+
op = 'or';
|
|
512
525
|
return `(${left} ${op} ${right})`;
|
|
513
526
|
}
|
|
514
527
|
emitUnaryExpr(expr) {
|
|
515
|
-
|
|
516
|
-
|
|
528
|
+
let op = expr.operator;
|
|
529
|
+
if (op === '!')
|
|
530
|
+
op = 'not';
|
|
531
|
+
const space = (op === 'not' || op === 'and' || op === 'or') ? ' ' : '';
|
|
532
|
+
return `${op}${space}${this.emitExpr(expr.operand)}`;
|
|
517
533
|
}
|
|
518
534
|
emitCallExpr(expr) {
|
|
519
535
|
const callee = this.emitExpr(expr.callee);
|
|
@@ -7,10 +7,12 @@ export class Lexer {
|
|
|
7
7
|
source;
|
|
8
8
|
tokens = [];
|
|
9
9
|
pos = 0;
|
|
10
|
-
line
|
|
11
|
-
column
|
|
10
|
+
line;
|
|
11
|
+
column;
|
|
12
12
|
constructor(source) {
|
|
13
13
|
this.source = source;
|
|
14
|
+
this.line = 1;
|
|
15
|
+
this.column = 1;
|
|
14
16
|
}
|
|
15
17
|
tokenize() {
|
|
16
18
|
while (!this.isEOF()) {
|
|
@@ -75,6 +77,8 @@ export class Lexer {
|
|
|
75
77
|
// Two-char operators
|
|
76
78
|
const twoChar = ch + next;
|
|
77
79
|
const twoCharMap = {
|
|
80
|
+
'&&': TokenType.AND,
|
|
81
|
+
'||': TokenType.OR,
|
|
78
82
|
'=>': TokenType.ARROW,
|
|
79
83
|
'?.': TokenType.OPT_DOT,
|
|
80
84
|
'?[': TokenType.OPT_BRACKET,
|
|
@@ -120,6 +124,7 @@ export class Lexer {
|
|
|
120
124
|
'.': TokenType.DOT,
|
|
121
125
|
':': TokenType.COLON,
|
|
122
126
|
';': TokenType.SEMICOLON,
|
|
127
|
+
'!': TokenType.NOT,
|
|
123
128
|
'?': TokenType.QUESTION,
|
|
124
129
|
};
|
|
125
130
|
if (singleCharMap[ch]) {
|
|
@@ -55,7 +55,9 @@ export var TokenType;
|
|
|
55
55
|
TokenType["EVENT"] = "EVENT";
|
|
56
56
|
TokenType["NETEVENT"] = "NETEVENT";
|
|
57
57
|
TokenType["EXPORT"] = "EXPORT";
|
|
58
|
-
TokenType["
|
|
58
|
+
TokenType["ADDCMD"] = "ADDCMD";
|
|
59
|
+
// Type System
|
|
60
|
+
TokenType["INTERFACE"] = "INTERFACE";
|
|
59
61
|
// Logical
|
|
60
62
|
TokenType["AND"] = "AND";
|
|
61
63
|
TokenType["OR"] = "OR";
|
|
@@ -157,7 +159,9 @@ export const KEYWORDS = {
|
|
|
157
159
|
event: TokenType.EVENT,
|
|
158
160
|
netevent: TokenType.NETEVENT,
|
|
159
161
|
export: TokenType.EXPORT,
|
|
160
|
-
|
|
162
|
+
addcmd: TokenType.ADDCMD,
|
|
163
|
+
// Type System
|
|
164
|
+
interface: TokenType.INTERFACE,
|
|
161
165
|
// Logical
|
|
162
166
|
and: TokenType.AND,
|
|
163
167
|
or: TokenType.OR,
|
|
@@ -28,6 +28,7 @@ export declare enum NodeType {
|
|
|
28
28
|
EmitStmt = "EmitStmt",
|
|
29
29
|
EventHandler = "EventHandler",
|
|
30
30
|
ExportDecl = "ExportDecl",
|
|
31
|
+
TypeDecl = "TypeDecl",
|
|
31
32
|
BinaryExpr = "BinaryExpr",
|
|
32
33
|
UnaryExpr = "UnaryExpr",
|
|
33
34
|
UpdateExpr = "UpdateExpr",// ++, --
|
|
@@ -83,10 +84,20 @@ export interface Parameter {
|
|
|
83
84
|
typeAnnotation?: string;
|
|
84
85
|
defaultValue?: Expression;
|
|
85
86
|
}
|
|
87
|
+
export interface TypeField {
|
|
88
|
+
name: string;
|
|
89
|
+
type: string;
|
|
90
|
+
}
|
|
91
|
+
export interface TypeDecl extends Statement {
|
|
92
|
+
kind: NodeType.TypeDecl;
|
|
93
|
+
name: Identifier;
|
|
94
|
+
fields: TypeField[];
|
|
95
|
+
}
|
|
86
96
|
export interface AssignmentStmt extends Statement {
|
|
87
97
|
kind: NodeType.AssignmentStmt;
|
|
88
98
|
targets: Expression[];
|
|
89
99
|
values: Expression[];
|
|
100
|
+
typeAnnotation?: string;
|
|
90
101
|
}
|
|
91
102
|
export interface CompoundAssignment extends Statement {
|
|
92
103
|
kind: NodeType.CompoundAssignment;
|
|
@@ -151,6 +162,7 @@ export interface GuardStmt extends Statement {
|
|
|
151
162
|
kind: NodeType.GuardStmt;
|
|
152
163
|
condition: Expression;
|
|
153
164
|
elseBody?: Statement[];
|
|
165
|
+
returnValue?: Expression;
|
|
154
166
|
}
|
|
155
167
|
export interface SafeCallStmt extends Statement {
|
|
156
168
|
kind: NodeType.SafeCallStmt;
|