ticbuild 1.0.0

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.
Files changed (290) hide show
  1. package/.attachments/support_me_on_kofi_beige.png +0 -0
  2. package/.env.example +3 -0
  3. package/.prettierignore +10 -0
  4. package/LICENSE +15 -0
  5. package/README.md +429 -0
  6. package/debug/obj/resolvedManifest.ticbuild.jsonc +108 -0
  7. package/dist/backend/ImportedResource.d.ts +11 -0
  8. package/dist/backend/ImportedResource.d.ts.map +1 -0
  9. package/dist/backend/ImportedResource.js +53 -0
  10. package/dist/backend/ImportedResource.js.map +1 -0
  11. package/dist/backend/ImportedResourceTypes.d.ts +24 -0
  12. package/dist/backend/ImportedResourceTypes.d.ts.map +1 -0
  13. package/dist/backend/ImportedResourceTypes.js +35 -0
  14. package/dist/backend/ImportedResourceTypes.js.map +1 -0
  15. package/dist/backend/codeBanking.test.d.ts +2 -0
  16. package/dist/backend/codeBanking.test.d.ts.map +1 -0
  17. package/dist/backend/codeBanking.test.js.map +1 -0
  18. package/dist/backend/importResources.d.ts +4 -0
  19. package/dist/backend/importResources.d.ts.map +1 -0
  20. package/dist/backend/importResources.js +58 -0
  21. package/dist/backend/importResources.js.map +1 -0
  22. package/dist/backend/importUtils.d.ts +14 -0
  23. package/dist/backend/importUtils.d.ts.map +1 -0
  24. package/dist/backend/importUtils.js +77 -0
  25. package/dist/backend/importUtils.js.map +1 -0
  26. package/dist/backend/importers/LuaCodeImporter.d.ts +47 -0
  27. package/dist/backend/importers/LuaCodeImporter.d.ts.map +1 -0
  28. package/dist/backend/importers/LuaCodeImporter.js +196 -0
  29. package/dist/backend/importers/LuaCodeImporter.js.map +1 -0
  30. package/dist/backend/importers/LuaCodeImporter.test.d.ts +2 -0
  31. package/dist/backend/importers/LuaCodeImporter.test.d.ts.map +1 -0
  32. package/dist/backend/importers/LuaCodeImporter.test.js.map +1 -0
  33. package/dist/backend/importers/binaryResourceImporter.d.ts +22 -0
  34. package/dist/backend/importers/binaryResourceImporter.d.ts.map +1 -0
  35. package/dist/backend/importers/binaryResourceImporter.js +53 -0
  36. package/dist/backend/importers/binaryResourceImporter.js.map +1 -0
  37. package/dist/backend/importers/luaImporter.d.ts +1 -0
  38. package/dist/backend/importers/luaImporter.d.ts.map +1 -0
  39. package/dist/backend/importers/luaImporter.js +3 -0
  40. package/dist/backend/importers/luaImporter.js.map +1 -0
  41. package/dist/backend/importers/textResourceImporter.d.ts +23 -0
  42. package/dist/backend/importers/textResourceImporter.d.ts.map +1 -0
  43. package/dist/backend/importers/textResourceImporter.js +55 -0
  44. package/dist/backend/importers/textResourceImporter.js.map +1 -0
  45. package/dist/backend/importers/tic80CartImporter.d.ts +21 -0
  46. package/dist/backend/importers/tic80CartImporter.d.ts.map +1 -0
  47. package/dist/backend/importers/tic80CartImporter.js +96 -0
  48. package/dist/backend/importers/tic80CartImporter.js.map +1 -0
  49. package/dist/backend/loadAllImports.d.ts +1 -0
  50. package/dist/backend/loadAllImports.d.ts.map +1 -0
  51. package/dist/backend/loadAllImports.js +3 -0
  52. package/dist/backend/loadAllImports.js.map +1 -0
  53. package/dist/backend/luaBinaryEncoding.d.ts +6 -0
  54. package/dist/backend/luaBinaryEncoding.d.ts.map +1 -0
  55. package/dist/backend/luaBinaryEncoding.js +94 -0
  56. package/dist/backend/luaBinaryEncoding.js.map +1 -0
  57. package/dist/backend/luaPreprocessor.d.ts +8 -0
  58. package/dist/backend/luaPreprocessor.d.ts.map +1 -0
  59. package/dist/backend/luaPreprocessor.js +862 -0
  60. package/dist/backend/luaPreprocessor.js.map +1 -0
  61. package/dist/backend/luaPreprocessor.test.d.ts +2 -0
  62. package/dist/backend/luaPreprocessor.test.d.ts.map +1 -0
  63. package/dist/backend/luaPreprocessor.test.js.map +1 -0
  64. package/dist/backend/manifestLoader.d.ts +19 -0
  65. package/dist/backend/manifestLoader.d.ts.map +1 -0
  66. package/dist/backend/manifestLoader.js +142 -0
  67. package/dist/backend/manifestLoader.js.map +1 -0
  68. package/dist/backend/manifestLoader.test.d.ts +2 -0
  69. package/dist/backend/manifestLoader.test.d.ts.map +1 -0
  70. package/dist/backend/manifestLoader.test.js.map +1 -0
  71. package/dist/backend/manifestTypes.d.ts +454 -0
  72. package/dist/backend/manifestTypes.d.ts.map +1 -0
  73. package/dist/backend/manifestTypes.js +28 -0
  74. package/dist/backend/manifestTypes.js.map +1 -0
  75. package/dist/backend/project.d.ts +24 -0
  76. package/dist/backend/project.d.ts.map +1 -0
  77. package/dist/backend/project.js +159 -0
  78. package/dist/backend/project.js.map +1 -0
  79. package/dist/backend/projectCore.d.ts +34 -0
  80. package/dist/backend/projectCore.d.ts.map +1 -0
  81. package/dist/backend/projectCore.js +226 -0
  82. package/dist/backend/projectCore.js.map +1 -0
  83. package/dist/backend/tic80Resolver.d.ts +6 -0
  84. package/dist/backend/tic80Resolver.d.ts.map +1 -0
  85. package/dist/backend/tic80Resolver.js +66 -0
  86. package/dist/backend/tic80Resolver.js.map +1 -0
  87. package/dist/buildInfo.d.ts +9 -0
  88. package/dist/buildInfo.d.ts.map +1 -0
  89. package/dist/buildInfo.js +13 -0
  90. package/dist/buildInfo.js.map +1 -0
  91. package/dist/frontend/build.d.ts +3 -0
  92. package/dist/frontend/build.d.ts.map +1 -0
  93. package/dist/frontend/build.js +8 -0
  94. package/dist/frontend/build.js.map +1 -0
  95. package/dist/frontend/codeBankWarnings.test.d.ts +2 -0
  96. package/dist/frontend/codeBankWarnings.test.d.ts.map +1 -0
  97. package/dist/frontend/codeBankWarnings.test.js.map +1 -0
  98. package/dist/frontend/core.d.ts +3 -0
  99. package/dist/frontend/core.d.ts.map +1 -0
  100. package/dist/frontend/core.js +259 -0
  101. package/dist/frontend/core.js.map +1 -0
  102. package/dist/frontend/init.d.ts +7 -0
  103. package/dist/frontend/init.d.ts.map +1 -0
  104. package/dist/frontend/init.js +95 -0
  105. package/dist/frontend/init.js.map +1 -0
  106. package/dist/frontend/parseOptions.d.ts +7 -0
  107. package/dist/frontend/parseOptions.d.ts.map +1 -0
  108. package/dist/frontend/parseOptions.js +68 -0
  109. package/dist/frontend/parseOptions.js.map +1 -0
  110. package/dist/frontend/run.d.ts +3 -0
  111. package/dist/frontend/run.d.ts.map +1 -0
  112. package/dist/frontend/run.js +63 -0
  113. package/dist/frontend/run.js.map +1 -0
  114. package/dist/frontend/watch.d.ts +3 -0
  115. package/dist/frontend/watch.d.ts.map +1 -0
  116. package/dist/frontend/watch.js +208 -0
  117. package/dist/frontend/watch.js.map +1 -0
  118. package/dist/index.d.ts +3 -0
  119. package/dist/index.d.ts.map +1 -0
  120. package/dist/index.js +191 -0
  121. package/dist/index.js.map +1 -0
  122. package/dist/obj/resolvedManifest.ticbuild.jsonc +110 -0
  123. package/dist/obj/variables.json +19 -0
  124. package/dist/utils/algorithms.d.ts +4 -0
  125. package/dist/utils/algorithms.d.ts.map +1 -0
  126. package/dist/utils/algorithms.js +15 -0
  127. package/dist/utils/algorithms.js.map +1 -0
  128. package/dist/utils/algorithms.test.d.ts +2 -0
  129. package/dist/utils/algorithms.test.d.ts.map +1 -0
  130. package/dist/utils/algorithms.test.js.map +1 -0
  131. package/dist/utils/bin.d.ts +4 -0
  132. package/dist/utils/bin.d.ts.map +1 -0
  133. package/dist/utils/bin.js +16 -0
  134. package/dist/utils/bin.js.map +1 -0
  135. package/dist/utils/charMap.d.ts +28 -0
  136. package/dist/utils/charMap.d.ts.map +1 -0
  137. package/dist/utils/charMap.js +31 -0
  138. package/dist/utils/charMap.js.map +1 -0
  139. package/dist/utils/console.d.ts +10 -0
  140. package/dist/utils/console.d.ts.map +1 -0
  141. package/dist/utils/console.js +66 -0
  142. package/dist/utils/console.js.map +1 -0
  143. package/dist/utils/encoding/b85.d.ts +5 -0
  144. package/dist/utils/encoding/b85.d.ts.map +1 -0
  145. package/dist/utils/encoding/b85.js +136 -0
  146. package/dist/utils/encoding/b85.js.map +1 -0
  147. package/dist/utils/encoding/codecRegistry.d.ts +333 -0
  148. package/dist/utils/encoding/codecRegistry.d.ts.map +1 -0
  149. package/dist/utils/encoding/codecRegistry.js +81 -0
  150. package/dist/utils/encoding/codecRegistry.js.map +1 -0
  151. package/dist/utils/encoding/hex.d.ts +3 -0
  152. package/dist/utils/encoding/hex.d.ts.map +1 -0
  153. package/dist/utils/encoding/hex.js +30 -0
  154. package/dist/utils/encoding/hex.js.map +1 -0
  155. package/dist/utils/encoding/lz.d.ts +12 -0
  156. package/dist/utils/encoding/lz.d.ts.map +1 -0
  157. package/dist/utils/encoding/lz.js +271 -0
  158. package/dist/utils/encoding/lz.js.map +1 -0
  159. package/dist/utils/enum.d.ts +45 -0
  160. package/dist/utils/enum.d.ts.map +1 -0
  161. package/dist/utils/enum.js +135 -0
  162. package/dist/utils/enum.js.map +1 -0
  163. package/dist/utils/errorHandling.d.ts +13 -0
  164. package/dist/utils/errorHandling.d.ts.map +1 -0
  165. package/dist/utils/errorHandling.js +18 -0
  166. package/dist/utils/errorHandling.js.map +1 -0
  167. package/dist/utils/fileSystem.d.ts +16 -0
  168. package/dist/utils/fileSystem.d.ts.map +1 -0
  169. package/dist/utils/fileSystem.js +161 -0
  170. package/dist/utils/fileSystem.js.map +1 -0
  171. package/dist/utils/help.d.ts +7 -0
  172. package/dist/utils/help.d.ts.map +1 -0
  173. package/dist/utils/help.js +87 -0
  174. package/dist/utils/help.js.map +1 -0
  175. package/dist/utils/lua/luaUtils.d.ts +1 -0
  176. package/dist/utils/lua/luaUtils.d.ts.map +1 -0
  177. package/dist/utils/lua/luaUtils.js +3 -0
  178. package/dist/utils/lua/luaUtils.js.map +1 -0
  179. package/dist/utils/lua/lua_alias_expressions.d.ts +20 -0
  180. package/dist/utils/lua/lua_alias_expressions.d.ts.map +1 -0
  181. package/dist/utils/lua/lua_alias_expressions.js +233 -0
  182. package/dist/utils/lua/lua_alias_expressions.js.map +1 -0
  183. package/dist/utils/lua/lua_alias_literals.d.ts +20 -0
  184. package/dist/utils/lua/lua_alias_literals.d.ts.map +1 -0
  185. package/dist/utils/lua/lua_alias_literals.js +165 -0
  186. package/dist/utils/lua/lua_alias_literals.js.map +1 -0
  187. package/dist/utils/lua/lua_alias_shared.d.ts +31 -0
  188. package/dist/utils/lua/lua_alias_shared.d.ts.map +1 -0
  189. package/dist/utils/lua/lua_alias_shared.js +415 -0
  190. package/dist/utils/lua/lua_alias_shared.js.map +1 -0
  191. package/dist/utils/lua/lua_ast.d.ts +9 -0
  192. package/dist/utils/lua/lua_ast.d.ts.map +1 -0
  193. package/dist/utils/lua/lua_ast.js +90 -0
  194. package/dist/utils/lua/lua_ast.js.map +1 -0
  195. package/dist/utils/lua/lua_fundamentals.d.ts +14 -0
  196. package/dist/utils/lua/lua_fundamentals.d.ts.map +1 -0
  197. package/dist/utils/lua/lua_fundamentals.js +93 -0
  198. package/dist/utils/lua/lua_fundamentals.js.map +1 -0
  199. package/dist/utils/lua/lua_pack_locals.d.ts +3 -0
  200. package/dist/utils/lua/lua_pack_locals.d.ts.map +1 -0
  201. package/dist/utils/lua/lua_pack_locals.js +206 -0
  202. package/dist/utils/lua/lua_pack_locals.js.map +1 -0
  203. package/dist/utils/lua/lua_processor.d.ts +65 -0
  204. package/dist/utils/lua/lua_processor.d.ts.map +1 -0
  205. package/dist/utils/lua/lua_processor.js +1153 -0
  206. package/dist/utils/lua/lua_processor.js.map +1 -0
  207. package/dist/utils/lua/lua_processor.test.d.ts +2 -0
  208. package/dist/utils/lua/lua_processor.test.d.ts.map +1 -0
  209. package/dist/utils/lua/lua_processor.test.js.map +1 -0
  210. package/dist/utils/lua/lua_remove_unused_functions.d.ts +6 -0
  211. package/dist/utils/lua/lua_remove_unused_functions.d.ts.map +1 -0
  212. package/dist/utils/lua/lua_remove_unused_functions.js +474 -0
  213. package/dist/utils/lua/lua_remove_unused_functions.js.map +1 -0
  214. package/dist/utils/lua/lua_remove_unused_locals.d.ts +3 -0
  215. package/dist/utils/lua/lua_remove_unused_locals.d.ts.map +1 -0
  216. package/dist/utils/lua/lua_remove_unused_locals.js +303 -0
  217. package/dist/utils/lua/lua_remove_unused_locals.js.map +1 -0
  218. package/dist/utils/lua/lua_rename_allowed_table_keys.d.ts +3 -0
  219. package/dist/utils/lua/lua_rename_allowed_table_keys.d.ts.map +1 -0
  220. package/dist/utils/lua/lua_rename_allowed_table_keys.js +157 -0
  221. package/dist/utils/lua/lua_rename_allowed_table_keys.js.map +1 -0
  222. package/dist/utils/lua/lua_rename_table_fields.d.ts +3 -0
  223. package/dist/utils/lua/lua_rename_table_fields.d.ts.map +1 -0
  224. package/dist/utils/lua/lua_rename_table_fields.js +427 -0
  225. package/dist/utils/lua/lua_rename_table_fields.js.map +1 -0
  226. package/dist/utils/lua/lua_renamer.d.ts +3 -0
  227. package/dist/utils/lua/lua_renamer.d.ts.map +1 -0
  228. package/dist/utils/lua/lua_renamer.js +229 -0
  229. package/dist/utils/lua/lua_renamer.js.map +1 -0
  230. package/dist/utils/lua/lua_simplify.d.ts +3 -0
  231. package/dist/utils/lua/lua_simplify.d.ts.map +1 -0
  232. package/dist/utils/lua/lua_simplify.js +541 -0
  233. package/dist/utils/lua/lua_simplify.js.map +1 -0
  234. package/dist/utils/lua/lua_utils.d.ts +13 -0
  235. package/dist/utils/lua/lua_utils.d.ts.map +1 -0
  236. package/dist/utils/lua/lua_utils.js +58 -0
  237. package/dist/utils/lua/lua_utils.js.map +1 -0
  238. package/dist/utils/math.d.ts +2 -0
  239. package/dist/utils/math.d.ts.map +1 -0
  240. package/dist/utils/math.js +7 -0
  241. package/dist/utils/math.js.map +1 -0
  242. package/dist/utils/math.test.d.ts +2 -0
  243. package/dist/utils/math.test.d.ts.map +1 -0
  244. package/dist/utils/math.test.js.map +1 -0
  245. package/dist/utils/templates.d.ts +3 -0
  246. package/dist/utils/templates.d.ts.map +1 -0
  247. package/dist/utils/templates.js +57 -0
  248. package/dist/utils/templates.js.map +1 -0
  249. package/dist/utils/tic80/bankSupport.test.d.ts +2 -0
  250. package/dist/utils/tic80/bankSupport.test.d.ts.map +1 -0
  251. package/dist/utils/tic80/bankSupport.test.js.map +1 -0
  252. package/dist/utils/tic80/cartLoader.d.ts +3 -0
  253. package/dist/utils/tic80/cartLoader.d.ts.map +1 -0
  254. package/dist/utils/tic80/cartLoader.js +54 -0
  255. package/dist/utils/tic80/cartLoader.js.map +1 -0
  256. package/dist/utils/tic80/cartWriter.d.ts +5 -0
  257. package/dist/utils/tic80/cartWriter.d.ts.map +1 -0
  258. package/dist/utils/tic80/cartWriter.js +95 -0
  259. package/dist/utils/tic80/cartWriter.js.map +1 -0
  260. package/dist/utils/tic80/launch.d.ts +4 -0
  261. package/dist/utils/tic80/launch.d.ts.map +1 -0
  262. package/dist/utils/tic80/launch.js +36 -0
  263. package/dist/utils/tic80/launch.js.map +1 -0
  264. package/dist/utils/tic80/tic80.d.ts +1149 -0
  265. package/dist/utils/tic80/tic80.d.ts.map +1 -0
  266. package/dist/utils/tic80/tic80.js +114 -0
  267. package/dist/utils/tic80/tic80.js.map +1 -0
  268. package/dist/utils/utils.d.ts +13 -0
  269. package/dist/utils/utils.d.ts.map +1 -0
  270. package/dist/utils/utils.js +109 -0
  271. package/dist/utils/utils.js.map +1 -0
  272. package/dist/utils/versionString.d.ts +12 -0
  273. package/dist/utils/versionString.d.ts.map +1 -0
  274. package/dist/utils/versionString.js +33 -0
  275. package/dist/utils/versionString.js.map +1 -0
  276. package/dist/utils/windowPosition.d.ts +10 -0
  277. package/dist/utils/windowPosition.d.ts.map +1 -0
  278. package/dist/utils/windowPosition.js +222 -0
  279. package/dist/utils/windowPosition.js.map +1 -0
  280. package/example.ticbuild.jsonc +94 -0
  281. package/package.json +51 -0
  282. package/templates/help/build.txt +23 -0
  283. package/templates/help/init.txt +23 -0
  284. package/templates/help/main.txt +41 -0
  285. package/templates/help/run.txt +22 -0
  286. package/templates/help/tic80.txt +8 -0
  287. package/templates/help/watch.txt +24 -0
  288. package/templates/minimal/project.ticbuild.jsonc +43 -0
  289. package/ticbuild-1.0.0.tgz +0 -0
  290. package/ticbuild.schema.json +327 -0
