1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2026-03-15 08:16:25 +00:00
Files
8bitworkshop/gen/tools/testlib.js

437 lines
14 KiB
JavaScript

"use strict";
// testlib - Clean async API for compiling and testing 8bitworkshop projects
// Wraps the worker build system for use in tests and CLI tools
// FOR TESTING ONLY
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TOOLS = exports.PLATFORM_PARAMS = exports.TOOL_PRELOADFS = exports.store = void 0;
exports.initialize = initialize;
exports.preload = preload;
exports.compile = compile;
exports.compileFile = compileFile;
exports.getToolForFilename = getToolForFilename;
exports.compileSourceFile = compileSourceFile;
exports.listTools = listTools;
exports.listPlatforms = listPlatforms;
exports.ab2str = ab2str;
exports.createMockLocalStorage = createMockLocalStorage;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const util_1 = require("../common/util");
const baseplatform_1 = require("../common/baseplatform");
const workerlib_1 = require("../worker/workerlib");
Object.defineProperty(exports, "store", { enumerable: true, get: function () { return workerlib_1.store; } });
Object.defineProperty(exports, "TOOL_PRELOADFS", { enumerable: true, get: function () { return workerlib_1.TOOL_PRELOADFS; } });
const platforms_1 = require("../worker/platforms");
Object.defineProperty(exports, "PLATFORM_PARAMS", { enumerable: true, get: function () { return platforms_1.PLATFORM_PARAMS; } });
const workertools_1 = require("../worker/workertools");
Object.defineProperty(exports, "TOOLS", { enumerable: true, get: function () { return workertools_1.TOOLS; } });
let initialized = false;
/**
* Initialize the Node.js environment for compilation.
* Must be called once before any compile/preload calls.
*/
async function initialize() {
if (initialized)
return;
(0, workerlib_1.setupNodeEnvironment)();
// The worker tools are already registered through the import chain:
// workerlib -> workertools -> tools/* and builder -> TOOLS
// No need to load the esbuild bundle.
initialized = true;
}
/**
* Preload a tool's filesystem (e.g. CC65 standard libraries).
*/
async function preload(tool, platform) {
await initialize();
var msg = { preload: tool };
if (platform)
msg.platform = platform;
await (0, workerlib_1.handleMessage)(msg);
}
/**
* Compile source code with the specified tool and platform.
*/
async function compile(options) {
await initialize();
// Reset the store for a clean build
await (0, workerlib_1.handleMessage)({ reset: true });
let result;
if (options.files && options.buildsteps) {
// Multi-file build
var msg = {
updates: options.files.map(f => ({ path: f.path, data: f.data })),
buildsteps: options.buildsteps
};
result = await (0, workerlib_1.handleMessage)(msg);
}
else {
// Single-file build
var msg = {
code: options.code,
platform: options.platform,
tool: options.tool,
path: options.path || ('src.' + options.tool),
mainfile: options.mainfile !== false
};
result = await (0, workerlib_1.handleMessage)(msg);
}
return workerResultToCompileResult(result);
}
/**
* Compile a file from the presets directory.
*/
async function compileFile(tool, platform, presetPath) {
await initialize();
var code = fs.readFileSync('presets/' + platform + '/' + presetPath, 'utf-8');
return compile({
tool: tool,
platform: platform,
code: code,
path: presetPath,
});
}
/**
* Parse include and link dependencies from source text.
* Extracted from CodeProject.parseIncludeDependencies / parseLinkDependencies.
* TODO: project.ts should be refactored so we don't have to duplicate the logic
*/
function parseIncludeDependencies(text, platformId, mainPath) {
let files = [];
let m;
var dir = (0, util_1.getFolderForPath)(mainPath);
function pushFile(fn) {
files.push(fn);
if (dir.length > 0 && dir != 'local')
files.push(dir + '/' + fn);
}
if (platformId.startsWith('verilog')) {
let re1 = /^\s*(`include|[.]include)\s+"(.+?)"/gmi;
while (m = re1.exec(text)) {
pushFile(m[2]);
}
let re1a = /^\s*\$(include|\$dofile|\$write_image_in_table)\('(.+?)'/gmi;
while (m = re1a.exec(text)) {
pushFile(m[2]);
}
let re2 = /^\s*([.]arch)\s+(\w+)/gmi;
while (m = re2.exec(text)) {
pushFile(m[2] + ".json");
}
let re3 = /\$readmem[bh]\("(.+?)"/gmi;
while (m = re3.exec(text)) {
pushFile(m[1]);
}
}
else {
let re2 = /^\s*[.#%]?(include|incbin|embed)\s+"(.+?)"/gmi;
while (m = re2.exec(text)) {
pushFile(m[2]);
}
let re3 = /^\s*([;']|[/][/])#(resource)\s+"(.+?)"/gm;
while (m = re3.exec(text)) {
pushFile(m[3]);
}
let re4 = /^\s+(USE|ASM)\s+(\S+[.]\S+)/gm;
while (m = re4.exec(text)) {
pushFile(m[2]);
}
let re5 = /^\s*(import|embed)\s*"(.+?)";/gmi;
while (m = re5.exec(text)) {
if (m[1] == 'import')
pushFile(m[2] + ".wiz");
else
pushFile(m[2]);
}
let re6 = /^\s*(import)\s*"(.+?)"/gmi;
while (m = re6.exec(text)) {
pushFile(m[2]);
}
let re7 = /^[!]src\s+"(.+?)"/gmi;
while (m = re7.exec(text)) {
pushFile(m[1]);
}
}
return files;
}
function parseLinkDependencies(text, platformId, mainPath) {
let files = [];
let m;
var dir = (0, util_1.getFolderForPath)(mainPath);
function pushFile(fn) {
files.push(fn);
if (dir.length > 0 && dir != 'local')
files.push(dir + '/' + fn);
}
if (!platformId.startsWith('verilog')) {
let re = /^\s*([;]|[/][/])#link\s+"(.+?)"/gm;
while (m = re.exec(text)) {
pushFile(m[2]);
}
}
return files;
}
/**
* Try to resolve a file path by searching the source directory,
* the presets directory for the platform, and the current working directory.
*/
function resolveFileData(filePath, sourceDir, platform) {
var searchPaths = [];
// Try relative to source file directory
if (sourceDir) {
searchPaths.push(path.resolve(sourceDir, filePath));
}
// Try presets directory
var basePlatform = (0, util_1.getBasePlatform)(platform);
searchPaths.push(path.resolve('presets', basePlatform, filePath));
// Try current working directory
searchPaths.push(path.resolve(filePath));
for (var p of searchPaths) {
try {
if (fs.existsSync(p)) {
if ((0, util_1.isProbablyBinary)(filePath)) {
return new Uint8Array(fs.readFileSync(p));
}
else {
return fs.readFileSync(p, 'utf-8');
}
}
}
catch (e) {
// continue searching
}
}
return null;
}
/**
* Strips the main file's folder prefix from a path (matching CodeProject.stripLocalPath).
*/
function stripLocalPath(filePath, mainPath) {
var folder = (0, util_1.getFolderForPath)(mainPath);
if (folder != '' && filePath.startsWith(folder + '/')) {
filePath = filePath.substring(folder.length + 1);
}
return filePath;
}
/**
* Recursively resolve all file dependencies for a source file.
*/
function resolveAllDependencies(mainText, mainPath, platform, sourceDir) {
var resolved = [];
var seen = new Set();
function resolve(text, currentPath) {
var includes = parseIncludeDependencies(text, platform, currentPath);
var links = parseLinkDependencies(text, platform, currentPath);
var allPaths = includes.concat(links);
var linkSet = new Set(links);
for (var depPath of allPaths) {
var filename = stripLocalPath(depPath, mainPath);
if (seen.has(filename))
continue;
seen.add(filename);
var data = resolveFileData(depPath, sourceDir, platform);
if (data != null) {
resolved.push({
path: depPath,
filename: filename,
data: data,
link: linkSet.has(depPath),
});
// Recursively parse text files for their own dependencies
if (typeof data === 'string') {
resolve(data, depPath);
}
}
}
}
resolve(mainText, mainPath);
return resolved;
}
// TODO: refactor dependency parsing and tool selection into a common library
// shared between CodeProject (src/ide/project.ts) and testlib
/**
* Select the appropriate tool for a filename based on platform architecture.
*/
function getToolForFilename(fn, platform) {
var params = platforms_1.PLATFORM_PARAMS[(0, util_1.getBasePlatform)(platform)];
var arch = params && params.arch;
switch (arch) {
case 'z80':
case 'gbz80':
return (0, baseplatform_1.getToolForFilename_z80)(fn);
case '6502':
return (0, baseplatform_1.getToolForFilename_6502)(fn);
case '6809':
return (0, baseplatform_1.getToolForFilename_6809)(fn);
default:
return (0, baseplatform_1.getToolForFilename_z80)(fn); // fallback
}
}
/**
* Compile an arbitrary source file path.
* Parses include/link/resource directives and loads dependent files.
*/
async function compileSourceFile(tool, platform, filePath) {
await initialize();
var code = fs.readFileSync(filePath, 'utf-8');
var basename = filePath.split('/').pop();
var sourceDir = path.dirname(path.resolve(filePath));
// Auto-detect tool from filename if not specified
if (!tool) {
tool = getToolForFilename(basename, platform);
}
// Parse and resolve all dependencies
var deps = resolveAllDependencies(code, basename, platform, sourceDir);
if (deps.length === 0) {
// No dependencies found, use simple single-file path
return compile({
tool: tool,
platform: platform,
code: code,
path: basename,
});
}
// Build multi-file message with updates and buildsteps
var files = [];
var depFilenames = [];
// Main file first
files.push({ path: basename, data: code });
// Include files (non-link dependencies)
for (var dep of deps) {
if (!dep.link) {
files.push({ path: dep.filename, data: dep.data });
depFilenames.push(dep.filename);
}
}
// Build steps: main file first
var buildsteps = [];
buildsteps.push({
path: basename,
files: [basename].concat(depFilenames),
platform: platform,
tool: tool,
mainfile: true,
});
// Link dependencies get their own build steps, with tool selected by extension
for (var dep of deps) {
if (dep.link && dep.data) {
files.push({ path: dep.filename, data: dep.data });
buildsteps.push({
path: dep.filename,
files: [dep.filename].concat(depFilenames),
platform: platform,
tool: getToolForFilename(dep.filename, platform),
});
}
}
return compile({
tool: tool,
platform: platform,
files: files,
buildsteps: buildsteps,
});
}
function workerResultToCompileResult(result) {
if (!result) {
return { success: true, unchanged: true };
}
if ('unchanged' in result && result.unchanged) {
return { success: true, unchanged: true };
}
if ('errors' in result && result.errors && result.errors.length > 0) {
return {
success: false,
errors: result.errors,
};
}
if ('output' in result) {
return {
success: true,
output: result.output,
listings: result.listings,
symbolmap: result.symbolmap,
segments: result.segments,
params: result.params,
};
}
return { success: false, errors: [{ line: 0, msg: 'Unknown result format' }] };
}
/**
* List available compilation tools.
*/
function listTools() {
return Object.keys(workertools_1.TOOLS);
}
/**
* List available target platforms.
*/
function listPlatforms() {
return Object.keys(platforms_1.PLATFORM_PARAMS);
}
/**
* Convert a binary buffer to a hex string for display.
*/
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
}
// Platform test harness utilities
/**
* Create a mock localStorage for tests that need it.
*/
function createMockLocalStorage() {
var items = {};
return {
get length() {
return Object.keys(items).length;
},
clear() {
items = {};
},
getItem(k) {
return items[k] || null;
},
setItem(k, v) {
items[k] = v;
},
removeItem(k) {
delete items[k];
},
key(i) {
return Object.keys(items)[i] || null;
}
};
}
//# sourceMappingURL=testlib.js.map