8bitworkshop/gen/ide/project.js

450 lines
16 KiB
JavaScript

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createNewPersistentStore = exports.CodeProject = exports.LocalForageFilesystem = exports.OverlayFilesystem = exports.NullFilesystem = exports.WebPresetsFileSystem = void 0;
const workertypes_1 = require("../common/workertypes");
const util_1 = require("../common/util");
const localforage_1 = __importDefault(require("localforage"));
class WebPresetsFileSystem {
constructor(platform_id) {
this.preset_id = (0, util_1.getBasePlatform)(platform_id); // remove .suffix from preset name
}
async getRemoteFile(path) {
return new Promise((yes, no) => {
return (0, util_1.getWithBinary)(path, yes, (0, util_1.isProbablyBinary)(path) ? 'arraybuffer' : 'text');
});
}
async getFileData(path) {
// found on remote fetch?
var webpath = "presets/" + this.preset_id + "/" + path;
var data = await this.getRemoteFile(webpath);
if (data)
console.log("read", webpath, data.length, 'bytes');
return data;
}
async setFileData(path, data) {
// not implemented
}
}
exports.WebPresetsFileSystem = WebPresetsFileSystem;
class NullFilesystem {
constructor() {
this.gets = [];
this.sets = [];
}
getFileData(path) {
this.gets.push(path);
return null;
}
setFileData(path, data) {
this.sets.push(path);
return;
}
}
exports.NullFilesystem = NullFilesystem;
class OverlayFilesystem {
constructor(basefs, overlayfs) {
this.basefs = basefs;
this.overlayfs = overlayfs;
}
async getFileData(path) {
var data = await this.overlayfs.getFileData(path);
if (data == null) {
return this.basefs.getFileData(path);
}
else {
return data;
}
}
async setFileData(path, data) {
await this.overlayfs.setFileData(path, data);
return this.basefs.setFileData(path, data);
}
}
exports.OverlayFilesystem = OverlayFilesystem;
class LocalForageFilesystem {
constructor(store) {
this.store = store;
}
async getFileData(path) {
return this.store.getItem(path);
}
async setFileData(path, data) {
return this.store.setItem(path, data);
}
}
exports.LocalForageFilesystem = LocalForageFilesystem;
function isEmptyString(text) {
return typeof text == 'string' && text.trim && text.trim().length == 0;
}
class CodeProject {
constructor(worker, platform_id, platform, filesystem) {
this.filedata = {};
this.pendingWorkerMessages = 0;
this.tools_preloaded = {};
this.isCompiling = false;
this.filename2path = {}; // map stripped paths to full paths
this.worker = worker;
this.platform_id = platform_id;
this.platform = platform;
this.filesystem = filesystem;
worker.onmessage = (e) => {
this.receiveWorkerMessage(e.data);
};
}
receiveWorkerMessage(data) {
var notfinal = this.pendingWorkerMessages > 1;
if (notfinal) {
this.sendBuild();
this.pendingWorkerMessages = 1;
}
else {
if (this.callbackBuildStatus)
this.callbackBuildStatus(false);
if (!this.isCompiling) {
console.log(this.pendingWorkerMessages);
console.trace();
} // debug compile problems
this.isCompiling = false;
this.pendingWorkerMessages = 0;
}
if (data && (0, workertypes_1.isOutputResult)(data)) {
this.processBuildResult(data);
}
else if ((0, workertypes_1.isErrorResult)(data)) {
this.processBuildListings(data);
}
this.callbackBuildResult(data);
}
getToolForFilename(path) {
if (this.remoteTool) {
return "remote:" + this.remoteTool;
}
else {
return this.platform.getToolForFilename(path);
}
}
preloadWorker(path) {
var tool = this.getToolForFilename(path);
if (tool && !this.tools_preloaded[tool]) {
this.worker.postMessage({ preload: tool, platform: this.platform_id });
this.tools_preloaded[tool] = true;
}
}
pushAllFiles(files, fn) {
// look for local and preset files
files.push(fn);
// look for files in current (main file) folder
var dir = (0, util_1.getFolderForPath)(this.mainPath);
if (dir.length > 0 && dir != 'local') // TODO
files.push(dir + '/' + fn);
}
// TODO: use tool id to parse files, not platform
parseIncludeDependencies(text) {
let files = [];
let m;
if (this.platform_id.startsWith('verilog')) {
// include verilog includes
let re1 = /^\s*(`include|[.]include)\s+"(.+?)"/gmi;
while (m = re1.exec(text)) {
this.pushAllFiles(files, m[2]);
}
// for Silice
let re1a = /^\s*\$(include|\$dofile|\$write_image_in_table)\('(.+?)'/gmi;
while (m = re1a.exec(text)) {
this.pushAllFiles(files, m[2]);
}
// include .arch (json) statements
let re2 = /^\s*([.]arch)\s+(\w+)/gmi;
while (m = re2.exec(text)) {
this.pushAllFiles(files, m[2] + ".json");
}
// include $readmem[bh] (TODO)
let re3 = /\$readmem[bh]\("(.+?)"/gmi;
while (m = re3.exec(text)) {
this.pushAllFiles(files, m[1]);
}
}
else {
// for .asm -- [.%]include "file"
// for .c -- #include "file"
let re2 = /^\s*[.#%]?(include|incbin|embed)\s+"(.+?)"/gmi;
while (m = re2.exec(text)) {
this.pushAllFiles(files, m[2]);
}
// for .c -- //#resource "file" (or ;resource or #resource)
let re3 = /^\s*([;']|[/][/])#(resource)\s+"(.+?)"/gm;
while (m = re3.exec(text)) {
this.pushAllFiles(files, m[3]);
}
// for XASM only (USE include.ext)
// for merlin32 (ASM include.ext)
let re4 = /^\s+(USE|ASM)\s+(\S+[.]\S+)/gm;
while (m = re4.exec(text)) {
this.pushAllFiles(files, m[2]);
}
// for wiz
let re5 = /^\s*(import|embed)\s*"(.+?)";/gmi;
while (m = re5.exec(text)) {
if (m[1] == 'import')
this.pushAllFiles(files, m[2] + ".wiz");
else
this.pushAllFiles(files, m[2]);
}
// for ecs
let re6 = /^\s*(import)\s*"(.+?)"/gmi;
while (m = re6.exec(text)) {
this.pushAllFiles(files, m[2]);
}
// for acme
let re7 = /^[!]src\s+"(.+?)"/gmi;
while (m = re7.exec(text)) {
this.pushAllFiles(files, m[1]);
}
}
return files;
}
parseLinkDependencies(text) {
let files = [];
let m;
if (this.platform_id.startsWith('verilog')) {
//
}
else {
// for .c -- //#link "file" (or ;link or #link)
let re = /^\s*([;]|[/][/])#link\s+"(.+?)"/gm;
while (m = re.exec(text)) {
this.pushAllFiles(files, m[2]);
}
}
return files;
}
loadFileDependencies(text) {
let includes = this.parseIncludeDependencies(text);
let linkfiles = this.parseLinkDependencies(text);
let allfiles = includes.concat(linkfiles);
return this.loadFiles(allfiles).then((result) => {
// set 'link' property on files that are link dependencies (must match filename)
if (result) {
for (let dep of result) {
dep.link = linkfiles.indexOf(dep.path) >= 0;
}
}
return result;
});
}
okToSend() {
return this.pendingWorkerMessages++ == 0 && this.mainPath != null;
}
updateFileInStore(path, text) {
this.filesystem.setFileData(path, text);
}
// TODO: test duplicate files, local paths mixed with presets
buildWorkerMessage(depends) {
this.preloadWorker(this.mainPath);
var msg = { updates: [], buildsteps: [] };
// TODO: add preproc directive for __MAINFILE__
var mainfilename = this.stripLocalPath(this.mainPath);
var maintext = this.getFile(this.mainPath);
var depfiles = [];
msg.updates.push({ path: mainfilename, data: maintext });
this.filename2path[mainfilename] = this.mainPath;
const tool = this.getToolForFilename(this.mainPath);
let usesRemoteTool = tool.startsWith('remote:');
for (var dep of depends) {
// remote tools send both includes and linked files in one build step
if (!dep.link || usesRemoteTool) {
msg.updates.push({ path: dep.filename, data: dep.data });
depfiles.push(dep.filename);
}
this.filename2path[dep.filename] = dep.path;
}
msg.buildsteps.push({
path: mainfilename,
files: [mainfilename].concat(depfiles),
platform: this.platform_id,
tool: this.getToolForFilename(this.mainPath),
mainfile: true
});
for (var dep of depends) {
if (dep.data && dep.link) {
this.preloadWorker(dep.filename);
msg.updates.push({ path: dep.filename, data: dep.data });
msg.buildsteps.push({
path: dep.filename,
files: [dep.filename].concat(depfiles),
platform: this.platform_id,
tool: this.getToolForFilename(dep.path)
});
}
}
if (this.dataItems)
msg.setitems = this.dataItems;
return msg;
}
// TODO: get local file as well as presets?
async loadFiles(paths) {
var result = [];
var addResult = (path, data) => {
result.push({
path: path,
filename: this.stripLocalPath(path),
link: true,
data: data
});
};
for (var path of paths) {
// look in cache
if (path in this.filedata) { // found in cache?
var data = this.filedata[path];
if (data) {
addResult(path, data);
}
}
else {
var data = await this.filesystem.getFileData(path);
if (data) {
this.filedata[path] = data; // do not update store, just cache
addResult(path, data);
}
else {
this.filedata[path] = null; // mark entry as invalid
}
}
}
return result;
}
getFile(path) {
return this.filedata[path];
}
// TODO: purge files not included in latest build?
iterateFiles(callback) {
for (var path in this.filedata) {
callback(path, this.getFile(path));
}
}
sendBuild() {
if (!this.mainPath)
throw Error("need to call setMainFile first");
var maindata = this.getFile(this.mainPath);
// if binary blob, just return it as ROM
if (maindata instanceof Uint8Array) {
this.isCompiling = true;
this.receiveWorkerMessage({
output: maindata,
errors: [],
listings: null,
symbolmap: null,
params: {}
});
return;
}
// otherwise, make it a string
var text = typeof maindata === "string" ? maindata : '';
// TODO: load dependencies of non-main files
return this.loadFileDependencies(text).then((depends) => {
if (!depends)
depends = [];
var workermsg = this.buildWorkerMessage(depends);
this.worker.postMessage(workermsg);
this.isCompiling = true;
});
}
updateFile(path, text) {
if (this.filedata[path] == text)
return; // unchanged, don't update
this.updateFileInStore(path, text); // TODO: isBinary
this.filedata[path] = text;
if (this.okToSend()) {
if (this.callbackBuildStatus)
this.callbackBuildStatus(true);
this.sendBuild();
}
}
;
setMainFile(path) {
this.mainPath = path;
if (this.callbackBuildStatus)
this.callbackBuildStatus(true);
this.sendBuild();
}
processBuildListings(data) {
// TODO: link listings with source files
if (data.listings) {
this.listings = data.listings;
for (var lstname in this.listings) {
var lst = this.listings[lstname];
if (lst.lines)
lst.sourcefile = new workertypes_1.SourceFile(lst.lines, lst.text);
if (lst.asmlines)
lst.assemblyfile = new workertypes_1.SourceFile(lst.asmlines, lst.text);
}
}
}
processBuildResult(data) {
this.processBuildListings(data);
this.processBuildSegments(data);
}
processBuildSegments(data) {
// save and sort segment list
var segs = (this.platform.getMemoryMap && this.platform.getMemoryMap()["main"]) || [];
if (segs === null || segs === void 0 ? void 0 : segs.length) {
segs.forEach(seg => seg.source = 'native');
}
if (data.segments) {
data.segments.forEach(seg => seg.source = 'linker');
segs = segs.concat(data.segments || []);
}
segs.sort((a, b) => { return a.start - b.start; });
this.segments = segs;
}
getListings() {
return this.listings;
}
// returns first listing in format [prefix].lst (TODO: could be better)
getListingForFile(path) {
// ignore include files (TODO)
//if (path.toLowerCase().endsWith('.h') || path.toLowerCase().endsWith('.inc'))
//return;
var fnprefix = (0, util_1.getFilenamePrefix)(this.stripLocalPath(path));
// find listing with matching prefix
var listings = this.getListings();
for (var lstfn in listings) {
if (lstfn == path)
return listings[lstfn];
}
for (var lstfn in listings) {
if ((0, util_1.getFilenamePrefix)(lstfn) == fnprefix) {
return listings[lstfn];
}
}
}
stripLocalPath(path) {
if (this.mainPath) {
var folder = (0, util_1.getFolderForPath)(this.mainPath);
// TODO: kinda weird if folder is same name as file prefix
if (folder != '' && path.startsWith(folder + '/')) {
path = path.substring(folder.length + 1);
}
}
return path;
}
updateDataItems(items) {
this.dataItems = items;
if (this.okToSend()) { // TODO? mainpath == null?
this.sendBuild(); // TODO: don't need entire build?
}
}
}
exports.CodeProject = CodeProject;
function createNewPersistentStore(storeid) {
var store = localforage_1.default.createInstance({
name: "__" + storeid,
version: 2.0
});
return store;
}
exports.createNewPersistentStore = createNewPersistentStore;
//# sourceMappingURL=project.js.map