-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtstl-const-prop.ts
170 lines (147 loc) · 4.3 KB
/
tstl-const-prop.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import * as ts from 'typescript';
import * as tstl from 'typescript-to-lua';
// Find every instance of creating a local with an ALL_CAPS label
const pattern = /local\s([A-Z_0-9]+)\s=\s(.+)/g;
// Variable to capture matches
let match: RegExpExecArray | null;
// Running only once doesn't find all values
// so we re-run the function this many times per file
const maxLoop = 100;
/**
* Replaces local declarations with simple literals in the emitted Lua code
*/
function constProp(file: tstl.EmitFile) {
while ((match = pattern.exec(file.code)) !== null) {
const statement = match[0];
const label = match[1];
const value = match[2].trim();
// Find only values that are simple literals (numbers and strings)
if (
// Check for string literals (has exactly two quote marks)
(value.split('"').length - 1 === 2 &&
value.charAt(0) === '"' &&
value.charAt(value.length - 1) === '"') ||
// Check for numbers
!isNaN(value as unknown as number)
) {
// Replace local declaration with an empty line
file.code = file.code.replace(statement, '');
// Replace label with value
const findLabel = new RegExp(`(${label})(?!\\w)`, 'g');
file.code = file.code.replace(findLabel, value);
}
}
}
/**
* Evaluates arithmetic expressions and returns the result
*/
function evaluateExpression(expression: string): number | null {
const operators = /[+\-*/%^]/;
const parts = expression.split(operators);
// Check if both sides are valid numbers
const bothSidesAreNumbers = parts.every(
(part) => !isNaN(part as unknown as number),
);
if (!bothSidesAreNumbers) {
return null;
}
const numbers = expression.split(/([+\-*/%^])/).map((part) => {
return operators.test(part) ? part : Number(part);
});
let result = numbers[0];
if (typeof result !== 'number' || isNaN(result)) {
return null; // Return null if the operand is not a valid number
}
for (let i = 1; i < numbers.length; i += 2) {
const operator = numbers[i];
const operand = numbers[i + 1];
if (typeof operand !== 'number' || isNaN(operand)) {
return null; // Return null if the operand is not a valid number
}
switch (operator) {
case '+':
result += operand;
break;
case '-':
result -= operand;
break;
case '*':
result *= operand;
break;
case '/':
result /= operand;
break;
case '%':
result %= operand;
break;
case '^':
result **= operand;
break;
default:
break;
}
}
if (isNaN(result)) {
return null;
}
return result;
}
/**
* Unrolls constant expressions in the emitted Lua code
*/
function constUnroll(file: tstl.EmitFile) {
// Define a regular expression to match lines with assignments
const assignmentRegex = /\s*=\s*([^\n]+)\s*/g;
// Iterate through matches in the code
let match;
while ((match = assignmentRegex.exec(file.code)) !== null) {
const originalLine = match[0];
const equation = match[1].trim();
// Replace expressions within parentheses first
const regex = /\(([^()]+)\)/g;
let updatedEquation = equation;
let subMatch;
while ((subMatch = regex.exec(equation)) !== null) {
const subExpression = subMatch[1];
const subExpressionResult = evaluateExpression(subExpression);
if (subExpressionResult !== null) {
updatedEquation = updatedEquation.replace(
`(${subExpression})`,
subExpressionResult.toString(),
);
}
}
// Evaluate the updated equation in order of BEDMAS/BODMAS without using eval
const result = evaluateExpression(updatedEquation);
if (result !== null) {
// Replace the original equation with the result
const replacement = originalLine.replace(/\s*=\s*[^\n]+/, ` = ${result}`);
file.code = file.code.replace(originalLine, replacement);
} else {
// Silently skip the case where the equation couldn't be evaluated
}
}
}
/**
* Plugin definition for TypeScript-to-Lua
*/
const plugin: tstl.Plugin = {
afterEmit: (
_program: ts.Program,
_options: tstl.CompilerOptions,
emitHost: tstl.EmitHost,
result: tstl.EmitFile[],
) => {
for (const file of result) {
// Alternate between propagation and unrolling
for (let index = 0; index < maxLoop; index++) {
constProp(file);
constUnroll(file);
}
// Write the changed code
emitHost.writeFile(file.outputPath, file.code, false);
}
},
};
// Export the plugin for use in TypeScript-to-Lua
export default plugin;