Skip to content

Commit 88279c1

Browse files
committed
Ooops! run fix:prettier
1 parent a3e1c3f commit 88279c1

11 files changed

Lines changed: 967 additions & 985 deletions

File tree

README.md

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,57 +24,64 @@
2424
The main goal of this fork is to bring **Kotlin-style zero-overhead `@inline` functions** to TypeScriptToLua. Unlike experimental community plugins, this feature is baked directly into the compiler engine, supporting deep cross-file analysis and complex control flows.
2525

2626
### 1. Cross-File Macro Inlining
27-
* Functions marked with JSDoc `/** @inline */` are completely unwrapped into the calling expression/statement.
28-
* Works seamlessly across different files and modules via strict TS Symbol aliasing resolution.
27+
28+
- Functions marked with JSDoc `/** @inline */` are completely unwrapped into the calling expression/statement.
29+
- Works seamlessly across different files and modules via strict TS Symbol aliasing resolution.
2930

3031
### 2. High-Order Functions & Lambda Optimization
31-
* Fully supports inlining functions that accept other functions (lambdas/callbacks) as arguments (e.g., custom `filter`, `map`, `forEach`).
32-
* Arrow functions and inline expressions are embedded into loops and conditions without creating closures or allocating anonymous tables in Lua.
32+
33+
- Fully supports inlining functions that accept other functions (lambdas/callbacks) as arguments (e.g., custom `filter`, `map`, `forEach`).
34+
- Arrow functions and inline expressions are embedded into loops and conditions without creating closures or allocating anonymous tables in Lua.
3335

3436
### 3. Adaptive Control Flow (Target-Specific Compilation)
35-
* **Lua 5.2+, JIT, Luau**: Translates deep `return` statements in complex multi-return lambdas into high-performance native `goto` jumps with macro hygiene (guaranteed unique labels).
36-
* **Lua 5.1 / Universal**: Automatically downgrades complex control flow into efficient state-machine blocks using temporary execution flags (`____done_...`), ensuring 100% runtime compatibility.
37+
38+
- **Lua 5.2+, JIT, Luau**: Translates deep `return` statements in complex multi-return lambdas into high-performance native `goto` jumps with macro hygiene (guaranteed unique labels).
39+
- **Lua 5.1 / Universal**: Automatically downgrades complex control flow into efficient state-machine blocks using temporary execution flags (`____done_...`), ensuring 100% runtime compatibility.
3740

3841
### 4. Zero-Overhead Fast Path & `$multi` Integration
39-
* Triivial functions (single trailing return expressions) skip block overhead and flatten directly into equations or native multiple assignments.
40-
* Perfect cooperation with `LuaMultiReturn` / `$multi` compiler macro.
42+
43+
- Triivial functions (single trailing return expressions) skip block overhead and flatten directly into equations or native multiple assignments.
44+
- Perfect cooperation with `LuaMultiReturn` / `$multi` compiler macro.
4145

4246
### 5. New CLI / tsconfig.json compiler options
43-
* `inlineGenerateComment`: (boolean) Generates `-- Start inline [name]` annotations directly in the emitted Lua files to simplify debugging.
44-
* `inlineRemoveDefault`: (boolean) Globally toggles whether to strip the original inline function declarations from the compiled output. Can be overridden per function via `/** @inline toggle */`.
47+
48+
- `inlineGenerateComment`: (boolean) Generates `-- Start inline [name]` annotations directly in the emitted Lua files to simplify debugging.
49+
- `inlineRemoveDefault`: (boolean) Globally toggles whether to strip the original inline function declarations from the compiled output. Can be overridden per function via `/** @inline toggle */`.
4550

4651
---
4752

4853
## 🛠️ Quick Example
4954