@@ -0,0 +1,1153 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.LuaPrinter = void 0;
37
+ exports.unparseLua = unparseLua;
38
+ exports.parseLua = parseLua;
39
+ exports.processLua = processLua;
40
+ const luaparse = __importStar(require("luaparse"));
41
+ const lua_renamer_1 = require("./lua_renamer");
42
+ const lua_alias_literals_1 = require("./lua_alias_literals");
43
+ const lua_alias_expressions_1 = require("./lua_alias_expressions");
44
+ const lua_pack_locals_1 = require("./lua_pack_locals");
45
+ const lua_simplify_1 = require("./lua_simplify");
46
+ const lua_remove_unused_locals_1 = require("./lua_remove_unused_locals");
47
+ const lua_remove_unused_functions_1 = require("./lua_remove_unused_functions");
48
+ const lua_rename_table_fields_1 = require("./lua_rename_table_fields");
49
+ const lua_rename_allowed_table_keys_1 = require("./lua_rename_allowed_table_keys");
50
+ const lua_fundamentals_1 = require("./lua_fundamentals");
51
+ // Precedence tables, low → high
52
+ const LOGICAL_PRECEDENCE = {
53
+ or: 1,
54
+ and: 2,
55
+ };
56
+ const BINARY_PRECEDENCE = {
57
+ "<": 3,
58
+ ">": 3,
59
+ "<=": 3,
60
+ ">=": 3,
61
+ "~=": 3,
62
+ "==": 3,
63
+ "|": 4,
64
+ "~": 5,
65
+ "&": 6,
66
+ "<<": 7,
67
+ ">>": 7,
68
+ "..": 8, // right associative
69
+ "+": 9,
70
+ "-": 9,
71
+ "*": 10,
72
+ "/": 10,
73
+ "//": 10,
74
+ "%": 10,
75
+ };
76
+ const UNARY_PRECEDENCE = 11; // not, #, -, ~
77
+ const POW_PRECEDENCE = 12; // ^
78
+ function getPrecedence(node) {
79
+ switch (node.type) {
80
+ case "LogicalExpression":
81
+ return LOGICAL_PRECEDENCE[node.operator];
82
+ case "BinaryExpression": {
83
+ const op = node.operator;
84
+ if (op === "^")
85
+ return POW_PRECEDENCE;
86
+ return BINARY_PRECEDENCE[op];
87
+ }
88
+ case "UnaryExpression":
89
+ return UNARY_PRECEDENCE;
90
+ default:
91
+ // Primary expressions (literals, identifiers, calls, table ctors, etc.)
92
+ return 100;
93
+ }
94
+ }
95
+ class LuaPrinter {
96
+ constructor(options, blockComments) {
97
+ this.buf = [];
98
+ this.indentLevel = 0;
99
+ this.indentUnit = " "; // only used if !minified
100
+ this.currentLine = "";
101
+ this.inlineMode = false; // When true, render everything on single lines without packing
102
+ this.options = options;
103
+ this.blockComments = blockComments || new Map();
104
+ }
105
+ print(chunk) {
106
+ const mode = this.options.lineBehavior || "pretty";
107
+ // For tight mode, use the token-stream approach
108
+ if (mode === "tight") {
109
+ return this.printTightMode(chunk);
110
+ }
111
+ // For pretty and single-line-blocks, use structured approach
112
+ this.buf = [];
113
+ this.currentLine = "";
114
+ this.indentLevel = 0;
115
+ this.printBlock(chunk.body);
116
+ if (this.currentLine.length > 0)
117
+ this.flushLine();
118
+ return this.buf.join("");
119
+ }
120
+ // ===== TIGHT MODE: Token-stream based packing =====
121
+ // Renders everything to space-separated tokens, then packs into lines
122
+ printTightMode(chunk) {
123
+ const tokens = this.collectTokens(chunk.body);
124
+ return this.packTokensIntoLines(tokens);
125
+ }
126
+ // Collect all tokens from a block of statements
127
+ collectTokens(body) {
128
+ const comments = [...(this.blockComments.get(body) || [])];
129
+ const items = [];
130
+ let ci = 0;
131
+ for (const stmt of body) {
132
+ while (ci < comments.length && this.startPos(comments[ci]) <= this.startPos(stmt)) {
133
+ items.push(comments[ci]);
134
+ ci++;
135
+ }
136
+ items.push(stmt);
137
+ }
138
+ while (ci < comments.length) {
139
+ items.push(comments[ci]);
140
+ ci++;
141
+ }
142
+ const tokens = [];
143
+ for (const node of items) {
144
+ if (node.type === "Comment") {
145
+ // Comments get special handling - they force a line break
146
+ tokens.push("\n" + this.renderComment(node));
147
+ }
148
+ else {
149
+ const stmtTokens = this.statementToTokens(node);
150
+ tokens.push(...stmtTokens);
151
+ }
152
+ }
153
+ return tokens;
154
+ }
155
+ // Convert a statement to tokens (space-separated pieces)
156
+ // For maximum packing flexibility, we separate keywords from their arguments
157
+ statementToTokens(stmt) {
158
+ const tokens = [];
159
+ switch (stmt.type) {
160
+ case "AssignmentStatement": {
161
+ const st = stmt;
162
+ const vars = st.variables.map((v) => this.expr(v)).join(",");
163
+ const vals = st.init.map((v) => this.expr(v)).join(",");
164
+ tokens.push(vars + "=" + vals);
165
+ break;
166
+ }
167
+ case "LocalStatement": {
168
+ const st = stmt;
169
+ const vars = st.variables.map((v) => this.expr(v)).join(",");
170
+ let s = "local " + vars;
171
+ if (st.init && st.init.length > 0) {
172
+ s += "=" + st.init.map((v) => this.expr(v)).join(",");
173
+ }
174
+ tokens.push(s);
175
+ break;
176
+ }
177
+ case "CallStatement": {
178
+ const st = stmt;
179
+ tokens.push(this.expr(st.expression));
180
+ break;
181
+ }
182
+ case "ReturnStatement": {
183
+ const st = stmt;
184
+ if (st.arguments.length > 0) {
185
+ // Keep return with all its comma-separated arguments as one token
186
+ // to preserve required commas between multiple return values
187
+ tokens.push("return " + st.arguments.map((a) => this.expr(a)).join(","));
188
+ }
189
+ else {
190
+ tokens.push("return");
191
+ }
192
+ break;
193
+ }
194
+ case "BreakStatement":
195
+ tokens.push("break");
196
+ break;
197
+ case "FunctionDeclaration": {
198
+ const fn = stmt;
199
+ let s = fn.isLocal ? "local function " : "function ";
200
+ if (fn.identifier) {
201
+ s += this.expr(fn.identifier);
202
+ }
203
+ s += "(" + fn.parameters.map((p) => this.expr(p)).join(",") + ")";
204
+ tokens.push(s);
205
+ tokens.push(...this.collectTokens(fn.body));
206
+ tokens.push("end");
207
+ break;
208
+ }
209
+ case "IfStatement": {
210
+ const ifs = stmt;
211
+ for (const clause of ifs.clauses) {
212
+ if (clause.type === "IfClause") {
213
+ tokens.push("if " + this.expr(clause.condition) + " then");
214
+ }
215
+ else if (clause.type === "ElseifClause") {
216
+ tokens.push("elseif " + this.expr(clause.condition) + " then");
217
+ }
218
+ else {
219
+ tokens.push("else");
220
+ }
221
+ tokens.push(...this.collectTokens(clause.body));
222
+ }
223
+ tokens.push("end");
224
+ break;
225
+ }
226
+ case "WhileStatement": {
227
+ const st = stmt;
228
+ tokens.push("while " + this.expr(st.condition) + " do");
229
+ tokens.push(...this.collectTokens(st.body));
230
+ tokens.push("end");
231
+ break;
232
+ }
233
+ case "RepeatStatement": {
234
+ const st = stmt;
235
+ tokens.push("repeat");
236
+ tokens.push(...this.collectTokens(st.body));
237
+ tokens.push("until " + this.expr(st.condition));
238
+ break;
239
+ }
240
+ case "ForNumericStatement": {
241
+ const st = stmt;
242
+ let s = "for " + this.expr(st.variable) + "=" + this.expr(st.start) + "," + this.expr(st.end);
243
+ if (st.step) {
244
+ s += "," + this.expr(st.step);
245
+ }
246
+ s += " do";
247
+ tokens.push(s);
248
+ tokens.push(...this.collectTokens(st.body));
249
+ tokens.push("end");
250
+ break;
251
+ }
252
+ case "ForGenericStatement": {
253
+ const st = stmt;
254
+ const vars = st.variables.map((v) => this.expr(v)).join(",");
255
+ const iters = st.iterators.map((it) => this.expr(it)).join(",");
256
+ tokens.push("for " + vars + " in " + iters + " do");
257
+ tokens.push(...this.collectTokens(st.body));
258
+ tokens.push("end");
259
+ break;
260
+ }
261
+ case "DoStatement": {
262
+ const st = stmt;
263
+ tokens.push("do");
264
+ tokens.push(...this.collectTokens(st.body));
265
+ tokens.push("end");
266
+ break;
267
+ }
268
+ default:
269
+ break;
270
+ }
271
+ return tokens;
272
+ }
273
+ // Pack tokens into lines respecting maxLineLength
274
+ packTokensIntoLines(tokens) {
275
+ const maxLen = this.options.maxLineLength || 120;
276
+ const lines = [];
277
+ let currentLine = "";
278
+ for (const token of tokens) {
279
+ // Special case: comment tokens start with \n and force a new line
280
+ if (token.startsWith("\n")) {
281
+ if (currentLine.length > 0) {
282
+ lines.push(currentLine);
283
+ currentLine = "";
284
+ }
285
+ lines.push(token.slice(1)); // Remove the leading \n marker
286
+ continue;
287
+ }
288
+ if (currentLine.length === 0) {
289
+ currentLine = token;
290
+ }
291
+ else {
292
+ const candidate = currentLine + " " + token;
293
+ // Use <= to allow filling lines up to exactly maxLen
294
+ // EXCEPT: if the token is 'end', use < to prefer wrapping
295
+ // This avoids packing 'end' to fill exactly maxLen
296
+ const isEndToken = token === "end";
297
+ const fits = isEndToken ? candidate.length < maxLen : candidate.length <= maxLen;
298
+ if (fits) {
299
+ currentLine = candidate;
300
+ }
301
+ else {
302
+ lines.push(currentLine);
303
+ currentLine = token;
304
+ }
305
+ }
306
+ }
307
+ if (currentLine.length > 0) {
308
+ lines.push(currentLine);
309
+ }
310
+ return lines.join("\n") + (lines.length > 0 ? "\n" : "");
311
+ }
312
+ // Render a comment for tight mode
313
+ renderComment(comment) {
314
+ if (comment.raw) {
315
+ return comment.raw.trim();
316
+ }
317
+ return "--" + comment.value;
318
+ }
319
+ // --- low-level emit helpers ---
320
+ emit(s) {
321
+ this.currentLine += s;
322
+ }
323
+ newline() {
324
+ this.flushLine();
325
+ }
326
+ flushLine() {
327
+ this.buf.push(this.currentLine + "\n");
328
+ this.currentLine = "";
329
+ }
330
+ emitKeyword(s) {
331
+ this.emit(s);
332
+ }
333
+ startPos(node) {
334
+ if (node && Array.isArray(node.range) && node.range.length > 0) {
335
+ return node.range[0];
336
+ }
337
+ return 0;
338
+ }
339
+ printIndent() {
340
+ const indentLevel = Math.min(this.indentLevel, this.options.maxIndentLevel);
341
+ this.buf.push(this.indentUnit.repeat(indentLevel));
342
+ // if (!this.options.stripWhitespace) {
343
+ // this.buf.push(this.indentUnit.repeat(this.indentLevel));
344
+ // }
345
+ }
346
+ printBlock(body) {
347
+ const comments = [...(this.blockComments.get(body) || [])];
348
+ const items = [];
349
+ let ci = 0;
350
+ for (const stmt of body) {
351
+ while (ci < comments.length && this.startPos(comments[ci]) <= this.startPos(stmt)) {
352
+ items.push(comments[ci]);
353
+ ci++;
354
+ }
355
+ items.push(stmt);
356
+ }
357
+ while (ci < comments.length) {
358
+ items.push(comments[ci]);
359
+ ci++;
360
+ }
361
+ const mode = this.options.lineBehavior || "pretty";
362
+ const maxLen = this.options.maxLineLength || 120;
363
+ // In inline mode, render all statements space-separated on one line (no newlines)
364
+ if (this.inlineMode) {
365
+ for (let i = 0; i < items.length; i++) {
366
+ const node = items[i];
367
+ if (i > 0)
368
+ this.emit(" ");
369
+ this.printStatementInline(node);
370
+ }
371
+ return;
372
+ }
373
+ // In pretty mode, print each statement on its own line
374
+ if (mode === "pretty") {
375
+ for (const node of items) {
376
+ this.printIndent();
377
+ this.printStatement(node);
378
+ }
379
+ return;
380
+ }
381
+ // single-line-blocks mode: blocks are either entirely single-line or multi-line
382
+ // (tight mode is handled separately via printTightMode)
383
+ const flushLineIfAny = () => {
384
+ if (this.currentLine.length > 0)
385
+ this.flushLine();
386
+ };
387
+ for (const node of items) {
388
+ // Comments always get their own line
389
+ if (node.type === "Comment") {
390
+ flushLineIfAny();
391
+ this.printIndent();
392
+ this.printStatement(node);
393
+ continue;
394
+ }
395
+ const stmt = node;
396
+ const inline = this.renderInlineStatement(stmt, maxLen);
397
+ const indent = this.indentUnit.repeat(Math.min(this.indentLevel, this.options.maxIndentLevel));
398
+ // Check if inline version fits on its own line (with indent)
399
+ const inlineWithIndent = inline !== null ? indent + inline : null;
400
+ const inlineFits = inlineWithIndent !== null && inlineWithIndent.length < maxLen;
401
+ if (inlineFits) {
402
+ // Try to pack onto current line
403
+ const sep = this.currentLine.length === 0 ? "" : " ";
404
+ const prefix = this.currentLine.length === 0 ? indent : this.currentLine;
405
+ const candidate = prefix + sep + inline;
406
+ if (candidate.length < maxLen || this.currentLine.length === 0) {
407
+ if (this.currentLine.length === 0) {
408
+ this.currentLine = indent + inline;
409
+ }
410
+ else {
411
+ this.currentLine = this.currentLine + " " + inline;
412
+ }
413
+ continue;
414
+ }
415
+ // Doesn't fit on current line, but inline exists - start new line with inline
416
+ flushLineIfAny();
417
+ this.currentLine = indent + inline;
418
+ continue;
419
+ }
420
+ // Fallback to normal multi-line printing for this statement
421
+ flushLineIfAny();
422
+ this.printIndent();
423
+ this.printStatement(node);
424
+ }
425
+ // Ensure any buffered inline statements inside this block are flushed before exiting it.
426
+ flushLineIfAny();
427
+ }
428
+ // Print a statement without any newline at the end (for inline mode)
429
+ printStatementInline(node) {
430
+ switch (node.type) {
431
+ case "AssignmentStatement": {
432
+ const st = node;
433
+ const vars = st.variables.map((v) => this.expr(v)).join(",");
434
+ const vals = st.init.map((v) => this.expr(v)).join(",");
435
+ this.emit(vars + "=" + vals);
436
+ break;
437
+ }
438
+ case "LocalStatement": {
439
+ const st = node;
440
+ const vars = st.variables.map((v) => this.expr(v)).join(",");
441
+ let s = "local " + vars;
442
+ if (st.init && st.init.length > 0) {
443
+ s += "=" + st.init.map((v) => this.expr(v)).join(",");
444
+ }
445
+ this.emit(s);
446
+ break;
447
+ }
448
+ case "CallStatement": {
449
+ const st = node;
450
+ this.emit(this.expr(st.expression));
451
+ break;
452
+ }
453
+ case "FunctionDeclaration": {
454
+ const fn = node;
455
+ let s = fn.isLocal ? "local function " : "function ";
456
+ if (fn.identifier) {
457
+ s += this.expr(fn.identifier);
458
+ }
459
+ s += "(" + fn.parameters.map((p) => this.expr(p)).join(",") + ") ";
460
+ this.emit(s);
461
+ this.printBlock(fn.body);
462
+ this.emit(" end");
463
+ break;
464
+ }
465
+ case "IfStatement": {
466
+ const ifs = node;
467
+ for (let i = 0; i < ifs.clauses.length; i++) {
468
+ const clause = ifs.clauses[i];
469
+ if (clause.type === "IfClause") {
470
+ this.emit("if " + this.expr(clause.condition) + " then ");
471
+ }
472
+ else if (clause.type === "ElseifClause") {
473
+ this.emit(" elseif " + this.expr(clause.condition) + " then ");
474
+ }
475
+ else {
476
+ this.emit(" else ");
477
+ }
478
+ this.printBlock(clause.body);
479
+ }
480
+ this.emit(" end");
481
+ break;
482
+ }
483
+ case "WhileStatement": {
484
+ const st = node;
485
+ this.emit("while " + this.expr(st.condition) + " do ");
486
+ this.printBlock(st.body);
487
+ this.emit(" end");
488
+ break;
489
+ }
490
+ case "RepeatStatement": {
491
+ const st = node;
492
+ this.emit("repeat ");
493
+ this.printBlock(st.body);
494
+ this.emit(" until " + this.expr(st.condition));
495
+ break;
496
+ }
497
+ case "ForNumericStatement": {
498
+ const st = node;
499
+ let s = "for " + this.expr(st.variable) + "=" + this.expr(st.start) + "," + this.expr(st.end);
500
+ if (st.step) {
501
+ s += "," + this.expr(st.step);
502
+ }
503
+ s += " do ";
504
+ this.emit(s);
505
+ this.printBlock(st.body);
506
+ this.emit(" end");
507
+ break;
508
+ }
509
+ case "ForGenericStatement": {
510
+ const st = node;
511
+ const vars = st.variables.map((v) => this.expr(v)).join(",");
512
+ const iters = st.iterators.map((it) => this.expr(it)).join(",");
513
+ this.emit("for " + vars + " in " + iters + " do ");
514
+ this.printBlock(st.body);
515
+ this.emit(" end");
516
+ break;
517
+ }
518
+ case "ReturnStatement": {
519
+ const st = node;
520
+ if (st.arguments.length > 0) {
521
+ this.emit("return " + st.arguments.map((a) => this.expr(a)).join(","));
522
+ }
523
+ else {
524
+ this.emit("return");
525
+ }
526
+ break;
527
+ }
528
+ case "BreakStatement":
529
+ this.emit("break");
530
+ break;
531
+ case "DoStatement": {
532
+ const st = node;
533
+ this.emit("do ");
534
+ this.printBlock(st.body);
535
+ this.emit(" end");
536
+ break;
537
+ }
538
+ case "Comment":
539
+ // Skip comments in inline mode
540
+ break;
541
+ default:
542
+ break;
543
+ }
544
+ }
545
+ // --- statement printer ---
546
+ printStatement(node) {
547
+ switch (node.type) {
548
+ case "AssignmentStatement": {
549
+ const st = node;
550
+ const vars = st.variables.map((v) => this.expr(v)).join(",");
551
+ const vals = st.init.map((v) => this.expr(v)).join(",");
552
+ this.emit(vars);
553
+ this.emit("=");
554
+ this.emit(vals);
555
+ this.newline();
556
+ break;
557
+ }
558
+ case "LocalStatement": {
559
+ const st = node;
560
+ const vars = st.variables.map((v) => this.expr(v)).join(",");
561
+ this.emitKeyword("local");
562
+ this.emit(" ");
563
+ this.emit(vars);
564
+ if (st.init && st.init.length > 0) {
565
+ const vals = st.init.map((v) => this.expr(v)).join(",");
566
+ this.emit("=");
567
+ this.emit(vals);
568
+ }
569
+ this.newline();
570
+ break;
571
+ }
572
+ case "CallStatement": {
573
+ const st = node;
574
+ this.emit(this.expr(st.expression));
575
+ this.newline();
576
+ break;
577
+ }
578
+ case "FunctionDeclaration": {
579
+ const fn = node;
580
+ if (fn.isLocal) {
581
+ this.emitKeyword("local");
582
+ this.emit(" ");
583
+ }
584
+ this.emitKeyword("function");
585
+ this.emit(" ");
586
+ if (fn.identifier) {
587
+ this.emit(this.expr(fn.identifier));
588
+ }
589
+ this.emit("(");
590
+ this.emit(fn.parameters.map((p) => this.expr(p)).join(","));
591
+ this.emit(")");
592
+ this.newline();
593
+ this.indentLevel++;
594
+ this.printBlock(fn.body);
595
+ this.indentLevel--;
596
+ this.printIndent();
597
+ this.emitKeyword("end");
598
+ this.newline();
599
+ break;
600
+ }
601
+ case "IfStatement": {
602
+ const ifs = node;
603
+ ifs.clauses.forEach((clause, idx) => {
604
+ // The enclosing block printer (`printBlock`) emits indentation only once for the
605
+ // top-level statement. Subsequent clauses need to re-emit indentation explicitly,
606
+ // otherwise `elseif`/`else` start at column 0.
607
+ if (idx > 0) {
608
+ this.printIndent();
609
+ }
610
+ if (clause.type === "IfClause") {
611
+ this.emitKeyword("if");
612
+ this.emit(" ");
613
+ this.emit(this.expr(clause.condition));
614
+ this.emitKeyword(" then");
615
+ }
616
+ else if (clause.type === "ElseifClause") {
617
+ this.emitKeyword("elseif");
618
+ this.emit(" ");
619
+ this.emit(this.expr(clause.condition));
620
+ this.emitKeyword(" then");
621
+ }
622
+ else {
623
+ this.emitKeyword("else");
624
+ }
625
+ this.newline();
626
+ this.indentLevel++;
627
+ this.printBlock(clause.body);
628
+ this.indentLevel--;
629
+ });
630
+ this.printIndent();
631
+ this.emitKeyword("end");
632
+ this.newline();
633
+ break;
634
+ }
635
+ case "WhileStatement": {
636
+ const st = node;
637
+ this.emitKeyword("while");
638
+ this.emit(" ");
639
+ this.emit(this.expr(st.condition));
640
+ this.emitKeyword(" do");
641
+ this.newline();
642
+ this.indentLevel++;
643
+ this.printBlock(st.body);
644
+ this.indentLevel--;
645
+ this.printIndent();
646
+ this.emitKeyword("end");
647
+ this.newline();
648
+ break;
649
+ }
650
+ case "RepeatStatement": {
651
+ const st = node;
652
+ this.emitKeyword("repeat");
653
+ this.newline();
654
+ this.indentLevel++;
655
+ this.printBlock(st.body);
656
+ this.indentLevel--;
657
+ this.emitKeyword("until");
658
+ this.emit(" ");
659
+ this.emit(this.expr(st.condition));
660
+ this.newline();
661
+ break;
662
+ }
663
+ case "ForNumericStatement": {
664
+ const st = node;
665
+ this.emitKeyword("for");
666
+ this.emit(" ");
667
+ this.emit(this.expr(st.variable));
668
+ this.emit("=");
669
+ this.emit(this.expr(st.start));
670
+ this.emit(",");
671
+ this.emit(this.expr(st.end));
672
+ if (st.step) {
673
+ this.emit(",");
674
+ this.emit(this.expr(st.step));
675
+ }
676
+ this.emitKeyword(" do");
677
+ this.newline();
678
+ this.indentLevel++;
679
+ this.printBlock(st.body);
680
+ this.indentLevel--;
681
+ this.printIndent();
682
+ this.emitKeyword("end");
683
+ this.newline();
684
+ break;
685
+ }
686
+ case "ForGenericStatement": {
687
+ const st = node;
688
+ this.emitKeyword("for");
689
+ this.emit(" ");
690
+ this.emit(st.variables.map((v) => this.expr(v)).join(","));
691
+ this.emitKeyword(" in ");
692
+ this.emit(st.iterators.map((it) => this.expr(it)).join(","));
693
+ this.emitKeyword(" do");
694
+ this.newline();
695
+ this.indentLevel++;
696
+ this.printBlock(st.body);
697
+ this.indentLevel--;
698
+ this.printIndent();
699
+ this.emitKeyword("end");
700
+ this.newline();
701
+ break;
702
+ }
703
+ case "ReturnStatement": {
704
+ const st = node;
705
+ this.emitKeyword("return");
706
+ if (st.arguments.length > 0) {
707
+ this.emit(" ");
708
+ this.emit(st.arguments.map((a) => this.expr(a)).join(","));
709
+ }
710
+ this.newline();
711
+ break;
712
+ }
713
+ case "BreakStatement": {
714
+ this.emitKeyword("break");
715
+ this.newline();
716
+ break;
717
+ }
718
+ case "DoStatement": {
719
+ const st = node;
720
+ this.emitKeyword("do");
721
+ this.newline();
722
+ this.indentLevel++;
723
+ this.printBlock(st.body);
724
+ this.indentLevel--;
725
+ this.printIndent();
726
+ this.emitKeyword("end");
727
+ this.newline();
728
+ break;
729
+ }
730
+ case "Comment":
731
+ this.printComment(node);
732
+ break;
733
+ default:
734
+ // console.warn("Unimplemented statement type:", node.type);
735
+ break;
736
+ }
737
+ }
738
+ // Try to render a statement as a single-line string.
739
+ // For single-line-blocks mode: only return non-null if the entire statement (including nested blocks) fits on one line.
740
+ // Returns null if the statement cannot be rendered inline under the given constraints.
741
+ renderInlineStatement(stmt, maxLen) {
742
+ // Use a temporary printer in inline mode to render everything on one line
743
+ const temp = new LuaPrinter(this.options, this.blockComments);
744
+ temp.inlineMode = true;
745
+ temp.buf = [];
746
+ temp.currentLine = "";
747
+ temp.printStatementInline(stmt);
748
+ const out = temp.currentLine.trim();
749
+ // If it exceeds maxLen, cannot inline
750
+ if (out.length > maxLen) {
751
+ return null;
752
+ }
753
+ return out;
754
+ }
755
+ // Check if statement contains nested blocks
756
+ isBlockStatement(stmt) {
757
+ switch (stmt.type) {
758
+ case "IfStatement":
759
+ case "WhileStatement":
760
+ case "RepeatStatement":
761
+ case "ForNumericStatement":
762
+ case "ForGenericStatement":
763
+ case "FunctionDeclaration":
764
+ case "DoStatement":
765
+ return true;
766
+ default:
767
+ return false;
768
+ }
769
+ }
770
+ // --- expression printer ---
771
+ expr(node, parentPrec = 0) {
772
+ if (!node)
773
+ return "";
774
+ switch (node.type) {
775
+ case "Identifier":
776
+ return node.name;
777
+ case "StringLiteral":
778
+ return this.stringLiteral(node);
779
+ case "NumericLiteral":
780
+ return this.numericLiteral(node);
781
+ case "BooleanLiteral":
782
+ return node.value ? "true" : "false";
783
+ case "NilLiteral":
784
+ return "nil";
785
+ case "VarargLiteral":
786
+ return "...";
787
+ case "TableConstructorExpression":
788
+ return this.tableConstructor(node);
789
+ case "UnaryExpression":
790
+ return this.unaryExpr(node, parentPrec);
791
+ case "BinaryExpression":
792
+ return this.binaryExpr(node, parentPrec);
793
+ case "LogicalExpression":
794
+ return this.logicalExpr(node, parentPrec);
795
+ case "MemberExpression":
796
+ return this.memberExpr(node);
797
+ case "IndexExpression":
798
+ return this.indexExpr(node);
799
+ case "CallExpression":
800
+ return this.callExpr(node);
801
+ case "TableCallExpression":
802
+ return this.tableCallExpr(node);
803
+ case "StringCallExpression":
804
+ return this.stringCallExpr(node);
805
+ case "FunctionDeclaration":
806
+ return this.functionExpr(node);
807
+ default:
808
+ return `<${node.type}>`;
809
+ }
810
+ }
811
+ stringLiteral(node) {
812
+ if (node.raw)
813
+ return node.raw;
814
+ return (0, lua_fundamentals_1.toLuaStringLiteral)(node.value);
815
+ }
816
+ numericLiteral(node, options) {
817
+ const value = node.value;
818
+ const decimalStr = Number.isFinite(value) ? value.toString(10) : String(value);
819
+ if (/^-?0\.\d/.test(decimalStr)) {
820
+ const compact = decimalStr.replace(/^(-?)0\./, "$1.");
821
+ if (options?.forceLeadingZero && /^-?\./.test(compact)) {
822
+ return compact.replace(/^(-?)\./, "$10.");
823
+ }
824
+ return compact;
825
+ }
826
+ if (options?.forceLeadingZero && /^-?\./.test(decimalStr)) {
827
+ return decimalStr.replace(/^(-?)\./, "$10.");
828
+ }
829
+ return decimalStr;
830
+ }
831
+ tableConstructor(node) {
832
+ if (node.fields.length === 0)
833
+ return "{}";
834
+ const parts = [];
835
+ for (const f of node.fields) {
836
+ if (f.type === "TableKey") {
837
+ parts.push(`[${this.expr(f.key)}]=${this.expr(f.value)}`);
838
+ }
839
+ else if (f.type === "TableKeyString") {
840
+ // luaparse gives key as an Identifier or StringLiteral
841
+ parts.push(`${this.expr(f.key)}=${this.expr(f.value)}`);
842
+ }
843
+ else {
844
+ // TableValue
845
+ parts.push(this.expr(f.value));
846
+ }
847
+ }
848
+ return `{${parts.join(",")}}`;
849
+ }
850
+ unaryExpr(node, parentPrec) {
851
+ const prec = getPrecedence(node);
852
+ const arg = this.expr(node.argument, prec);
853
+ const op = node.operator;
854
+ let s;
855
+ if (op === "not") {
856
+ s = `not ${arg}`;
857
+ }
858
+ else {
859
+ s = op + arg;
860
+ }
861
+ if (prec < parentPrec)
862
+ s = `(${s})`;
863
+ return s;
864
+ }
865
+ binaryExpr(node, parentPrec) {
866
+ const prec = getPrecedence(node);
867
+ const left = node.operator === ".." ? this.concatLeftExpr(node.left, prec) : this.expr(node.left, prec);
868
+ const rightRaw = node.operator === ".." ? this.concatRightExpr(node.right, prec) : this.expr(node.right, prec);
869
+ const right = node.operator === ".." ? this.ensureConcatSafeRight(rightRaw) : rightRaw;
870
+ let s = `${left}${node.operator}${right}`;
871
+ if (prec < parentPrec)
872
+ s = `(${s})`;
873
+ return s;
874
+ }
875
+ concatLeftExpr(node, parentPrec) {
876
+ if (node.type === "NumericLiteral") {
877
+ return `(${this.numericLiteral(node)})`;
878
+ }
879
+ if (node.type === "UnaryExpression") {
880
+ const unary = node;
881
+ if (unary.operator === "-" && unary.argument.type === "NumericLiteral") {
882
+ const arg = this.numericLiteral(unary.argument);
883
+ return `(-${arg})`;
884
+ }
885
+ }
886
+ return this.expr(node, parentPrec);
887
+ }
888
+ concatRightExpr(node, parentPrec) {
889
+ if (node.type === "NumericLiteral") {
890
+ return this.numericLiteral(node, { forceLeadingZero: true });
891
+ }
892
+ if (node.type === "UnaryExpression") {
893
+ const unary = node;
894
+ if (unary.operator === "-" && unary.argument.type === "NumericLiteral") {
895
+ const arg = this.numericLiteral(unary.argument, { forceLeadingZero: true });
896
+ let s = `-${arg}`;
897
+ if (getPrecedence(unary) < parentPrec)
898
+ s = `(${s})`;
899
+ return s;
900
+ }
901
+ }
902
+ return this.expr(node, parentPrec);
903
+ }
904
+ ensureConcatSafeRight(text) {
905
+ if (text.startsWith("-.")) {
906
+ return "-0" + text.slice(1);
907
+ }
908
+ if (text.startsWith(".")) {
909
+ return "0" + text;
910
+ }
911
+ return text;
912
+ }
913
+ logicalExpr(node, parentPrec) {
914
+ const prec = getPrecedence(node);
915
+ const left = this.expr(node.left, prec);
916
+ const right = this.expr(node.right, prec);
917
+ let s = `${left} ${node.operator} ${right}`;
918
+ if (prec < parentPrec)
919
+ s = `(${s})`;
920
+ return s;
921
+ }
922
+ memberExpr(node) {
923
+ // luaparse usually gives . or : in node.indexer
924
+ const base = this.expr(node.base, 100); // force parens if non-primary
925
+ const id = this.expr(node.identifier);
926
+ const indexer = node.indexer || ".";
927
+ return `${base}${indexer}${id}`;
928
+ }
929
+ indexExpr(node) {
930
+ const base = this.expr(node.base, 100);
931
+ return `${base}[${this.expr(node.index)}]`;
932
+ }
933
+ callExpr(node) {
934
+ const base = this.expr(node.base, 100);
935
+ const args = node.arguments.map((a) => this.expr(a)).join(",");
936
+ return `${base}(${args})`;
937
+ }
938
+ tableCallExpr(node) {
939
+ // sugar: f{...} → f({ ... })
940
+ const base = this.expr(node.base, 100);
941
+ const arg = this.expr(node.arguments);
942
+ return `${base}(${arg})`;
943
+ }
944
+ stringCallExpr(node) {
945
+ // sugar: f"str" → f("str")
946
+ const base = this.expr(node.base, 100);
947
+ const arg = this.stringLiteral(node.argument);
948
+ return `${base}(${arg})`;
949
+ }
950
+ functionExpr(node) {
951
+ // function used as expression: "function(a,b) ... end"
952
+ const params = node.parameters.map((p) => this.expr(p)).join(",");
953
+ const bodyPrinter = new LuaPrinter(this.options, this.blockComments);
954
+ // reuse statement printer but avoid duplicating indent handling:
955
+ const innerChunk = {
956
+ type: "Chunk",
957
+ body: node.body,
958
+ comments: [],
959
+ //globals: [],
960
+ };
961
+ const bodyCode = bodyPrinter.print(innerChunk).trimEnd();
962
+ return `function(${params})\n${bodyCode}\nend`;
963
+ }
964
+ printComment(node) {
965
+ if (node.raw) {
966
+ this.emit(node.raw);
967
+ }
968
+ else {
969
+ this.emit("--" + node.value);
970
+ }
971
+ this.newline();
972
+ }
973
+ }
974
+ exports.LuaPrinter = LuaPrinter;
975
+ function nodeRange(node) {
976
+ if (node && Array.isArray(node.range) && node.range.length > 1) {
977
+ return node.range;
978
+ }
979
+ return [0, Number.MAX_SAFE_INTEGER];
980
+ }
981
+ function rangeContains(outer, inner) {
982
+ return inner[0] >= outer[0] && inner[1] <= outer[1];
983
+ }
984
+ // Collect all statement blocks (function bodies, if/else bodies, loops, etc.)
985
+ function collectBlocksFromStatement(node, blocks) {
986
+ switch (node.type) {
987
+ case "FunctionDeclaration": {
988
+ const fn = node;
989
+ blocks.push({ body: fn.body, range: nodeRange(fn) });
990
+ fn.body.forEach((st) => collectBlocksFromStatement(st, blocks));
991
+ break;
992
+ }
993
+ case "IfStatement": {
994
+ const ifs = node;
995
+ ifs.clauses.forEach((clause) => {
996
+ blocks.push({ body: clause.body, range: nodeRange(clause) });
997
+ clause.body.forEach((st) => collectBlocksFromStatement(st, blocks));
998
+ });
999
+ break;
1000
+ }
1001
+ case "WhileStatement":
1002
+ case "RepeatStatement":
1003
+ case "ForNumericStatement":
1004
+ case "ForGenericStatement":
1005
+ case "DoStatement": {
1006
+ const body = node.body;
1007
+ blocks.push({ body, range: nodeRange(node) });
1008
+ body.forEach((st) => collectBlocksFromStatement(st, blocks));
1009
+ break;
1010
+ }
1011
+ default:
1012
+ break;
1013
+ }
1014
+ }
1015
+ function collectAllStatementBlocks(chunk) {
1016
+ const blocks = [
1017
+ { body: chunk.body, range: nodeRange(chunk) },
1018
+ ];
1019
+ for (const st of chunk.body) {
1020
+ collectBlocksFromStatement(st, blocks);
1021
+ }
1022
+ return blocks;
1023
+ }
1024
+ // Build a map from statement blocks to comments contained within them
1025
+ // why is this needed?
1026
+ // luaparse gives comments attached to the root chunk only, not to inner blocks
1027
+ // so we have to manually assign them to the correct blocks
1028
+ function buildCommentMap(ast) {
1029
+ const blocks = collectAllStatementBlocks(ast);
1030
+ const map = new Map();
1031
+ blocks.forEach((b) => map.set(b.body, []));
1032
+ const comments = ast.comments || [];
1033
+ for (const c of comments) {
1034
+ const cr = nodeRange(c);
1035
+ let target = blocks[0];
1036
+ for (const blk of blocks) {
1037
+ if (rangeContains(blk.range, cr)) {
1038
+ const widthCurrent = target ? target.range[1] - target.range[0] : Number.MAX_SAFE_INTEGER;
1039
+ const widthCandidate = blk.range[1] - blk.range[0];
1040
+ if (widthCandidate <= widthCurrent) {
1041
+ target = blk;
1042
+ }
1043
+ }
1044
+ }
1045
+ const list = map.get(target.body) || [];
1046
+ list.push(c);
1047
+ map.set(target.body, list);
1048
+ }
1049
+ for (const [body, list] of map.entries()) {
1050
+ list.sort((a, b) => nodeRange(a)[0] - nodeRange(b)[0]);
1051
+ }
1052
+ return map;
1053
+ }
1054
+ // Generate Lua code from an AST
1055
+ function unparseLua(ast, ruleOptions) {
1056
+ const generator = new LuaPrinter(ruleOptions, buildCommentMap(ast));
1057
+ return generator.print(ast);
1058
+ }
1059
+ function parseLua(code) {
1060
+ //console.log(code);
1061
+ try {
1062
+ const ast = luaparse.parse(code, {
1063
+ luaVersion: "5.3", // TIC-80 is 5.3-ish
1064
+ comments: true,
1065
+ locations: true,
1066
+ ranges: true,
1067
+ });
1068
+ return ast;
1069
+ }
1070
+ catch (error) {
1071
+ console.error("Error parsing Lua code:", error);
1072
+ console.log("Lua code:\n", code);
1073
+ }
1074
+ return null;
1075
+ }
1076
+ function processLua(code, ruleOptions) {
1077
+ // Apply optimization rules
1078
+ //const options = {...DEFAULT_OPTIMIZATION_RULES, ...ruleOptions};
1079
+ // Strip debug blocks and lines before parsing (line-based string matching)
1080
+ let processedCode = code;
1081
+ // if (ruleOptions.stripDebugBlocks) {
1082
+ // // Strip debug blocks
1083
+ // processedCode = replaceLuaBlock(processedCode, "-- BEGIN_DEBUG_ONLY", "-- END_DEBUG_ONLY", "");
1084
+ // // Strip individual lines marked with -- DEBUG_ONLY
1085
+ // const eol = processedCode.includes("\r\n") ? "\r\n" : "\n";
1086
+ // const lines = processedCode.split(eol);
1087
+ // const filteredLines = lines.filter(line => !line.includes("-- DEBUG_ONLY"));
1088
+ // processedCode = filteredLines.join(eol);
1089
+ // }
1090
+ // Honor explicit directives to keep certain regions verbatim
1091
+ // doing this at text level for simplification and because the printer can reformat everything.
1092
+ const disableMinify = (0, lua_fundamentals_1.extractLuaBlocks)(processedCode, "-- MINIFICATION OFF", "-- MINIFICATION ON", (i) => `__SOMATIC_DISABLED_MINIFICATION_BLOCK_${i}__()`, { strict: false });
1093
+ processedCode = disableMinify.code;
1094
+ let ast = parseLua(processedCode);
1095
+ if (!ast) {
1096
+ console.error("Failed to parse Lua code; returning original code.");
1097
+ return code;
1098
+ }
1099
+ //console.log("Parsed Lua AST:", ast);
1100
+ if (ruleOptions.stripComments) {
1101
+ ast.comments = [];
1102
+ }
1103
+ if (ruleOptions.simplifyExpressions) {
1104
+ ast = (0, lua_simplify_1.simplifyExpressionsInAST)(ast);
1105
+ }
1106
+ if (ruleOptions.removeUnusedLocals) {
1107
+ ast = (0, lua_remove_unused_locals_1.removeUnusedLocalsInAST)(ast);
1108
+ }
1109
+ if (ruleOptions.removeUnusedFunctions) {
1110
+ ast = (0, lua_remove_unused_functions_1.removeUnusedFunctionsInAST)(ast, {
1111
+ functionNamesToKeep: ruleOptions.functionNamesToKeep,
1112
+ });
1113
+ }
1114
+ if (ruleOptions.aliasLiterals) {
1115
+ ast = (0, lua_alias_literals_1.aliasLiteralsInAST)(ast);
1116
+ }
1117
+ if (ruleOptions.aliasRepeatedExpressions) {
1118
+ ast = (0, lua_alias_expressions_1.aliasRepeatedExpressionsInAST)(ast);
1119
+ }
1120
+ if (ruleOptions.packLocalDeclarations) {
1121
+ ast = (0, lua_pack_locals_1.packLocalDeclarationsInAST)(ast);
1122
+ }
1123
+ if (ruleOptions.renameLocalVariables) {
1124
+ ast = (0, lua_renamer_1.renameLocalVariablesInAST)(ast);
1125
+ }
1126
+ if (ruleOptions.tableEntryKeysToRename && ruleOptions.tableEntryKeysToRename.length > 0) {
1127
+ ast = (0, lua_rename_allowed_table_keys_1.renameAllowedTableKeysInAST)(ast, ruleOptions.tableEntryKeysToRename);
1128
+ }
1129
+ if (ruleOptions.renameTableFields) {
1130
+ ast = (0, lua_rename_table_fields_1.renameTableFieldsInAST)(ast);
1131
+ }
1132
+ const minified = unparseLua(ast, ruleOptions);
1133
+ return reinsertDisableMinificationBlocks(minified, disableMinify.blocks);
1134
+ }
1135
+ // Escape special characters in a string for use in a RegExp
1136
+ function escapeRegExp(s) {
1137
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1138
+ }
1139
+ function reinsertDisableMinificationBlocks(src, blocks) {
1140
+ if (blocks.length === 0)
1141
+ return src;
1142
+ let out = src;
1143
+ for (const b of blocks) {
1144
+ // normalize line endings and trim trailing newlines from the block content
1145
+ const normalized = b.content.replace(/\r?\n/g, "\n").replace(/\n+$/g, "");
1146
+ const replacement = `\n${normalized}\n`;
1147
+ // remove surrounding whitespace introduced by tight packing.
1148
+ const re = new RegExp(`[\\t ]*${escapeRegExp(b.placeholder)}[\\t ]*`, "g");
1149
+ out = out.replace(re, replacement);
1150
+ }
1151
+ return out;
1152
+ }
1153
+ //# sourceMappingURL=lua_processor.js.map