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.
- package/.attachments/support_me_on_kofi_beige.png +0 -0
- package/.env.example +3 -0
- package/.prettierignore +10 -0
- package/LICENSE +15 -0
- package/README.md +429 -0
- package/debug/obj/resolvedManifest.ticbuild.jsonc +108 -0
- package/dist/backend/ImportedResource.d.ts +11 -0
- package/dist/backend/ImportedResource.d.ts.map +1 -0
- package/dist/backend/ImportedResource.js +53 -0
- package/dist/backend/ImportedResource.js.map +1 -0
- package/dist/backend/ImportedResourceTypes.d.ts +24 -0
- package/dist/backend/ImportedResourceTypes.d.ts.map +1 -0
- package/dist/backend/ImportedResourceTypes.js +35 -0
- package/dist/backend/ImportedResourceTypes.js.map +1 -0
- package/dist/backend/codeBanking.test.d.ts +2 -0
- package/dist/backend/codeBanking.test.d.ts.map +1 -0
- package/dist/backend/codeBanking.test.js.map +1 -0
- package/dist/backend/importResources.d.ts +4 -0
- package/dist/backend/importResources.d.ts.map +1 -0
- package/dist/backend/importResources.js +58 -0
- package/dist/backend/importResources.js.map +1 -0
- package/dist/backend/importUtils.d.ts +14 -0
- package/dist/backend/importUtils.d.ts.map +1 -0
- package/dist/backend/importUtils.js +77 -0
- package/dist/backend/importUtils.js.map +1 -0
- package/dist/backend/importers/LuaCodeImporter.d.ts +47 -0
- package/dist/backend/importers/LuaCodeImporter.d.ts.map +1 -0
- package/dist/backend/importers/LuaCodeImporter.js +196 -0
- package/dist/backend/importers/LuaCodeImporter.js.map +1 -0
- package/dist/backend/importers/LuaCodeImporter.test.d.ts +2 -0
- package/dist/backend/importers/LuaCodeImporter.test.d.ts.map +1 -0
- package/dist/backend/importers/LuaCodeImporter.test.js.map +1 -0
- package/dist/backend/importers/binaryResourceImporter.d.ts +22 -0
- package/dist/backend/importers/binaryResourceImporter.d.ts.map +1 -0
- package/dist/backend/importers/binaryResourceImporter.js +53 -0
- package/dist/backend/importers/binaryResourceImporter.js.map +1 -0
- package/dist/backend/importers/luaImporter.d.ts +1 -0
- package/dist/backend/importers/luaImporter.d.ts.map +1 -0
- package/dist/backend/importers/luaImporter.js +3 -0
- package/dist/backend/importers/luaImporter.js.map +1 -0
- package/dist/backend/importers/textResourceImporter.d.ts +23 -0
- package/dist/backend/importers/textResourceImporter.d.ts.map +1 -0
- package/dist/backend/importers/textResourceImporter.js +55 -0
- package/dist/backend/importers/textResourceImporter.js.map +1 -0
- package/dist/backend/importers/tic80CartImporter.d.ts +21 -0
- package/dist/backend/importers/tic80CartImporter.d.ts.map +1 -0
- package/dist/backend/importers/tic80CartImporter.js +96 -0
- package/dist/backend/importers/tic80CartImporter.js.map +1 -0
- package/dist/backend/loadAllImports.d.ts +1 -0
- package/dist/backend/loadAllImports.d.ts.map +1 -0
- package/dist/backend/loadAllImports.js +3 -0
- package/dist/backend/loadAllImports.js.map +1 -0
- package/dist/backend/luaBinaryEncoding.d.ts +6 -0
- package/dist/backend/luaBinaryEncoding.d.ts.map +1 -0
- package/dist/backend/luaBinaryEncoding.js +94 -0
- package/dist/backend/luaBinaryEncoding.js.map +1 -0
- package/dist/backend/luaPreprocessor.d.ts +8 -0
- package/dist/backend/luaPreprocessor.d.ts.map +1 -0
- package/dist/backend/luaPreprocessor.js +862 -0
- package/dist/backend/luaPreprocessor.js.map +1 -0
- package/dist/backend/luaPreprocessor.test.d.ts +2 -0
- package/dist/backend/luaPreprocessor.test.d.ts.map +1 -0
- package/dist/backend/luaPreprocessor.test.js.map +1 -0
- package/dist/backend/manifestLoader.d.ts +19 -0
- package/dist/backend/manifestLoader.d.ts.map +1 -0
- package/dist/backend/manifestLoader.js +142 -0
- package/dist/backend/manifestLoader.js.map +1 -0
- package/dist/backend/manifestLoader.test.d.ts +2 -0
- package/dist/backend/manifestLoader.test.d.ts.map +1 -0
- package/dist/backend/manifestLoader.test.js.map +1 -0
- package/dist/backend/manifestTypes.d.ts +454 -0
- package/dist/backend/manifestTypes.d.ts.map +1 -0
- package/dist/backend/manifestTypes.js +28 -0
- package/dist/backend/manifestTypes.js.map +1 -0
- package/dist/backend/project.d.ts +24 -0
- package/dist/backend/project.d.ts.map +1 -0
- package/dist/backend/project.js +159 -0
- package/dist/backend/project.js.map +1 -0
- package/dist/backend/projectCore.d.ts +34 -0
- package/dist/backend/projectCore.d.ts.map +1 -0
- package/dist/backend/projectCore.js +226 -0
- package/dist/backend/projectCore.js.map +1 -0
- package/dist/backend/tic80Resolver.d.ts +6 -0
- package/dist/backend/tic80Resolver.d.ts.map +1 -0
- package/dist/backend/tic80Resolver.js +66 -0
- package/dist/backend/tic80Resolver.js.map +1 -0
- package/dist/buildInfo.d.ts +9 -0
- package/dist/buildInfo.d.ts.map +1 -0
- package/dist/buildInfo.js +13 -0
- package/dist/buildInfo.js.map +1 -0
- package/dist/frontend/build.d.ts +3 -0
- package/dist/frontend/build.d.ts.map +1 -0
- package/dist/frontend/build.js +8 -0
- package/dist/frontend/build.js.map +1 -0
- package/dist/frontend/codeBankWarnings.test.d.ts +2 -0
- package/dist/frontend/codeBankWarnings.test.d.ts.map +1 -0
- package/dist/frontend/codeBankWarnings.test.js.map +1 -0
- package/dist/frontend/core.d.ts +3 -0
- package/dist/frontend/core.d.ts.map +1 -0
- package/dist/frontend/core.js +259 -0
- package/dist/frontend/core.js.map +1 -0
- package/dist/frontend/init.d.ts +7 -0
- package/dist/frontend/init.d.ts.map +1 -0
- package/dist/frontend/init.js +95 -0
- package/dist/frontend/init.js.map +1 -0
- package/dist/frontend/parseOptions.d.ts +7 -0
- package/dist/frontend/parseOptions.d.ts.map +1 -0
- package/dist/frontend/parseOptions.js +68 -0
- package/dist/frontend/parseOptions.js.map +1 -0
- package/dist/frontend/run.d.ts +3 -0
- package/dist/frontend/run.d.ts.map +1 -0
- package/dist/frontend/run.js +63 -0
- package/dist/frontend/run.js.map +1 -0
- package/dist/frontend/watch.d.ts +3 -0
- package/dist/frontend/watch.d.ts.map +1 -0
- package/dist/frontend/watch.js +208 -0
- package/dist/frontend/watch.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +191 -0
- package/dist/index.js.map +1 -0
- package/dist/obj/resolvedManifest.ticbuild.jsonc +110 -0
- package/dist/obj/variables.json +19 -0
- package/dist/utils/algorithms.d.ts +4 -0
- package/dist/utils/algorithms.d.ts.map +1 -0
- package/dist/utils/algorithms.js +15 -0
- package/dist/utils/algorithms.js.map +1 -0
- package/dist/utils/algorithms.test.d.ts +2 -0
- package/dist/utils/algorithms.test.d.ts.map +1 -0
- package/dist/utils/algorithms.test.js.map +1 -0
- package/dist/utils/bin.d.ts +4 -0
- package/dist/utils/bin.d.ts.map +1 -0
- package/dist/utils/bin.js +16 -0
- package/dist/utils/bin.js.map +1 -0
- package/dist/utils/charMap.d.ts +28 -0
- package/dist/utils/charMap.d.ts.map +1 -0
- package/dist/utils/charMap.js +31 -0
- package/dist/utils/charMap.js.map +1 -0
- package/dist/utils/console.d.ts +10 -0
- package/dist/utils/console.d.ts.map +1 -0
- package/dist/utils/console.js +66 -0
- package/dist/utils/console.js.map +1 -0
- package/dist/utils/encoding/b85.d.ts +5 -0
- package/dist/utils/encoding/b85.d.ts.map +1 -0
- package/dist/utils/encoding/b85.js +136 -0
- package/dist/utils/encoding/b85.js.map +1 -0
- package/dist/utils/encoding/codecRegistry.d.ts +333 -0
- package/dist/utils/encoding/codecRegistry.d.ts.map +1 -0
- package/dist/utils/encoding/codecRegistry.js +81 -0
- package/dist/utils/encoding/codecRegistry.js.map +1 -0
- package/dist/utils/encoding/hex.d.ts +3 -0
- package/dist/utils/encoding/hex.d.ts.map +1 -0
- package/dist/utils/encoding/hex.js +30 -0
- package/dist/utils/encoding/hex.js.map +1 -0
- package/dist/utils/encoding/lz.d.ts +12 -0
- package/dist/utils/encoding/lz.d.ts.map +1 -0
- package/dist/utils/encoding/lz.js +271 -0
- package/dist/utils/encoding/lz.js.map +1 -0
- package/dist/utils/enum.d.ts +45 -0
- package/dist/utils/enum.d.ts.map +1 -0
- package/dist/utils/enum.js +135 -0
- package/dist/utils/enum.js.map +1 -0
- package/dist/utils/errorHandling.d.ts +13 -0
- package/dist/utils/errorHandling.d.ts.map +1 -0
- package/dist/utils/errorHandling.js +18 -0
- package/dist/utils/errorHandling.js.map +1 -0
- package/dist/utils/fileSystem.d.ts +16 -0
- package/dist/utils/fileSystem.d.ts.map +1 -0
- package/dist/utils/fileSystem.js +161 -0
- package/dist/utils/fileSystem.js.map +1 -0
- package/dist/utils/help.d.ts +7 -0
- package/dist/utils/help.d.ts.map +1 -0
- package/dist/utils/help.js +87 -0
- package/dist/utils/help.js.map +1 -0
- package/dist/utils/lua/luaUtils.d.ts +1 -0
- package/dist/utils/lua/luaUtils.d.ts.map +1 -0
- package/dist/utils/lua/luaUtils.js +3 -0
- package/dist/utils/lua/luaUtils.js.map +1 -0
- package/dist/utils/lua/lua_alias_expressions.d.ts +20 -0
- package/dist/utils/lua/lua_alias_expressions.d.ts.map +1 -0
- package/dist/utils/lua/lua_alias_expressions.js +233 -0
- package/dist/utils/lua/lua_alias_expressions.js.map +1 -0
- package/dist/utils/lua/lua_alias_literals.d.ts +20 -0
- package/dist/utils/lua/lua_alias_literals.d.ts.map +1 -0
- package/dist/utils/lua/lua_alias_literals.js +165 -0
- package/dist/utils/lua/lua_alias_literals.js.map +1 -0
- package/dist/utils/lua/lua_alias_shared.d.ts +31 -0
- package/dist/utils/lua/lua_alias_shared.d.ts.map +1 -0
- package/dist/utils/lua/lua_alias_shared.js +415 -0
- package/dist/utils/lua/lua_alias_shared.js.map +1 -0
- package/dist/utils/lua/lua_ast.d.ts +9 -0
- package/dist/utils/lua/lua_ast.d.ts.map +1 -0
- package/dist/utils/lua/lua_ast.js +90 -0
- package/dist/utils/lua/lua_ast.js.map +1 -0
- package/dist/utils/lua/lua_fundamentals.d.ts +14 -0
- package/dist/utils/lua/lua_fundamentals.d.ts.map +1 -0
- package/dist/utils/lua/lua_fundamentals.js +93 -0
- package/dist/utils/lua/lua_fundamentals.js.map +1 -0
- package/dist/utils/lua/lua_pack_locals.d.ts +3 -0
- package/dist/utils/lua/lua_pack_locals.d.ts.map +1 -0
- package/dist/utils/lua/lua_pack_locals.js +206 -0
- package/dist/utils/lua/lua_pack_locals.js.map +1 -0
- package/dist/utils/lua/lua_processor.d.ts +65 -0
- package/dist/utils/lua/lua_processor.d.ts.map +1 -0
- package/dist/utils/lua/lua_processor.js +1153 -0
- package/dist/utils/lua/lua_processor.js.map +1 -0
- package/dist/utils/lua/lua_processor.test.d.ts +2 -0
- package/dist/utils/lua/lua_processor.test.d.ts.map +1 -0
- package/dist/utils/lua/lua_processor.test.js.map +1 -0
- package/dist/utils/lua/lua_remove_unused_functions.d.ts +6 -0
- package/dist/utils/lua/lua_remove_unused_functions.d.ts.map +1 -0
- package/dist/utils/lua/lua_remove_unused_functions.js +474 -0
- package/dist/utils/lua/lua_remove_unused_functions.js.map +1 -0
- package/dist/utils/lua/lua_remove_unused_locals.d.ts +3 -0
- package/dist/utils/lua/lua_remove_unused_locals.d.ts.map +1 -0
- package/dist/utils/lua/lua_remove_unused_locals.js +303 -0
- package/dist/utils/lua/lua_remove_unused_locals.js.map +1 -0
- package/dist/utils/lua/lua_rename_allowed_table_keys.d.ts +3 -0
- package/dist/utils/lua/lua_rename_allowed_table_keys.d.ts.map +1 -0
- package/dist/utils/lua/lua_rename_allowed_table_keys.js +157 -0
- package/dist/utils/lua/lua_rename_allowed_table_keys.js.map +1 -0
- package/dist/utils/lua/lua_rename_table_fields.d.ts +3 -0
- package/dist/utils/lua/lua_rename_table_fields.d.ts.map +1 -0
- package/dist/utils/lua/lua_rename_table_fields.js +427 -0
- package/dist/utils/lua/lua_rename_table_fields.js.map +1 -0
- package/dist/utils/lua/lua_renamer.d.ts +3 -0
- package/dist/utils/lua/lua_renamer.d.ts.map +1 -0
- package/dist/utils/lua/lua_renamer.js +229 -0
- package/dist/utils/lua/lua_renamer.js.map +1 -0
- package/dist/utils/lua/lua_simplify.d.ts +3 -0
- package/dist/utils/lua/lua_simplify.d.ts.map +1 -0
- package/dist/utils/lua/lua_simplify.js +541 -0
- package/dist/utils/lua/lua_simplify.js.map +1 -0
- package/dist/utils/lua/lua_utils.d.ts +13 -0
- package/dist/utils/lua/lua_utils.d.ts.map +1 -0
- package/dist/utils/lua/lua_utils.js +58 -0
- package/dist/utils/lua/lua_utils.js.map +1 -0
- package/dist/utils/math.d.ts +2 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/math.js +7 -0
- package/dist/utils/math.js.map +1 -0
- package/dist/utils/math.test.d.ts +2 -0
- package/dist/utils/math.test.d.ts.map +1 -0
- package/dist/utils/math.test.js.map +1 -0
- package/dist/utils/templates.d.ts +3 -0
- package/dist/utils/templates.d.ts.map +1 -0
- package/dist/utils/templates.js +57 -0
- package/dist/utils/templates.js.map +1 -0
- package/dist/utils/tic80/bankSupport.test.d.ts +2 -0
- package/dist/utils/tic80/bankSupport.test.d.ts.map +1 -0
- package/dist/utils/tic80/bankSupport.test.js.map +1 -0
- package/dist/utils/tic80/cartLoader.d.ts +3 -0
- package/dist/utils/tic80/cartLoader.d.ts.map +1 -0
- package/dist/utils/tic80/cartLoader.js +54 -0
- package/dist/utils/tic80/cartLoader.js.map +1 -0
- package/dist/utils/tic80/cartWriter.d.ts +5 -0
- package/dist/utils/tic80/cartWriter.d.ts.map +1 -0
- package/dist/utils/tic80/cartWriter.js +95 -0
- package/dist/utils/tic80/cartWriter.js.map +1 -0
- package/dist/utils/tic80/launch.d.ts +4 -0
- package/dist/utils/tic80/launch.d.ts.map +1 -0
- package/dist/utils/tic80/launch.js +36 -0
- package/dist/utils/tic80/launch.js.map +1 -0
- package/dist/utils/tic80/tic80.d.ts +1149 -0
- package/dist/utils/tic80/tic80.d.ts.map +1 -0
- package/dist/utils/tic80/tic80.js +114 -0
- package/dist/utils/tic80/tic80.js.map +1 -0
- package/dist/utils/utils.d.ts +13 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +109 -0
- package/dist/utils/utils.js.map +1 -0
- package/dist/utils/versionString.d.ts +12 -0
- package/dist/utils/versionString.d.ts.map +1 -0
- package/dist/utils/versionString.js +33 -0
- package/dist/utils/versionString.js.map +1 -0
- package/dist/utils/windowPosition.d.ts +10 -0
- package/dist/utils/windowPosition.d.ts.map +1 -0
- package/dist/utils/windowPosition.js +222 -0
- package/dist/utils/windowPosition.js.map +1 -0
- package/example.ticbuild.jsonc +94 -0
- package/package.json +51 -0
- package/templates/help/build.txt +23 -0
- package/templates/help/init.txt +23 -0
- package/templates/help/main.txt +41 -0
- package/templates/help/run.txt +22 -0
- package/templates/help/tic80.txt +8 -0
- package/templates/help/watch.txt +24 -0
- package/templates/minimal/project.ticbuild.jsonc +43 -0
- package/ticbuild-1.0.0.tgz +0 -0
- package/ticbuild.schema.json +327 -0
|
@@ -0,0 +1,862 @@
|
|
|
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.preprocessLuaCode = preprocessLuaCode;
|
|
37
|
+
const luaparse = __importStar(require("luaparse"));
|
|
38
|
+
const fileSystem_1 = require("../utils/fileSystem");
|
|
39
|
+
const lua_fundamentals_1 = require("../utils/lua/lua_fundamentals");
|
|
40
|
+
const lua_utils_1 = require("../utils/lua/lua_utils");
|
|
41
|
+
const cartLoader_1 = require("../utils/tic80/cartLoader");
|
|
42
|
+
const codecRegistry_1 = require("../utils/encoding/codecRegistry");
|
|
43
|
+
const luaBinaryEncoding_1 = require("./luaBinaryEncoding");
|
|
44
|
+
const importUtils_1 = require("./importUtils");
|
|
45
|
+
const manifestTypes_1 = require("./manifestTypes");
|
|
46
|
+
async function preprocessLuaCode(project, source, filePath) {
|
|
47
|
+
const state = {
|
|
48
|
+
defines: new Map(),
|
|
49
|
+
dependencies: new Set(),
|
|
50
|
+
pragmaOnceKeys: new Set(),
|
|
51
|
+
includeStack: [],
|
|
52
|
+
macros: new Map(),
|
|
53
|
+
};
|
|
54
|
+
const includeKey = makeIncludeKey(filePath, {});
|
|
55
|
+
const rawCode = await processSource(project, source, filePath, includeKey, state, {});
|
|
56
|
+
const expandedCode = expandMacros(project, rawCode, state.macros, filePath);
|
|
57
|
+
const finalCode = await expandPreprocessorCalls(project, expandedCode, filePath, state);
|
|
58
|
+
return {
|
|
59
|
+
code: finalCode,
|
|
60
|
+
dependencies: Array.from(state.dependencies.values()),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async function processSource(project, source, filePath, includeKey, state, inputOverrides, trackDependency = true) {
|
|
64
|
+
if (state.pragmaOnceKeys.has(includeKey)) {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
if (state.includeStack.includes(includeKey)) {
|
|
68
|
+
const cycle = [...state.includeStack, includeKey].join(" -> ");
|
|
69
|
+
throw new Error(`Lua preprocessor include cycle detected: ${cycle}`);
|
|
70
|
+
}
|
|
71
|
+
state.includeStack.push(includeKey);
|
|
72
|
+
if (trackDependency) {
|
|
73
|
+
state.dependencies.add(filePath);
|
|
74
|
+
}
|
|
75
|
+
const hasOverrides = Object.keys(inputOverrides).length > 0;
|
|
76
|
+
const localDefines = hasOverrides ? new Map(state.defines) : state.defines;
|
|
77
|
+
if (hasOverrides) {
|
|
78
|
+
for (const [key, value] of Object.entries(inputOverrides)) {
|
|
79
|
+
localDefines.set(key, value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const conditionalStack = [];
|
|
83
|
+
const outputLines = [];
|
|
84
|
+
// helper to check if current line is in active conditional block
|
|
85
|
+
const isActive = () => {
|
|
86
|
+
if (conditionalStack.length === 0) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return conditionalStack[conditionalStack.length - 1].active;
|
|
90
|
+
};
|
|
91
|
+
const lines = source.split(/\r?\n/);
|
|
92
|
+
for (let i = 0; i < lines.length; i++) {
|
|
93
|
+
const line = lines[i];
|
|
94
|
+
const lineNumber = i + 1;
|
|
95
|
+
const directiveMatch = line.match(/^\s*--#\s*(\w+)\s*(.*)$/);
|
|
96
|
+
if (!directiveMatch) {
|
|
97
|
+
if (isActive()) {
|
|
98
|
+
outputLines.push(line);
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const directive = directiveMatch[1];
|
|
103
|
+
const rest = directiveMatch[2] || "";
|
|
104
|
+
switch (directive) {
|
|
105
|
+
case "macro": {
|
|
106
|
+
const macroHeader = parseMacroHeader(rest, filePath, lineNumber);
|
|
107
|
+
if (macroHeader.inlineBody !== undefined) {
|
|
108
|
+
if (isActive()) {
|
|
109
|
+
state.macros.set(macroHeader.name, {
|
|
110
|
+
name: macroHeader.name,
|
|
111
|
+
params: macroHeader.params,
|
|
112
|
+
body: macroHeader.inlineBody,
|
|
113
|
+
sourceFile: filePath,
|
|
114
|
+
lineNumber,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
const bodyResult = readMacroBody(lines, i + 1, filePath, lineNumber);
|
|
120
|
+
i = bodyResult.endIndex;
|
|
121
|
+
if (isActive()) {
|
|
122
|
+
state.macros.set(macroHeader.name, {
|
|
123
|
+
name: macroHeader.name,
|
|
124
|
+
params: macroHeader.params,
|
|
125
|
+
body: bodyResult.body,
|
|
126
|
+
sourceFile: filePath,
|
|
127
|
+
lineNumber,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "endmacro": {
|
|
133
|
+
throw new Error(formatError(filePath, lineNumber, `--#endmacro without matching --#macro`));
|
|
134
|
+
}
|
|
135
|
+
case "define": {
|
|
136
|
+
if (!isActive()) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
const defineMatch = rest.match(/^([A-Za-z_][A-Za-z0-9_]*)(?:\s+(.*))?$/);
|
|
140
|
+
if (!defineMatch) {
|
|
141
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid --#define syntax: ${line}`));
|
|
142
|
+
}
|
|
143
|
+
const name = defineMatch[1];
|
|
144
|
+
const expr = defineMatch[2];
|
|
145
|
+
if (!expr || expr.trim() === "") {
|
|
146
|
+
localDefines.set(name, true);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const value = evaluateExpression(parseExpression(expr, filePath, lineNumber), localDefines, filePath, lineNumber);
|
|
150
|
+
localDefines.set(name, value);
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case "undef": {
|
|
155
|
+
if (!isActive()) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
const undefMatch = rest.match(/^([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
159
|
+
if (!undefMatch) {
|
|
160
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid --#undef syntax: ${line}`));
|
|
161
|
+
}
|
|
162
|
+
localDefines.delete(undefMatch[1]);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case "if": {
|
|
166
|
+
const parentActive = isActive();
|
|
167
|
+
let conditionMet = false;
|
|
168
|
+
if (parentActive) {
|
|
169
|
+
if (!rest || rest.trim() === "") {
|
|
170
|
+
throw new Error(formatError(filePath, lineNumber, `Missing expression in --#if`));
|
|
171
|
+
}
|
|
172
|
+
const exprValue = evaluateExpression(parseExpression(rest, filePath, lineNumber), localDefines, filePath, lineNumber);
|
|
173
|
+
conditionMet = isTruthy(exprValue);
|
|
174
|
+
}
|
|
175
|
+
conditionalStack.push({
|
|
176
|
+
parentActive,
|
|
177
|
+
conditionMet,
|
|
178
|
+
active: parentActive && conditionMet,
|
|
179
|
+
hasElse: false,
|
|
180
|
+
});
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case "else": {
|
|
184
|
+
if (conditionalStack.length === 0) {
|
|
185
|
+
throw new Error(formatError(filePath, lineNumber, `--#else without matching --#if`));
|
|
186
|
+
}
|
|
187
|
+
const top = conditionalStack[conditionalStack.length - 1];
|
|
188
|
+
if (top.hasElse) {
|
|
189
|
+
throw new Error(formatError(filePath, lineNumber, `Duplicate --#else for same --#if`));
|
|
190
|
+
}
|
|
191
|
+
top.hasElse = true;
|
|
192
|
+
top.active = top.parentActive && !top.conditionMet;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case "endif": {
|
|
196
|
+
if (conditionalStack.length === 0) {
|
|
197
|
+
throw new Error(formatError(filePath, lineNumber, `--#endif without matching --#if`));
|
|
198
|
+
}
|
|
199
|
+
conditionalStack.pop();
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "pragma": {
|
|
203
|
+
if (!isActive()) {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
const pragmaMatch = rest.trim().match(/^(\w+)$/);
|
|
207
|
+
if (!pragmaMatch) {
|
|
208
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid --#pragma syntax: ${line}`));
|
|
209
|
+
}
|
|
210
|
+
if (pragmaMatch[1] === "once") {
|
|
211
|
+
state.pragmaOnceKeys.add(includeKey);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
throw new Error(formatError(filePath, lineNumber, `Unknown pragma: ${pragmaMatch[1]}`));
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case "include": {
|
|
219
|
+
if (!isActive()) {
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
const includeMatch = rest.trim().match(/^"([^"]+)"(.*)$/);
|
|
223
|
+
if (!includeMatch) {
|
|
224
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid --#include syntax: ${line}`));
|
|
225
|
+
}
|
|
226
|
+
const includeTarget = includeMatch[1];
|
|
227
|
+
const remainder = includeMatch[2] || "";
|
|
228
|
+
const overrides = parseWithOverrides(remainder, localDefines, filePath, lineNumber);
|
|
229
|
+
const includedText = await resolveInclude(project, includeTarget, filePath, overrides, state, lineNumber);
|
|
230
|
+
if (includedText) {
|
|
231
|
+
outputLines.push(includedText);
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
default:
|
|
236
|
+
throw new Error(formatError(filePath, lineNumber, `Unknown directive: --#${directive}`));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (conditionalStack.length > 0) {
|
|
240
|
+
throw new Error(formatError(filePath, lines.length, `Unclosed --#if block`));
|
|
241
|
+
}
|
|
242
|
+
state.includeStack.pop();
|
|
243
|
+
return outputLines.join("\n");
|
|
244
|
+
}
|
|
245
|
+
async function resolveInclude(project, includeTarget, fromFile, overrides, state, lineNumber) {
|
|
246
|
+
if (includeTarget.startsWith("import:")) {
|
|
247
|
+
return await resolveImportInclude(project, includeTarget, overrides, state, lineNumber);
|
|
248
|
+
}
|
|
249
|
+
const substituted = project.substituteVariables(includeTarget);
|
|
250
|
+
let resolvedPath;
|
|
251
|
+
try {
|
|
252
|
+
resolvedPath = project.resolveIncludePath(substituted);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
256
|
+
throw new Error(formatError(fromFile, lineNumber, message));
|
|
257
|
+
}
|
|
258
|
+
const includeKey = makeIncludeKey(resolvedPath, overrides);
|
|
259
|
+
if (state.pragmaOnceKeys.has(includeKey)) {
|
|
260
|
+
return "";
|
|
261
|
+
}
|
|
262
|
+
const source = await (0, fileSystem_1.readTextFileAsync)(resolvedPath);
|
|
263
|
+
const included = await processSource(project, source, resolvedPath, includeKey, state, overrides);
|
|
264
|
+
return ensureTrailingNewline(included);
|
|
265
|
+
}
|
|
266
|
+
async function resolveImportInclude(project, includeTarget, overrides, state, lineNumber) {
|
|
267
|
+
const ref = (0, importUtils_1.parseImportReference)(includeTarget);
|
|
268
|
+
const importName = ref.importName;
|
|
269
|
+
const chunkSpec = ref.chunkSpec;
|
|
270
|
+
const importDef = project.manifest.imports.find((imp) => imp.name === importName);
|
|
271
|
+
if (!importDef) {
|
|
272
|
+
throw new Error(formatError("<include>", lineNumber, `Import not found: ${importName}`));
|
|
273
|
+
}
|
|
274
|
+
if (importDef.kind === manifestTypes_1.kImportKind.key.LuaCode) {
|
|
275
|
+
const resolvedPath = project.resolveImportPath(importDef);
|
|
276
|
+
const includeKey = makeIncludeKey(resolvedPath, overrides);
|
|
277
|
+
if (state.pragmaOnceKeys.has(includeKey)) {
|
|
278
|
+
return "";
|
|
279
|
+
}
|
|
280
|
+
const source = await (0, fileSystem_1.readTextFileAsync)(resolvedPath);
|
|
281
|
+
const included = await processSource(project, source, resolvedPath, includeKey, state, overrides);
|
|
282
|
+
return ensureTrailingNewline(included);
|
|
283
|
+
}
|
|
284
|
+
if (importDef.kind === manifestTypes_1.kImportKind.key.Tic80Cartridge) {
|
|
285
|
+
const resolvedPath = project.resolveImportPath(importDef);
|
|
286
|
+
const data = await (0, fileSystem_1.readBinaryFileAsync)(resolvedPath);
|
|
287
|
+
const cart = (0, cartLoader_1.parseTic80Cart)(data);
|
|
288
|
+
state.dependencies.add(resolvedPath);
|
|
289
|
+
const availableChunks = importDef.chunks && importDef.chunks.length > 0 ? importDef.chunks : cart.chunks.map((chunk) => chunk.chunkType);
|
|
290
|
+
const hasCode = availableChunks.includes("CODE");
|
|
291
|
+
if (!hasCode) {
|
|
292
|
+
throw new Error(formatError(resolvedPath, lineNumber, `No CODE chunk found in cart: ${importName}`));
|
|
293
|
+
}
|
|
294
|
+
let selectedChunk = "CODE";
|
|
295
|
+
if (chunkSpec) {
|
|
296
|
+
if (chunkSpec !== "CODE") {
|
|
297
|
+
throw new Error(formatError(resolvedPath, lineNumber, `Only CODE chunk is supported for include`));
|
|
298
|
+
}
|
|
299
|
+
selectedChunk = chunkSpec;
|
|
300
|
+
if (!availableChunks.includes(selectedChunk)) {
|
|
301
|
+
throw new Error(formatError(resolvedPath, lineNumber, `Requested chunk ${selectedChunk} not available in import`));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
else if (availableChunks.length > 1) {
|
|
305
|
+
throw new Error(formatError(resolvedPath, lineNumber, `Import ${importName} contains multiple chunks. Specify :CODE explicitly in include.`));
|
|
306
|
+
}
|
|
307
|
+
const codeChunk = cart.chunks.find((chunk) => chunk.chunkType === selectedChunk);
|
|
308
|
+
if (!codeChunk) {
|
|
309
|
+
throw new Error(formatError(resolvedPath, lineNumber, `Requested chunk ${selectedChunk} not found in cart`));
|
|
310
|
+
}
|
|
311
|
+
const decoder = new TextDecoder("utf-8");
|
|
312
|
+
const source = decoder.decode(codeChunk.data);
|
|
313
|
+
const includeKey = makeIncludeKey(`${includeTarget}:${selectedChunk}`, overrides);
|
|
314
|
+
const included = await processSource(project, source, `${includeTarget}:${selectedChunk}`, includeKey, state, overrides, false);
|
|
315
|
+
return ensureTrailingNewline(included);
|
|
316
|
+
}
|
|
317
|
+
throw new Error(formatError("<include>", lineNumber, `Unsupported import kind: ${importDef.kind}`));
|
|
318
|
+
}
|
|
319
|
+
function parseWithOverrides(remainder, defines, filePath, lineNumber) {
|
|
320
|
+
const match = remainder.match(/\bwith\s*(\{[\s\S]*\})\s*(?:--.*)?$/);
|
|
321
|
+
if (!match) {
|
|
322
|
+
return {};
|
|
323
|
+
}
|
|
324
|
+
const tableText = match[1];
|
|
325
|
+
const expr = parseExpression(tableText, filePath, lineNumber);
|
|
326
|
+
if (expr.type !== "TableConstructorExpression") {
|
|
327
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid with-clause expression`));
|
|
328
|
+
}
|
|
329
|
+
return evaluateTable(expr, defines, filePath, lineNumber);
|
|
330
|
+
}
|
|
331
|
+
function parseExpression(exprText, filePath, lineNumber) {
|
|
332
|
+
try {
|
|
333
|
+
const chunk = luaparse.parse(`return ${exprText}`);
|
|
334
|
+
if (chunk.body.length === 0 || chunk.body[0].type !== "ReturnStatement") {
|
|
335
|
+
throw new Error("Invalid expression");
|
|
336
|
+
}
|
|
337
|
+
const returnStmt = chunk.body[0];
|
|
338
|
+
if (returnStmt.arguments.length !== 1) {
|
|
339
|
+
throw new Error("Expected single expression");
|
|
340
|
+
}
|
|
341
|
+
return returnStmt.arguments[0];
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
throw new Error(formatError(filePath, lineNumber, `Failed to parse expression: ${exprText}`));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// alternatively i could actually RUN the Lua but it's not trivial and would be slow.
|
|
348
|
+
// turns out not to be so bad to just evaluate off the AST.
|
|
349
|
+
function evaluateExpression(expr, defines, filePath, lineNumber) {
|
|
350
|
+
switch (expr.type) {
|
|
351
|
+
case "NumericLiteral":
|
|
352
|
+
return expr.value;
|
|
353
|
+
case "StringLiteral": {
|
|
354
|
+
const value = (0, lua_utils_1.stringValue)(expr);
|
|
355
|
+
if (value === null) {
|
|
356
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid string literal`));
|
|
357
|
+
}
|
|
358
|
+
return value;
|
|
359
|
+
}
|
|
360
|
+
case "BooleanLiteral":
|
|
361
|
+
return expr.value;
|
|
362
|
+
case "Identifier": {
|
|
363
|
+
if (!defines.has(expr.name)) {
|
|
364
|
+
throw new Error(formatError(filePath, lineNumber, `Undefined preprocessor symbol: ${expr.name}`));
|
|
365
|
+
}
|
|
366
|
+
return defines.get(expr.name);
|
|
367
|
+
}
|
|
368
|
+
case "UnaryExpression": {
|
|
369
|
+
const arg = evaluateExpression(expr.argument, defines, filePath, lineNumber);
|
|
370
|
+
switch (expr.operator) {
|
|
371
|
+
case "not":
|
|
372
|
+
return !isTruthy(arg);
|
|
373
|
+
case "-":
|
|
374
|
+
return -asNumber(arg, filePath, lineNumber);
|
|
375
|
+
default:
|
|
376
|
+
throw new Error(formatError(filePath, lineNumber, `Unsupported unary operator: ${expr.operator}`));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
case "BinaryExpression": {
|
|
380
|
+
const left = evaluateExpression(expr.left, defines, filePath, lineNumber);
|
|
381
|
+
const right = evaluateExpression(expr.right, defines, filePath, lineNumber);
|
|
382
|
+
switch (expr.operator) {
|
|
383
|
+
case "+":
|
|
384
|
+
return asNumber(left, filePath, lineNumber) + asNumber(right, filePath, lineNumber);
|
|
385
|
+
case "-":
|
|
386
|
+
return asNumber(left, filePath, lineNumber) - asNumber(right, filePath, lineNumber);
|
|
387
|
+
case "*":
|
|
388
|
+
return asNumber(left, filePath, lineNumber) * asNumber(right, filePath, lineNumber);
|
|
389
|
+
case "/":
|
|
390
|
+
return asNumber(left, filePath, lineNumber) / asNumber(right, filePath, lineNumber);
|
|
391
|
+
case "%":
|
|
392
|
+
return asNumber(left, filePath, lineNumber) % asNumber(right, filePath, lineNumber);
|
|
393
|
+
case "..":
|
|
394
|
+
return String(left) + String(right);
|
|
395
|
+
case "==":
|
|
396
|
+
return left === right;
|
|
397
|
+
case "~=":
|
|
398
|
+
return left !== right;
|
|
399
|
+
case "<":
|
|
400
|
+
return compareValues(left, right, filePath, lineNumber, (a, b) => a < b);
|
|
401
|
+
case "<=":
|
|
402
|
+
return compareValues(left, right, filePath, lineNumber, (a, b) => a <= b);
|
|
403
|
+
case ">":
|
|
404
|
+
return compareValues(left, right, filePath, lineNumber, (a, b) => a > b);
|
|
405
|
+
case ">=":
|
|
406
|
+
return compareValues(left, right, filePath, lineNumber, (a, b) => a >= b);
|
|
407
|
+
default:
|
|
408
|
+
throw new Error(formatError(filePath, lineNumber, `Unsupported binary operator: ${expr.operator}`));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
case "LogicalExpression": {
|
|
412
|
+
const left = evaluateExpression(expr.left, defines, filePath, lineNumber);
|
|
413
|
+
if (expr.operator === "and") {
|
|
414
|
+
return isTruthy(left) ? evaluateExpression(expr.right, defines, filePath, lineNumber) : left;
|
|
415
|
+
}
|
|
416
|
+
if (expr.operator === "or") {
|
|
417
|
+
return isTruthy(left) ? left : evaluateExpression(expr.right, defines, filePath, lineNumber);
|
|
418
|
+
}
|
|
419
|
+
throw new Error(formatError(filePath, lineNumber, `Unsupported logical operator: ${expr.operator}`));
|
|
420
|
+
}
|
|
421
|
+
case "CallExpression": {
|
|
422
|
+
if (expr.base.type !== "Identifier" || expr.base.name !== "defined") {
|
|
423
|
+
throw new Error(formatError(filePath, lineNumber, `Unsupported function call in preprocessor expression`));
|
|
424
|
+
}
|
|
425
|
+
if (expr.arguments.length !== 1) {
|
|
426
|
+
throw new Error(formatError(filePath, lineNumber, `defined() expects exactly one argument`));
|
|
427
|
+
}
|
|
428
|
+
const arg = expr.arguments[0];
|
|
429
|
+
if (arg.type === "Identifier") {
|
|
430
|
+
return defines.has(arg.name);
|
|
431
|
+
}
|
|
432
|
+
if (arg.type === "StringLiteral") {
|
|
433
|
+
const value = (0, lua_utils_1.stringValue)(arg);
|
|
434
|
+
if (value === null) {
|
|
435
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid string literal in defined()`));
|
|
436
|
+
}
|
|
437
|
+
return defines.has(value);
|
|
438
|
+
}
|
|
439
|
+
throw new Error(formatError(filePath, lineNumber, `defined() argument must be an identifier or string literal`));
|
|
440
|
+
}
|
|
441
|
+
default:
|
|
442
|
+
throw new Error(formatError(filePath, lineNumber, `Unsupported expression type: ${expr.type}`));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// evaluates a table constructor expression into a key-value map
|
|
446
|
+
// used for with-clause parsing in include directives
|
|
447
|
+
function evaluateTable(expr, defines, filePath, lineNumber) {
|
|
448
|
+
const result = {};
|
|
449
|
+
for (const field of expr.fields) {
|
|
450
|
+
switch (field.type) {
|
|
451
|
+
case "TableKeyString": {
|
|
452
|
+
const key = field.key.name;
|
|
453
|
+
const value = evaluateExpression(field.value, defines, filePath, lineNumber);
|
|
454
|
+
result[key] = value;
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case "TableKey": {
|
|
458
|
+
if (field.key.type !== "StringLiteral") {
|
|
459
|
+
throw new Error(formatError(filePath, lineNumber, `Only string keys are supported in with-clause`));
|
|
460
|
+
}
|
|
461
|
+
const key = (0, lua_utils_1.stringValue)(field.key);
|
|
462
|
+
if (key === null) {
|
|
463
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid string key in with-clause`));
|
|
464
|
+
}
|
|
465
|
+
const value = evaluateExpression(field.value, defines, filePath, lineNumber);
|
|
466
|
+
result[key] = value;
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
default:
|
|
470
|
+
throw new Error(formatError(filePath, lineNumber, `Unsupported with-clause field type: ${field.type}`));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
function compareValues(left, right, filePath, lineNumber, comparator) {
|
|
476
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
477
|
+
return comparator(left, right);
|
|
478
|
+
}
|
|
479
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
480
|
+
return comparator(left, right);
|
|
481
|
+
}
|
|
482
|
+
throw new Error(formatError(filePath, lineNumber, `Comparison requires both values to be numbers or strings`));
|
|
483
|
+
}
|
|
484
|
+
// ensures the value is a number; throws otherwise
|
|
485
|
+
// used in expression evaluation
|
|
486
|
+
function asNumber(value, filePath, lineNumber) {
|
|
487
|
+
if (typeof value !== "number") {
|
|
488
|
+
throw new Error(formatError(filePath, lineNumber, `Expected number but got ${typeof value}`));
|
|
489
|
+
}
|
|
490
|
+
return value;
|
|
491
|
+
}
|
|
492
|
+
function isTruthy(value) {
|
|
493
|
+
return value !== false;
|
|
494
|
+
}
|
|
495
|
+
function ensureTrailingNewline(text) {
|
|
496
|
+
if (!text.endsWith("\n")) {
|
|
497
|
+
return text + "\n";
|
|
498
|
+
}
|
|
499
|
+
return text;
|
|
500
|
+
}
|
|
501
|
+
// unique key for an include based on file path and overrides
|
|
502
|
+
// used for pragma once and cycle detection
|
|
503
|
+
function makeIncludeKey(filePath, overrides) {
|
|
504
|
+
const keys = Object.keys(overrides).sort();
|
|
505
|
+
if (keys.length === 0) {
|
|
506
|
+
return filePath;
|
|
507
|
+
}
|
|
508
|
+
const serialized = keys.map((key) => `${key}=${String(overrides[key])}`).join(";");
|
|
509
|
+
return `${filePath}::${serialized}`;
|
|
510
|
+
}
|
|
511
|
+
function formatError(filePath, lineNumber, message) {
|
|
512
|
+
return `[LuaPreprocessor] ${filePath}:${lineNumber} ${message}`;
|
|
513
|
+
}
|
|
514
|
+
function parseMacroHeader(rest, filePath, lineNumber) {
|
|
515
|
+
const headerMatch = rest.trim().match(/^([A-Za-z_][A-Za-z0-9_]*)(\s*\(([^)]*)\))?\s*(?:=>\s*(.*))?$/);
|
|
516
|
+
if (!headerMatch) {
|
|
517
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid --#macro syntax: ${rest}`));
|
|
518
|
+
}
|
|
519
|
+
const name = headerMatch[1];
|
|
520
|
+
const paramList = headerMatch[3];
|
|
521
|
+
const inlineBody = headerMatch[4];
|
|
522
|
+
const params = paramList
|
|
523
|
+
? paramList
|
|
524
|
+
.split(",")
|
|
525
|
+
.map((p) => p.trim())
|
|
526
|
+
.filter((p) => p.length > 0)
|
|
527
|
+
: [];
|
|
528
|
+
return {
|
|
529
|
+
name,
|
|
530
|
+
params,
|
|
531
|
+
inlineBody: inlineBody !== undefined ? inlineBody.trim() : undefined,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function readMacroBody(lines, startIndex, filePath, lineNumber) {
|
|
535
|
+
const bodyLines = [];
|
|
536
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
537
|
+
const line = lines[i];
|
|
538
|
+
const match = line.match(/^\s*--#\s*(\w+)\s*(.*)$/);
|
|
539
|
+
if (match) {
|
|
540
|
+
if (match[1] === "endmacro") {
|
|
541
|
+
return { body: bodyLines.join("\n"), endIndex: i };
|
|
542
|
+
}
|
|
543
|
+
if (match[1] === "macro") {
|
|
544
|
+
throw new Error(formatError(filePath, lineNumber, `Nested --#macro is not supported`));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
bodyLines.push(line);
|
|
548
|
+
}
|
|
549
|
+
throw new Error(formatError(filePath, lineNumber, `Unclosed --#macro block`));
|
|
550
|
+
}
|
|
551
|
+
function expandMacros(project, code, macros, filePath) {
|
|
552
|
+
if (macros.size === 0) {
|
|
553
|
+
return code;
|
|
554
|
+
}
|
|
555
|
+
let current = code;
|
|
556
|
+
const maxPasses = 25;
|
|
557
|
+
for (let pass = 0; pass < maxPasses; pass++) {
|
|
558
|
+
const result = applyMacroPass(project, current, macros, filePath);
|
|
559
|
+
if (!result.changed) {
|
|
560
|
+
return current;
|
|
561
|
+
}
|
|
562
|
+
current = result.code;
|
|
563
|
+
}
|
|
564
|
+
throw new Error(formatError(filePath, 1, `Macro expansion exceeded ${maxPasses} passes (possible recursion)`));
|
|
565
|
+
}
|
|
566
|
+
function applyMacroPass(project, code, macros, filePath) {
|
|
567
|
+
const chunk = luaparse.parse(code, { ranges: true, locations: true });
|
|
568
|
+
const replacements = [];
|
|
569
|
+
walkLuaAst(chunk, (node, parent) => {
|
|
570
|
+
if (node.type !== "CallExpression") {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const callNode = node;
|
|
574
|
+
if (callNode.base.type !== "Identifier") {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const macroDef = macros.get(callNode.base.name);
|
|
578
|
+
if (!macroDef) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const range = getRange(callNode, filePath);
|
|
582
|
+
const lineNumber = getLineNumber(callNode, 1);
|
|
583
|
+
const args = callNode.arguments || [];
|
|
584
|
+
if (args.length !== macroDef.params.length) {
|
|
585
|
+
throw new Error(formatError(filePath, lineNumber, `Macro ${macroDef.name} expects ${macroDef.params.length} args but got ${args.length}`));
|
|
586
|
+
}
|
|
587
|
+
const argTexts = args.map((arg) => sliceRange(code, getRange(arg, filePath)));
|
|
588
|
+
const expanded = expandMacroBody(project, macroDef, argTexts, filePath, lineNumber);
|
|
589
|
+
replacements.push({ start: range[0], end: range[1], text: expanded });
|
|
590
|
+
});
|
|
591
|
+
if (replacements.length === 0) {
|
|
592
|
+
return { code, changed: false };
|
|
593
|
+
}
|
|
594
|
+
const sorted = replacements.sort((a, b) => b.start - a.start);
|
|
595
|
+
let out = code;
|
|
596
|
+
for (const rep of sorted) {
|
|
597
|
+
out = out.slice(0, rep.start) + rep.text + out.slice(rep.end);
|
|
598
|
+
}
|
|
599
|
+
return { code: out, changed: true };
|
|
600
|
+
}
|
|
601
|
+
function expandMacroBody(project, macro, argTexts, filePath, lineNumber) {
|
|
602
|
+
if (macro.params.length === 0) {
|
|
603
|
+
return wrapMacroBody(macro.body);
|
|
604
|
+
}
|
|
605
|
+
const parsed = parseExpressionWithRanges(macro.body, macro.sourceFile, macro.lineNumber);
|
|
606
|
+
const offset = "return ".length;
|
|
607
|
+
const replacements = [];
|
|
608
|
+
walkLuaAst(parsed, (node, parent) => {
|
|
609
|
+
if (node.type !== "Identifier") {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (isIdentifierKeyPosition(node, parent)) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const index = macro.params.indexOf(node.name);
|
|
616
|
+
if (index < 0) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const range = getRange(node, macro.sourceFile);
|
|
620
|
+
replacements.push({
|
|
621
|
+
start: range[0] - offset,
|
|
622
|
+
end: range[1] - offset,
|
|
623
|
+
text: `(${argTexts[index]})`,
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
if (replacements.length === 0) {
|
|
627
|
+
return wrapMacroBody(macro.body);
|
|
628
|
+
}
|
|
629
|
+
const sorted = replacements.sort((a, b) => b.start - a.start);
|
|
630
|
+
let out = macro.body;
|
|
631
|
+
for (const rep of sorted) {
|
|
632
|
+
out = out.slice(0, rep.start) + rep.text + out.slice(rep.end);
|
|
633
|
+
}
|
|
634
|
+
return wrapMacroBody(out);
|
|
635
|
+
}
|
|
636
|
+
function wrapMacroBody(body) {
|
|
637
|
+
const trimmed = body.trim();
|
|
638
|
+
if (trimmed.length === 0) {
|
|
639
|
+
return "";
|
|
640
|
+
}
|
|
641
|
+
return `(${trimmed})`;
|
|
642
|
+
}
|
|
643
|
+
async function expandPreprocessorCalls(project, code, filePath, state) {
|
|
644
|
+
const chunk = luaparse.parse(code, { ranges: true, locations: true });
|
|
645
|
+
const replacements = [];
|
|
646
|
+
const tasks = [];
|
|
647
|
+
const addReplacement = (node, text) => {
|
|
648
|
+
const range = getRange(node, filePath);
|
|
649
|
+
replacements.push({ start: range[0], end: range[1], text });
|
|
650
|
+
};
|
|
651
|
+
const getStringLiteralArg = (callNode, index, fnName) => {
|
|
652
|
+
const lineNumber = getLineNumber(callNode, 1);
|
|
653
|
+
const arg = callNode.arguments[index];
|
|
654
|
+
if (!arg || arg.type !== "StringLiteral") {
|
|
655
|
+
throw new Error(formatError(filePath, lineNumber, `${fnName} argument ${index + 1} must be a string literal`));
|
|
656
|
+
}
|
|
657
|
+
const rawValue = (0, lua_utils_1.stringValue)(arg);
|
|
658
|
+
if (rawValue === null) {
|
|
659
|
+
throw new Error(formatError(filePath, lineNumber, `Invalid string literal in ${fnName}`));
|
|
660
|
+
}
|
|
661
|
+
return { value: rawValue, lineNumber };
|
|
662
|
+
};
|
|
663
|
+
const resolveImportDefinition = (importName, lineNumber) => {
|
|
664
|
+
const importDef = project.manifest.imports.find((imp) => imp.name === importName);
|
|
665
|
+
if (!importDef) {
|
|
666
|
+
throw new Error(formatError(filePath, lineNumber, `Import not found: ${importName}`));
|
|
667
|
+
}
|
|
668
|
+
return importDef;
|
|
669
|
+
};
|
|
670
|
+
walkLuaAst(chunk, (node) => {
|
|
671
|
+
if (node.type !== "CallExpression") {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const callNode = node;
|
|
675
|
+
if (callNode.base.type !== "Identifier") {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const fnName = callNode.base.name;
|
|
679
|
+
if (fnName === "__EXPAND") {
|
|
680
|
+
tasks.push((async () => {
|
|
681
|
+
const lineNumber = getLineNumber(callNode, 1);
|
|
682
|
+
if (callNode.arguments.length !== 1) {
|
|
683
|
+
throw new Error(formatError(filePath, lineNumber, `__EXPAND expects exactly one argument`));
|
|
684
|
+
}
|
|
685
|
+
const arg = getStringLiteralArg(callNode, 0, "__EXPAND");
|
|
686
|
+
const substituted = project.substituteVariables(arg.value);
|
|
687
|
+
const literal = (0, lua_fundamentals_1.toLuaStringLiteral)(substituted);
|
|
688
|
+
addReplacement(callNode, literal);
|
|
689
|
+
})());
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (fnName === "__IMPORTTEXT") {
|
|
693
|
+
tasks.push((async () => {
|
|
694
|
+
const lineNumber = getLineNumber(callNode, 1);
|
|
695
|
+
if (callNode.arguments.length !== 1) {
|
|
696
|
+
throw new Error(formatError(filePath, lineNumber, `__IMPORTTEXT expects exactly one argument`));
|
|
697
|
+
}
|
|
698
|
+
const arg = getStringLiteralArg(callNode, 0, "__IMPORTTEXT");
|
|
699
|
+
const substituted = project.substituteVariables(arg.value);
|
|
700
|
+
if (!substituted.startsWith("import:")) {
|
|
701
|
+
addReplacement(callNode, (0, lua_fundamentals_1.toLuaStringLiteral)(substituted));
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const ref = (0, importUtils_1.parseImportReference)(substituted);
|
|
705
|
+
if (ref.chunkSpec) {
|
|
706
|
+
throw new Error(formatError(filePath, lineNumber, `__IMPORTTEXT does not support chunk specifiers`));
|
|
707
|
+
}
|
|
708
|
+
const importDef = resolveImportDefinition(ref.importName, lineNumber);
|
|
709
|
+
if (importDef.kind !== manifestTypes_1.kImportKind.key.text) {
|
|
710
|
+
throw new Error(formatError(filePath, lineNumber, `__IMPORTTEXT requires a text import`));
|
|
711
|
+
}
|
|
712
|
+
const result = await (0, importUtils_1.loadTextImportData)(project, importDef);
|
|
713
|
+
for (const dep of result.dependencies) {
|
|
714
|
+
state.dependencies.add(dep);
|
|
715
|
+
}
|
|
716
|
+
addReplacement(callNode, (0, lua_fundamentals_1.toLuaStringLiteral)(result.data));
|
|
717
|
+
})());
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (fnName === "__IMPORTBIN") {
|
|
721
|
+
tasks.push((async () => {
|
|
722
|
+
const lineNumber = getLineNumber(callNode, 1);
|
|
723
|
+
if (callNode.arguments.length !== 2) {
|
|
724
|
+
throw new Error(formatError(filePath, lineNumber, `__IMPORTBIN expects exactly two arguments`));
|
|
725
|
+
}
|
|
726
|
+
const encodingArg = getStringLiteralArg(callNode, 0, "__IMPORTBIN");
|
|
727
|
+
const importArg = getStringLiteralArg(callNode, 1, "__IMPORTBIN");
|
|
728
|
+
const encoding = (0, luaBinaryEncoding_1.normalizeBinaryOutputEncoding)(project.substituteVariables(encodingArg.value));
|
|
729
|
+
const importSpec = project.substituteVariables(importArg.value);
|
|
730
|
+
if (!importSpec.startsWith("import:")) {
|
|
731
|
+
throw new Error(formatError(filePath, lineNumber, `__IMPORTBIN requires an import reference`));
|
|
732
|
+
}
|
|
733
|
+
const ref = (0, importUtils_1.parseImportReference)(importSpec);
|
|
734
|
+
if (ref.chunkSpec) {
|
|
735
|
+
throw new Error(formatError(filePath, lineNumber, `__IMPORTBIN does not support chunk specifiers`));
|
|
736
|
+
}
|
|
737
|
+
const importDef = resolveImportDefinition(ref.importName, lineNumber);
|
|
738
|
+
if (importDef.kind !== manifestTypes_1.kImportKind.key.binary) {
|
|
739
|
+
throw new Error(formatError(filePath, lineNumber, `__IMPORTBIN requires a binary import`));
|
|
740
|
+
}
|
|
741
|
+
const result = await (0, importUtils_1.loadBinaryImportData)(project, importDef);
|
|
742
|
+
for (const dep of result.dependencies) {
|
|
743
|
+
state.dependencies.add(dep);
|
|
744
|
+
}
|
|
745
|
+
const literal = (0, luaBinaryEncoding_1.encodeBinaryAsLuaLiteral)(result.data, encoding);
|
|
746
|
+
addReplacement(callNode, literal);
|
|
747
|
+
})());
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (fnName === "__ENCODE") {
|
|
751
|
+
tasks.push((async () => {
|
|
752
|
+
const lineNumber = getLineNumber(callNode, 1);
|
|
753
|
+
if (callNode.arguments.length !== 3) {
|
|
754
|
+
throw new Error(formatError(filePath, lineNumber, `__ENCODE expects exactly three arguments`));
|
|
755
|
+
}
|
|
756
|
+
const sourceEncodingArg = getStringLiteralArg(callNode, 0, "__ENCODE");
|
|
757
|
+
const outputEncodingArg = getStringLiteralArg(callNode, 1, "__ENCODE");
|
|
758
|
+
const valueArg = getStringLiteralArg(callNode, 2, "__ENCODE");
|
|
759
|
+
const sourceEncodingRaw = project.substituteVariables(sourceEncodingArg.value).trim().toLowerCase();
|
|
760
|
+
const sourceEncoding = (0, codecRegistry_1.resolveSourceEncoding)(sourceEncodingRaw).key;
|
|
761
|
+
if (!(0, codecRegistry_1.isStringSourceEncoding)(sourceEncoding)) {
|
|
762
|
+
throw new Error(formatError(filePath, lineNumber, `__ENCODE only supports string-based source encodings`));
|
|
763
|
+
}
|
|
764
|
+
const outputEncoding = (0, luaBinaryEncoding_1.normalizeBinaryOutputEncoding)(project.substituteVariables(outputEncodingArg.value));
|
|
765
|
+
const sourceValue = project.substituteVariables(valueArg.value);
|
|
766
|
+
if (sourceValue.startsWith("import:")) {
|
|
767
|
+
console.warn(formatError(filePath, lineNumber, `__ENCODE used with import reference; use __IMPORTBIN`));
|
|
768
|
+
}
|
|
769
|
+
const data = (0, codecRegistry_1.decodeSourceDataFromString)(sourceEncoding, sourceValue);
|
|
770
|
+
const literal = (0, luaBinaryEncoding_1.encodeBinaryAsLuaLiteral)(data, outputEncoding);
|
|
771
|
+
addReplacement(callNode, literal);
|
|
772
|
+
})());
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
if (tasks.length === 0) {
|
|
776
|
+
return code;
|
|
777
|
+
}
|
|
778
|
+
await Promise.all(tasks);
|
|
779
|
+
if (replacements.length === 0) {
|
|
780
|
+
return code;
|
|
781
|
+
}
|
|
782
|
+
const sorted = replacements.sort((a, b) => b.start - a.start);
|
|
783
|
+
let out = code;
|
|
784
|
+
for (const rep of sorted) {
|
|
785
|
+
out = out.slice(0, rep.start) + rep.text + out.slice(rep.end);
|
|
786
|
+
}
|
|
787
|
+
return out;
|
|
788
|
+
}
|
|
789
|
+
function parseExpressionWithRanges(body, filePath, lineNumber) {
|
|
790
|
+
try {
|
|
791
|
+
const chunk = luaparse.parse(`return ${body}`, { ranges: true, locations: true });
|
|
792
|
+
if (chunk.body.length === 0 || chunk.body[0].type !== "ReturnStatement") {
|
|
793
|
+
throw new Error("Invalid expression");
|
|
794
|
+
}
|
|
795
|
+
const returnStmt = chunk.body[0];
|
|
796
|
+
if (returnStmt.arguments.length !== 1) {
|
|
797
|
+
throw new Error("Expected single expression");
|
|
798
|
+
}
|
|
799
|
+
return returnStmt.arguments[0];
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
803
|
+
throw new Error(formatError(filePath, lineNumber, `Failed to parse macro body: ${message}`));
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function getRange(node, filePath) {
|
|
807
|
+
const withRange = node;
|
|
808
|
+
if (!withRange.range) {
|
|
809
|
+
throw new Error(formatError(filePath, 1, `Missing range for Lua node`));
|
|
810
|
+
}
|
|
811
|
+
return withRange.range;
|
|
812
|
+
}
|
|
813
|
+
function getLineNumber(node, fallback) {
|
|
814
|
+
const withLoc = node;
|
|
815
|
+
return withLoc.loc?.start.line ?? fallback;
|
|
816
|
+
}
|
|
817
|
+
function sliceRange(source, range) {
|
|
818
|
+
return source.slice(range[0], range[1]);
|
|
819
|
+
}
|
|
820
|
+
function walkLuaAst(node, visit, parent = null) {
|
|
821
|
+
if (!isLuaNode(node)) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
visit(node, parent);
|
|
825
|
+
const record = node;
|
|
826
|
+
for (const value of Object.values(record)) {
|
|
827
|
+
if (Array.isArray(value)) {
|
|
828
|
+
for (const item of value) {
|
|
829
|
+
walkLuaAst(item, visit, node);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
else if (isLuaNode(value)) {
|
|
833
|
+
walkLuaAst(value, visit, node);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function isLuaNode(value) {
|
|
838
|
+
if (typeof value !== "object" || value === null) {
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
const record = value;
|
|
842
|
+
return typeof record.type === "string";
|
|
843
|
+
}
|
|
844
|
+
function isIdentifierKeyPosition(node, parent) {
|
|
845
|
+
if (!parent) {
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
if (parent.type === "TableKeyString") {
|
|
849
|
+
const tableKey = parent;
|
|
850
|
+
return tableKey.key === node;
|
|
851
|
+
}
|
|
852
|
+
if (parent.type === "TableKey") {
|
|
853
|
+
const tableKey = parent;
|
|
854
|
+
return tableKey.key === node;
|
|
855
|
+
}
|
|
856
|
+
if (parent.type === "MemberExpression") {
|
|
857
|
+
const member = parent;
|
|
858
|
+
return member.identifier === node;
|
|
859
|
+
}
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
//# sourceMappingURL=luaPreprocessor.js.map
|