5055
### TypeScript Source Code
56+
5157
```typescript
5258
/** @inline */
5359
function filter<T>(arr: T[], pred: (x: T) => boolean): T[] {
54-
const result: T[] = [];
55-
for (const item of arr) {
56-
if (pred(item)) result.push(item);
57-
}
58-
return result;
60+
const result: T[] = [];
61+
for (const item of arr) {
62+
if (pred(item)) result.push(item);
63+
}
64+
return result;
5965
}
6066

6167
export function test() {
62-
const data = [1, 2, 3, 4, 5, 6];
63-
64-
// Inlining simple conditions
65-
const evens = filter(data, (n) => n % 2 == 0);
66-
67-
// Inlining heavy block expressions with early returns
68-
const withBlock = filter(data, (n) => {
69-
if (n == 3) return true;
70-
return n > 4;
71-
});
72-
73-
return [evens, withBlock];
68+
const data = [1, 2, 3, 4, 5, 6];
69+
70+
// Inlining simple conditions
71+
const evens = filter(data, n => n % 2 == 0);
72+
73+
// Inlining heavy block expressions with early returns
74+
const withBlock = filter(data, n => {
75+
if (n == 3) return true;
76+
return n > 4;
77+
});
78+
79+
return [evens, withBlock];
7480
}
7581
```
7682

7783
### Transpiled Zero-Overhead Lua Output (Lua 5.2+)
84+
7885
```lua
7986
function ____exports.test(self)
8087
local data = {1, 2, 3, 4, 5, 6}
@@ -110,25 +117,29 @@ function ____exports.test(self)
110117
return {evens, withBlock}
111118
end
112119
```
120+
113121
> 💡 **Note on Variable Naming:** To make this example easy to read, some variable names have been manually cleaned up. In actual generated Lua code, the compiler enforces strict **macro hygiene** by automatically renaming local variables and arguments inside inline blocks (e.g., transforming `item` into `____item_inline_1`). This ensures complete scope isolation and completely prevents name collision bugs with the surrounding code!
122+
114123
---
115124

116125
## ⚠️ Known Limitations & AI Disclaimer
117126

118127
This inliner was developed as a powerful custom extension of the compiler. While it passes all core integration tests, please keep the following trade-offs and architectural quirks in mind:
119128

120129
### 1. Short-circuit Evaluation Side-Effects
121-
* **The Issue:** If you use inline functions inside logical conditions (e.g., `if (isValid() && fetchProps())`), the compiler hoists evaluation do-blocks *before* executing the `if` statement itself.
122-
* **The Result:** Both inline functions will **always** be executed, even if the first one returns `false`. This can cause unexpected side-effects. It is highly recommended to store inline function results in local variables manually before using them in complex conditional logic.
130+
131+
- **The Issue:** If you use inline functions inside logical conditions (e.g., `if (isValid() && fetchProps())`), the compiler hoists evaluation do-blocks _before_ executing the `if` statement itself.
132+
- **The Result:** Both inline functions will **always** be executed, even if the first one returns `false`. This can cause unexpected side-effects. It is highly recommended to store inline function results in local variables manually before using them in complex conditional logic.
123133

124134
### 2. Emitted Code Debugging (Source Maps)
125-
* Deeply nested inlining of complex statements—especially with state-machine generation for older Lua 5.1 targets—can complicate accurate runtime line debugging.
126-
* To make code tracking easier during development, always enable the `--inlineGenerateComment true` flag.
135+
136+
- Deeply nested inlining of complex statements—especially with state-machine generation for older Lua 5.1 targets—can complicate accurate runtime line debugging.
137+
- To make code tracking easier during development, always enable the `--inlineGenerateComment true` flag.
127138

128139
### 3. 🤖 AI-Assisted Development Notice
129-
* About **70% of this compiler fork's logic was built in collaboration with AI (Qwen)** directly inside the IDE.
130-
* Because of this AI-driven approach, some edge cases might still be unhandled, and deep internal refactoring can be highly complex. However, it completely fulfills its goal for practical, micro-optimization tasks! "It works on my machine" ™️ — use it with care and feel free to submit PRs for any bugs you find.
131140

141+
- About **70% of this compiler fork's logic was built in collaboration with AI (Qwen)** directly inside the IDE.
142+
- Because of this AI-driven approach, some edge cases might still be unhandled, and deep internal refactoring can be highly complex. However, it completely fulfills its goal for practical, micro-optimization tasks! "It works on my machine" ™️ — use it with care and feel free to submit PRs for any bugs you find.
132143

133144
# Original README
134145

src/LuaVisitor.ts

Lines changed: 38 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ export type LuaVisitorResult<T extends lua.Node> = T | T[] | undefined;
1414
/**
1515
* Walks through all nodes in the Lua AST using depth-first traversal
1616
* Calls the visitor function for each node encountered
17-
*
17+
*
1818
* Stops at the first non-undefined result returned by the visitor
19-
*
19+
*
2020
* @example
2121
* // Find the first identifier in an AST
2222
* const firstId = visitNode(file, (node) => {
2323
* if (lua.isIdentifier(node)) {
2424
* return node;
2525
* }
2626
* });
27-
*
27+
*
2828
* @example
2929
* // Check if AST contains a specific pattern
3030
* const hasFunctionCall = visitNode(file, (node) => {
@@ -286,7 +286,7 @@ export function visitNode<T>(node: lua.Node, visitor: LuaVisitor<T>): T | undefi
286286
/**
287287
* Walks through ALL nodes in the Lua AST (not stopping at first result)
288288
* Useful for collecting information or performing side effects
289-
*
289+
*
290290
* @example
291291
* // Count all function calls in the AST
292292
* let callCount = 0;
@@ -296,7 +296,7 @@ export function visitNode<T>(node: lua.Node, visitor: LuaVisitor<T>): T | undefi
296296
* }
297297
* });
298298
* console.log(`Found ${callCount} function calls`);
299-
*
299+
*
300300
* @example
301301
* // Collect all identifiers
302302
* const identifiers: lua.Identifier[] = [];
@@ -305,7 +305,7 @@ export function visitNode<T>(node: lua.Node, visitor: LuaVisitor<T>): T | undefi
305305
* identifiers.push(node);
306306
* }
307307
* });
308-
*
308+
*
309309
* @example
310310
* // Validate AST - check for invalid patterns
311311
* visitAllNodes(file, (node) => {
@@ -432,67 +432,64 @@ export function visitAllNodes(node: lua.Node, visitor: LuaVisitor): void {
432432

433433
/**
434434
* Collects all nodes of a specific kind from the AST
435-
*
435+
*
436436
* @example
437437
* // Get all identifiers in the file
438438
* const identifiers = collectNodes(file, lua.SyntaxKind.Identifier);
439-
*
439+
*
440440
* @example
441441
* // Get all function calls
442442
* const calls = collectNodes(file, lua.SyntaxKind.CallExpression);
443-
*
443+
*
444444
* @example
445445
* // Get all string literals
446446
* const strings = collectNodes(file, lua.SyntaxKind.StringLiteral);
447447
* console.log(strings.map(s => s.value));
448448
*/
449-
export function collectNodes<K extends lua.SyntaxKind>(
450-
node: lua.Node,
451-
kind: K
452-
): Array<Extract<lua.Node, { kind: K }>> {
449+
export function collectNodes<K extends lua.SyntaxKind>(node: lua.Node, kind: K): Array<Extract<lua.Node, { kind: K }>> {
453450
const results: Array<Extract<lua.Node, { kind: K }>> = [];
454-
455-
visitAllNodes(node, (n) => {
451+
452+
visitAllNodes(node, n => {
456453
if (n.kind === kind) {
457454
results.push(n as Extract<lua.Node, { kind: K }>);
458455
}
459456
});
460-
457+
461458
return results;
462459
}
463460

464461
/**
465462
* Transforms nodes in the AST by replacing them with visitor results
466463
* Returns a new AST with transformations applied (immutable - original is not modified)
467-
*
464+
*
468465
* The transformer function should return:
469466
* - A new node to replace the current one
470467
* - undefined to keep the current node and continue transforming children
471-
*
468+
*
472469
* @example
473470
* // Replace all identifiers named "oldName" with "newName"
474471
* const newFile = transformNode(file, (node) => {
475472
* if (lua.isIdentifier(node) && node.text === "oldName") {
476473
* return lua.createIdentifier("newName");
477474
* }
478475
* });
479-
*
476+
*
480477
* @example
481478
* // Wrap all numeric literals in parentheses
482479
* const wrappedFile = transformNode(file, (node) => {
483480
* if (lua.isNumericLiteral(node)) {
484481
* return lua.createParenthesizedExpression(node);
485482
* }
486483
* });
487-
*
484+
*
488485
* @example
489486
* // Convert all addition operations to multiplication
490487
* const transformed = transformNode(file, (node) => {
491488
* if (lua.isBinaryExpression(node) && node.operator === lua.SyntaxKind.AdditionOperator) {
492489
* return lua.createBinaryExpression(node.left, node.right, lua.SyntaxKind.MultiplicationOperator);
493490
* }
494491
* });
495-
*
492+
*
496493
* @example
497494
* // Add logging to all function calls
498495
* const loggedFile = transformNode(file, (node) => {
@@ -530,17 +527,13 @@ export function transformNode<T extends lua.Node>(node: T, transformer: LuaVisit
530527
const newRight = node.right?.map(expr => transformNode(expr, transformer));
531528
return lua.createVariableDeclarationStatement(newLeft, newRight) as any as T;
532529
} else if (lua.isAssignmentStatement(node)) {
533-
const newLeft = node.left.map(
534-
expr => transformNode(expr, transformer)
535-
);
530+
const newLeft = node.left.map(expr => transformNode(expr, transformer));
536531
const newRight = node.right.map(expr => transformNode(expr, transformer));
537532
return lua.createAssignmentStatement(newLeft, newRight) as any as T;
538533
} else if (lua.isIfStatement(node)) {
539534
const newCondition = transformNode(node.condition, transformer);
540535
const newIfBlock = transformNode(node.ifBlock, transformer);
541-
const newElseBlock = node.elseBlock
542-
? transformNode(node.elseBlock, transformer)
543-
: undefined;
536+
const newElseBlock = node.elseBlock ? transformNode(node.elseBlock, transformer) : undefined;
544537
return lua.createIfStatement(newCondition, newIfBlock, newElseBlock) as any as T;
545538
} else if (lua.isWhileStatement(node)) {
546539
const newCondition = transformNode(node.condition, transformer);
@@ -552,51 +545,32 @@ export function transformNode<T extends lua.Node>(node: T, transformer: LuaVisit
552545
return lua.createRepeatStatement(newBody, newCondition) as any as T;
553546
} else if (lua.isForStatement(node)) {
554547
const newControlVar = transformNode(node.controlVariable, transformer);
555-
const newInitializer = transformNode(
556-
node.controlVariableInitializer,
557-
transformer
558-
);
548+
const newInitializer = transformNode(node.controlVariableInitializer, transformer);
559549
const newLimit = transformNode(node.limitExpression, transformer);
560-
const newStep = node.stepExpression
561-
? (transformNode(node.stepExpression, transformer))
562-
: undefined;
550+
const newStep = node.stepExpression ? transformNode(node.stepExpression, transformer) : undefined;
563551
const newBody = transformNode(node.body, transformer);
564-
return lua.createForStatement(
565-
newBody,
566-
newControlVar,
567-
newInitializer,
568-
newLimit,
569-
newStep
570-
) as any as T;
552+
return lua.createForStatement(newBody, newControlVar, newInitializer, newLimit, newStep) as any as T;
571553
} else if (lua.isForInStatement(node)) {
572554
const newNames = node.names.map(name => transformNode(name, transformer));
573-
const newExpressions = node.expressions.map(expr =>
574-
transformNode(expr, transformer)
575-
);
555+
const newExpressions = node.expressions.map(expr => transformNode(expr, transformer));
576556
const newBody = transformNode(node.body, transformer);
577557
return lua.createForInStatement(newBody, newNames, newExpressions) as any as T;
578558
} else if (lua.isReturnStatement(node)) {
579-
const newExpressions = node.expressions.map(expr =>
580-
transformNode(expr, transformer)
581-
);
559+
const newExpressions = node.expressions.map(expr => transformNode(expr, transformer));
582560
return lua.createReturnStatement(newExpressions) as any as T;
583561
} else if (lua.isExpressionStatement(node)) {
584562
const newExpression = transformNode(node.expression, transformer);
585563
return lua.createExpressionStatement(newExpression) as any as T;
586564
} else if (lua.isFunctionExpression(node)) {
587565
const newParams = node.params?.map(param => transformNode(param, transformer));
588-
const newDots = node.dots
589-
? (transformNode(node.dots, transformer))
590-
: undefined;
566+
const newDots = node.dots ? transformNode(node.dots, transformer) : undefined;
591567
const newBody = transformNode(node.body, transformer);
592568
return lua.createFunctionExpression(newBody, newParams, newDots, node.flags) as any as T;
593569
} else if (lua.isTableExpression(node)) {
594-
const newFields = node.fields.map(field =>
595-
transformNode(field, transformer)
596-
);
570+
const newFields = node.fields.map(field => transformNode(field, transformer));
597571
return lua.createTableExpression(newFields) as any as T;
598572
} else if (lua.isTableFieldExpression(node)) {
599-
const newKey = node.key ? (transformNode(node.key, transformer)) : undefined;
573+
const newKey = node.key ? transformNode(node.key, transformer) : undefined;
600574
const newValue = transformNode(node.value, transformer);
601575
return lua.createTableFieldExpression(newValue, newKey) as any as T;
602576
} else if (lua.isUnaryExpression(node)) {
@@ -632,31 +606,30 @@ export function transformNode<T extends lua.Node>(node: T, transformer: LuaVisit
632606
return node;
633607
}
634608

635-
636609
/**
637610
* Finds the first node matching a predicate
638-
*
611+
*
639612
* @example
640613
* // Find first function call in the AST
641614
* const firstCall = findNode(file, (node) => lua.isCallExpression(node));
642-
*
615+
*
643616
* @example
644617
* // Find first identifier with a specific name
645-
* const myVar = findNode(file, (node) =>
618+
* const myVar = findNode(file, (node) =>
646619
* lua.isIdentifier(node) && node.text === "myVariable"
647620
* );
648-
*
621+
*
649622
* @example
650623
* // Find first table expression with more than 5 fields
651624
* const largeTable = findNode(file, (node) =>
652625
* lua.isTableExpression(node) && node.fields.length > 5
653626
* );
654-
*
627+
*
655628
* @example
656629
* // Find nested pattern: a call expression inside an if statement
657630
* let foundCall: lua.CallExpression | undefined;
658631
* let inIfStatement = false;
659-
*
632+
*
660633
* findNode(file, (node) => {
661634
* if (lua.isIfStatement(node)) {
662635
* inIfStatement = true;
@@ -669,14 +642,14 @@ export function transformNode<T extends lua.Node>(node: T, transformer: LuaVisit
669642
*/
670643
export function findNode(node: lua.Node, predicate: (node: lua.Node) => boolean): lua.Node | undefined {
671644
let found: lua.Node | undefined;
672-
673-
visitNode(node, (n) => {
645+
646+
visitNode(node, n => {
674647
if (predicate(n)) {
675648
found = n;
676649
return n;
677650
}
678651
return undefined;
679652
});
680-
653+
681654
return found;
682-
}
655+
}

0 commit comments

Comments
 (0)