converting some stuff to TypeScript (make tsweb)

This commit is contained in:
Steven Hugg 2018-07-05 19:13:07 -05:00
parent 4bb460a79b
commit 62f5303107
15 changed files with 1282 additions and 879 deletions

View File

@ -8,3 +8,7 @@ before_install:
language: node_js
node_js:
- "6.5.0"
script:
- npm run build
- npm test

View File

@ -27,4 +27,6 @@ web:
tsweb:
ifconfig | grep inet
node ../nodejs-typescript-webserver/bin/FileServer.js .
tsc -w &
python2 -m SimpleHTTPServer 2>> http.out
#node ../nodejs-typescript-webserver/bin/FileServer.js .

View File

@ -43,6 +43,7 @@ TODO:
(https://developers.google.com/web/updates/2018/04/loading-wasm)
- unify versioning
- more UI tests
- base-36 encoding for LZG
WEB WORKER FORMAT

View File

@ -207,6 +207,13 @@ ga('send', 'pageview');
<script src="codemirror/addon/selection/active-line.js"></script>
<link rel="stylesheet" href="codemirror/addon/dialog/dialog.css">
<script>
var exports = {};
function require(modname) {
if (modname == 'jquery') return $;
}
</script>
<script src="javatari.js/release/javatari/javatari.js"></script>
<script src="src/cpu/z80fast.js"></script>
<!--<script src="src/cpu/6809.js"></script>-->
@ -232,9 +239,11 @@ ga('send', 'pageview');
<script src="src/vlist.js"></script>
<script src="src/emu.js"></script>
<script src="src/audio.js"></script>
<script src="src/util.js"></script>
<script src="gen/util.js"></script>
<script src="src/cpu/disasm6502.js"></script>
<script src="src/project.js"></script>
<script src="gen/project.js"></script>
<script src="gen/windows.js"></script>
<script src="gen/views.js"></script>
<script src="src/ui.js"></script>
<!-- <script src="src/audio/votrax.js"></script> -->

292
package-lock.json generated Normal file
View File

@ -0,0 +1,292 @@
{
"name": "8bitworkshop",
"version": "2.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/jquery": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.4.tgz",
"integrity": "sha512-KqgLNDh8oTl43/2B78S7tjwPnOxPtP9wQsLptbuQCmMwqH5QuPZOk36RsNgbs3mnq/3SCyG1l/GJmhKk5Dg68g==",
"dev": true
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"browser-stdout": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
"integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
"dev": true
},
"commander": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
"integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
"dev": true,
"requires": {
"graceful-readlink": "1.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"debug": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
"integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"diff": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz",
"integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
"integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"graceful-readlink": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
"dev": true
},
"growl": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
"integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
"dev": true
},
"has-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
"integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
"dev": true
},
"he": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"json3": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
"integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
"dev": true
},
"lodash._baseassign": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
"integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
"dev": true,
"requires": {
"lodash._basecopy": "3.0.1",
"lodash.keys": "3.1.2"
}
},
"lodash._basecopy": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
"integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
"dev": true
},
"lodash._basecreate": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz",
"integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=",
"dev": true
},
"lodash._getnative": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
"integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
"dev": true
},
"lodash._isiterateecall": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
"integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
"dev": true
},
"lodash.create": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz",
"integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=",
"dev": true,
"requires": {
"lodash._baseassign": "3.2.0",
"lodash._basecreate": "3.0.3",
"lodash._isiterateecall": "3.0.9"
}
},
"lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
"dev": true
},
"lodash.isarray": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
"dev": true
},
"lodash.keys": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
"integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
"dev": true,
"requires": {
"lodash._getnative": "3.9.1",
"lodash.isarguments": "3.1.0",
"lodash.isarray": "3.0.4"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.11"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
},
"mocha": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz",
"integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==",
"dev": true,
"requires": {
"browser-stdout": "1.3.0",
"commander": "2.9.0",
"debug": "2.6.8",
"diff": "3.2.0",
"escape-string-regexp": "1.0.5",
"glob": "7.1.1",
"growl": "1.9.2",
"he": "1.1.1",
"json3": "3.3.2",
"lodash.create": "3.1.1",
"mkdirp": "0.5.1",
"supports-color": "3.1.2"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1.0.2"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"supports-color": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
"integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=",
"dev": true,
"requires": {
"has-flag": "1.0.0"
}
},
"wavedrom-cli": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/wavedrom-cli/-/wavedrom-cli-0.5.0.tgz",
"integrity": "sha1-h+lb+naRpbaTmPJzL13QycKkiCE=",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
}
}

View File

@ -4,6 +4,7 @@
"author": "Steven Hugg",
"dependencies": {},
"devDependencies": {
"@types/jquery": "^3.3.4",
"mocha": "^3.2.0",
"mocha-phantomjs": "^4.1.0",
"wavedrom-cli": "^0.5.0"
@ -15,6 +16,7 @@
"test": "tests"
},
"scripts": {
"build": "tsc",
"test": "npm run test-node && npm run test-browser",
"test-one": "mocha --recursive --timeout 20000",
"test-node": "mocha --recursive --timeout 20000 test/cli",

View File

@ -1,237 +0,0 @@
"use strict";
function SourceFile(lines, text) {
lines = lines || [];
this.lines = lines;
this.text = text;
this.offset2line = {};
this.line2offset = {};
for (var info of lines) {
if (info.offset >= 0) {
this.offset2line[info.offset] = info.line;
this.line2offset[info.line] = info.offset;
}
}
this.findLineForOffset = function(PC) {
if (this.offset2line) {
for (var i=0; i<16; i++) {
var line = this.offset2line[PC];
if (line >= 0) {
return line;
}
PC--;
}
}
return 0;
}
this.lineCount = function() { return lines.length; }
}
function CodeProject(worker, platform_id, platform, store, mainpath) {
var self = this;
var filedata = {};
var listings;
self.callbackBuildResult = function(result) { };
self.callbackBuildStatus = function(busy) { };
var pendingWorkerMessages = 0;
var tools_preloaded = {};
function preloadWorker(path) {
var tool = platform.getToolForFilename(path);
if (tool && !tools_preloaded[tool]) {
worker.postMessage({preload:tool, platform:platform_id});
tools_preloaded[tool] = true;
}
}
self.setMainPath = function(path) {
mainpath = path;
}
// TODO: get local file as well as presets?
self.loadFiles = function(paths, callback) {
var result = [];
function loadNext() {
var path = paths.shift();
if (!path) {
callback(null, result); // TODO?
} else {
store.getItem(path, function(err, value) {
if (err) {
callback(err);
} else if (value) {
result.push({
path:path,
filename:getFilenameForPath(path),
data:value
});
filedata[path] = value;
loadNext();
} else {
var webpath = "presets/" + platform_id + "/" + path;
if (platform_id == 'vcs' && path.indexOf('.') <= 0)
webpath += ".a"; // legacy stuff
// TODO: cache files
$.get( webpath, function( text ) {
console.log("GET",webpath,text.length,'bytes');
result.push({
path:path,
filename:getFilenameForPath(path),
data:text
});
filedata[path] = text;
loadNext();
}, 'text')
.fail(function() {
callback("Could not load preset " + path);
});
}
});
}
}
loadNext(); // load first file
}
function parseFileDependencies(text) {
var files = [];
if (platform_id == 'verilog') {
var re = /^(`include|[.]include)\s+"(.+?)"/gm;
var m;
while (m = re.exec(text)) {
files.push(m[2]);
}
} else {
var re = /^([;#]|[/][/][#])link\s+"(.+?)"/gm;
var m;
while (m = re.exec(text)) {
files.push(m[2]);
}
}
return files;
}
function loadFileDependencies(text, callback) {
var paths = parseFileDependencies(text);
self.loadFiles(paths, callback);
}
function okToSend() {
return pendingWorkerMessages++ == 0;
}
function updateFileInStore(path, text) {
// protect against accidential whole-file deletion
if (text.trim().length) {
// TODO? (originalFileID != path || text != originalText)) {
store.setItem(path, text);
}
}
// TODO: test duplicate files, local paths mixed with presets
function buildWorkerMessage(depends) {
preloadWorker(mainpath);
var msg = {updates:[], buildsteps:[]};
// TODO: add preproc directive for __MAINFILE__
var mainfilename = getFilenameForPath(mainpath);
var maintext = self.getFile(mainpath);
msg.updates.push({path:mainfilename, data:maintext});
msg.buildsteps.push({path:mainfilename, platform:platform_id, tool:platform.getToolForFilename(mainpath), mainfile:true});
for (var i=0; i<depends.length; i++) {
var dep = depends[i];
if (dep.data) {
preloadWorker(dep.filename);
msg.updates.push({path:dep.filename, data:dep.data});
msg.buildsteps.push({path:dep.filename, platform:platform_id, tool:platform.getToolForFilename(dep.path)});
}
}
return msg;
}
self.getFile = function(path) {
return filedata[path];
}
self.iterateFiles = function(callback) {
for (var path in filedata) {
callback(path, self.getFile(path));
}
}
self.sendBuild = function() {
var text = self.getFile(mainpath);
loadFileDependencies(text, function(err, depends) {
if (err) {
console.log(err); // TODO?
}
if (platform_id == 'verilog') {
// TODO: should get rid of this msg format
worker.postMessage({
code:text,
dependencies:depends,
platform:platform_id,
tool:platform.getToolForFilename(mainpath)
});
} else {
var workermsg = buildWorkerMessage(depends);
worker.postMessage(workermsg);
}
});
}
self.updateFile = function(path, text, isBinary) {
updateFileInStore(path, text); // TODO: isBinary
filedata[path] = text;
if (okToSend()) {
if (!mainpath) mainpath = path;
self.callbackBuildStatus(true);
self.sendBuild();
}
};
self.processBuildResult = function(data) {
// TODO: link listings with source files
listings = data.listings;
if (listings) {
for (var lstname in listings) {
var lst = listings[lstname];
if (lst.lines)
lst.sourcefile = new SourceFile(lst.lines);
if (lst.asmlines)
lst.assemblyfile = new SourceFile(lst.asmlines, lst.text);
}
}
}
self.getListings = function() {
return listings;
}
// returns first listing in format [prefix].lst (TODO: could be better)
self.getListingForFile = function(path) {
var fnprefix = getFilenamePrefix(getFilenameForPath(path));
var listings = self.getListings();
for (var lstfn in listings) {
if (getFilenamePrefix(lstfn) == fnprefix) {
return listings[lstfn];
}
}
}
worker.onmessage = function(e) {
if (pendingWorkerMessages > 1) {
self.sendBuild();
pendingWorkerMessages = 0;
} else {
pendingWorkerMessages = 0;
}
self.callbackBuildStatus(false);
if (e.data && !e.data.unchanged) {
self.processBuildResult(e.data);
self.callbackBuildResult(e.data); // call with data when changed
}
};
}

301
src/project.ts Normal file
View File

@ -0,0 +1,301 @@
"use strict";
type FileData = string | Uint8Array;
interface SourceLine {
offset:number;
line:number;
}
interface Dependency {
path:string,
filename:string,
data:FileData // TODO: or binary?
}
interface WorkerFileUpdate { path:string, data:FileData };
interface WorkerBuildStep { path:string, platform:string, tool:string, mainfile?:boolean };
interface WorkerMessage {
updates:WorkerFileUpdate[],
buildsteps:WorkerBuildStep[]
}
interface WorkerError {
line:number,
msg:string,
//TODO
}
export class SourceFile {
lines: SourceLine[];
text: string;
offset2line: {[offset:number]:number};
line2offset: {[line:number]:number};
constructor(lines:SourceLine[], text?:string) {
lines = lines || [];
this.lines = lines;
this.text = text;
this.offset2line = {};
this.line2offset = {};
for (var info of lines) {
if (info.offset >= 0) {
this.offset2line[info.offset] = info.line;
this.line2offset[info.line] = info.offset;
}
}
}
findLineForOffset(PC:number):number {
if (this.offset2line) {
for (var i=0; i<16; i++) {
var line = this.offset2line[PC];
if (line >= 0) {
return line;
}
PC--;
}
}
return null;
}
lineCount():number { return this.lines.length; }
}
interface CodeListing {
lines:SourceLine[],
asmlines:SourceLine[],
text:string,
sourcefile:SourceFile,
assemblyfile:SourceFile
}
type CodeListingMap = {[path:string]:CodeListing};
interface WorkerResult {
output:Uint8Array, //TODO
errors:WorkerError[],
listings:CodeListingMap,
}
type BuildResultCallback = (result:WorkerResult) => void;
type BuildStatusCallback = (busy:boolean) => void;
type LoadFilesCallback = (err:string, result?:Dependency[]) => void;
type IterateFilesCallback = (path:string, data:FileData) => void;
type GetRemoteCallback = any; // TODO (path:string, (text:string) => FileData) => void;
export class CodeProject {
filedata : {[path:string]:FileData} = {};
listings : CodeListingMap;
mainpath : string;
pendingWorkerMessages = 0;
tools_preloaded = {};
callbackBuildResult : BuildResultCallback;
callbackBuildStatus : BuildStatusCallback;
worker: any;
platform_id : string;
platform: any;
store: any;
callbackGetRemote : GetRemoteCallback;
mainPath: string;
constructor(worker, platform_id:string, platform, store) {
this.worker = worker;
this.platform_id = platform_id;
this.platform = platform;
this.store = store;
worker.onmessage = (e) => {
if (this.pendingWorkerMessages > 1) {
this.sendBuild();
this.pendingWorkerMessages = 0;
} else {
this.pendingWorkerMessages = 0;
}
if (this.callbackBuildStatus) this.callbackBuildStatus(false);
if (e.data && !e.data.unchanged) {
this.processBuildResult(e.data);
if (this.callbackBuildResult) this.callbackBuildResult(e.data); // call with data when changed
}
};
}
preloadWorker(path:string) {
var tool = this.platform.getToolForFilename(path);
if (tool && !this.tools_preloaded[tool]) {
this.worker.postMessage({preload:tool, platform:this.platform_id});
this.tools_preloaded[tool] = true;
}
}
parseFileDependencies(text:string):string[] {
var files = [];
if (this.platform_id == 'verilog') {
var re = /^(`include|[.]include)\s+"(.+?)"/gm;
var m;
while (m = re.exec(text)) {
files.push(m[2]);
}
} else {
var re = /^([;#]|[/][/][#])link\s+"(.+?)"/gm;
var m;
while (m = re.exec(text)) {
files.push(m[2]);
}
}
return files;
}
loadFileDependencies(text:string, callback:LoadFilesCallback) {
var paths = this.parseFileDependencies(text);
this.loadFiles(paths, callback);
}
okToSend():boolean {
return this.pendingWorkerMessages++ == 0;
}
updateFileInStore(path:string, text:FileData) {
// protect against accidential whole-file deletion
if ((<string>text).trim && (<string>text).trim().length) {
// TODO? (originalFileID != path || text != originalText)) {
this.store.setItem(path, text);
}
}
// TODO: test duplicate files, local paths mixed with presets
buildWorkerMessage(depends:Dependency[]) {
this.preloadWorker(this.mainpath);
var msg = {updates:[], buildsteps:[]};
// TODO: add preproc directive for __MAINFILE__
var mainfilename = getFilenameForPath(this.mainpath);
var maintext = this.getFile(this.mainpath);
msg.updates.push({path:mainfilename, data:maintext});
msg.buildsteps.push({path:mainfilename, platform:this.platform_id, tool:this.platform.getToolForFilename(this.mainpath), mainfile:true});
for (var i=0; i<depends.length; i++) {
var dep = depends[i];
if (dep.data) {
this.preloadWorker(dep.filename);
msg.updates.push({path:dep.filename, data:dep.data});
msg.buildsteps.push({path:dep.filename, platform:this.platform_id, tool:this.platform.getToolForFilename(dep.path)});
}
}
return msg;
}
// TODO: get local file as well as presets?
loadFiles(paths:string[], callback:LoadFilesCallback) {
var result = [];
var loadNext = () => {
var path = paths.shift();
if (!path) {
callback(null, result); // TODO?
} else {
this.store.getItem(path, (err, value) => {
if (err) {
callback(err);
} else if (value) {
result.push({
path:path,
filename:getFilenameForPath(path),
data:value
});
this.filedata[path] = value;
loadNext();
} else {
var webpath = "presets/" + this.platform_id + "/" + path;
if (this.platform_id == 'vcs' && path.indexOf('.') <= 0)
webpath += ".a"; // legacy stuff
// TODO: cache files
this.callbackGetRemote( webpath, (text:string) => {
console.log("GET",webpath,text.length,'bytes');
result.push({
path:path,
filename:getFilenameForPath(path),
data:text
});
this.filedata[path] = text;
loadNext();
}, 'text')
.fail(function() {
callback("Could not load preset " + path);
});
}
});
}
}
loadNext(); // load first file
}
getFile(path:string):FileData {
return this.filedata[path];
}
iterateFiles(callback:IterateFilesCallback) {
for (var path in this.filedata) {
callback(path, this.getFile(path));
}
}
sendBuild() {
var self = this;
var maindata = this.getFile(this.mainpath);
var text = typeof maindata === "string" ? maindata : '';
this.loadFileDependencies(text, (err, depends) => {
if (err) {
console.log(err); // TODO?
}
if (this.platform_id == 'verilog') {
// TODO: should get rid of this msg format
this.worker.postMessage({
code:text,
dependencies:depends,
platform:this.platform_id,
tool:this.platform.getToolForFilename(this.mainpath)
});
} else {
var workermsg = this.buildWorkerMessage(depends);
this.worker.postMessage(workermsg);
}
});
}
updateFile(path:string, text:FileData) {
this.updateFileInStore(path, text); // TODO: isBinary
this.filedata[path] = text;
if (this.okToSend()) {
if (!this.mainpath) this.mainpath = path;
if (this.callbackBuildStatus) this.callbackBuildStatus(true);
this.sendBuild();
}
};
processBuildResult(data:WorkerResult) {
// TODO: link listings with source files
this.listings = data.listings;
if (this.listings) {
for (var lstname in this.listings) {
var lst = this.listings[lstname];
if (lst.lines)
lst.sourcefile = new SourceFile(lst.lines);
if (lst.asmlines)
lst.assemblyfile = new SourceFile(lst.asmlines, lst.text);
}
}
}
getListings() : CodeListingMap {
return this.listings;
}
// returns first listing in format [prefix].lst (TODO: could be better)
getListingForFile(path) : CodeListing {
var fnprefix = getFilenamePrefix(getFilenameForPath(path));
var listings = this.getListings();
for (var lstfn in listings) {
if (getFilenamePrefix(lstfn) == fnprefix) {
return listings[lstfn];
}
}
}
}

623
src/ui.js
View File

@ -13,6 +13,9 @@ var toolbar = $("#controls_top");
var current_project; // current CodeProject object
var projectWindows; // window manager
// TODO: codemirror multiplex support?
var TOOL_TO_SOURCE_STYLE = {
'dasm': '6502',
@ -66,6 +69,8 @@ function setLastPreset(id) {
function initProject() {
current_project = new CodeProject(newWorker(), platform_id, platform, store);
projectWindows = new ProjectWindows($("#workspace")[0], current_project);
current_project.callbackGetRemote = $.get;
current_project.callbackBuildResult = function(result) {
setCompileOutput(result);
refreshWindowList();
@ -82,622 +87,6 @@ function initProject() {
};
}
// TODO: remove some calls of global functions
function SourceEditor(path, mode) {
var self = this;
var editor;
var dirtylisting = true;
var sourcefile;
var currentDebugLine;
self.createDiv = function(parent, text) {
var div = document.createElement('div');
div.setAttribute("class", "editor");
parent.appendChild(div);
newEditor(div);
if (text)
self.setText(text); // TODO: this calls setCode() and builds... it shouldn't
return div;
}
function newEditor(parent) {
var isAsm = mode=='6502' || mode =='z80' || mode=='verilog' || mode=='gas'; // TODO
editor = CodeMirror(parent, {
theme: 'mbo',
lineNumbers: true,
matchBrackets: true,
tabSize: 8,
indentAuto: true,
gutters: isAsm ? ["CodeMirror-linenumbers", "gutter-offset", "gutter-bytes", "gutter-clock", "gutter-info"]
: ["CodeMirror-linenumbers", "gutter-offset", "gutter-info"],
});
var timer;
editor.on('changes', function(ed, changeobj) {
clearTimeout(timer);
timer = setTimeout(function() {
current_project.updateFile(path, editor.getValue(), false);
}, 200);
});
editor.on('cursorActivity', function(ed) {
var start = editor.getCursor(true);
var end = editor.getCursor(false);
if (start.line == end.line && start.ch < end.ch) {
var name = editor.getSelection();
inspectVariable(editor, name);
} else {
inspectVariable(editor);
}
});
//scrollProfileView(editor);
editor.setOption("mode", mode);
}
self.setText = function(text) {
editor.setValue(text); // calls setCode()
editor.clearHistory();
}
self.getValue = function() {
return editor.getValue();
}
self.getPath = function() { return path; }
var lines2errmsg = [];
self.addErrorMarker = function(line, msg) {
var div = document.createElement("div");
div.setAttribute("class", "tooltipbox tooltiperror");
div.appendChild(document.createTextNode("\u24cd"));
var tooltip = document.createElement("span");
tooltip.setAttribute("class", "tooltiptext");
if (lines2errmsg[line])
msg = lines2errmsg[line] + "\n" + msg;
tooltip.appendChild(document.createTextNode(msg));
lines2errmsg[line] = msg;
div.appendChild(tooltip);
editor.setGutterMarker(line, "gutter-info", div);
}
self.markErrors = function(errors) {
// TODO: move cursor to error line if offscreen?
self.clearErrors();
var numLines = editor.lineCount();
for (var info of errors) {
// only mark errors with this filename, or without any filename
if (!info.path || path.endsWith(info.path)) {
var line = info.line-1;
if (line < 0 || line >= numLines) line = 0;
self.addErrorMarker(line, info.msg);
}
}
}
self.clearErrors = function() {
editor.clearGutter("gutter-info");
refreshDebugState();
dirtylisting = true;
}
self.getSourceFile = function() { return sourcefile; }
// TODO: update gutter only when refreshing this window
self.updateListing = function(_sourcefile) {
sourcefile = _sourcefile;
// update editor annotations
editor.clearGutter("gutter-info");
editor.clearGutter("gutter-bytes");
editor.clearGutter("gutter-offset");
editor.clearGutter("gutter-clock");
var lstlines = sourcefile.lines || [];
for (var info of lstlines) {
if (info.offset >= 0) {
var textel = document.createTextNode(hex(info.offset,4));
editor.setGutterMarker(info.line-1, "gutter-offset", textel);
}
if (info.insns) {
var insnstr = info.insns.length > 9 ? ("...") : info.insns;
var textel = document.createTextNode(insnstr);
editor.setGutterMarker(info.line-1, "gutter-bytes", textel);
if (info.iscode) {
var opcode = parseInt(info.insns.split()[0], 16);
if (platform.getOpcodeMetadata) {
var meta = platform.getOpcodeMetadata(opcode, info.offset);
var clockstr = meta.minCycles+"";
var textel = document.createTextNode(clockstr);
editor.setGutterMarker(info.line-1, "gutter-clock", textel);
}
}
}
}
}
self.setGutterBytes = function(line, s) {
var textel = document.createTextNode(s);
editor.setGutterMarker(line-1, "gutter-bytes", textel);
}
self.setCurrentLine = function(line) {
function addCurrentMarker(line) {
var div = document.createElement("div");
div.style.color = '#66ffff';
div.appendChild(document.createTextNode("\u25b6"));
editor.setGutterMarker(line, "gutter-info", div);
}
self.clearCurrentLine();
if (line>0) {
addCurrentMarker(line-1);
editor.setSelection({line:line,ch:0}, {line:line-1,ch:0}, {scroll:true});
currentDebugLine = line;
}
}
self.clearCurrentLine = function() {
if (currentDebugLine) {
editor.clearGutter("gutter-info");
editor.setSelection(editor.getCursor());
currentDebugLine = 0;
}
}
function refreshDebugState() {
self.clearCurrentLine();
var state = lastDebugState;
if (state && state.c) {
var PC = state.c.PC;
var line = sourcefile.findLineForOffset(PC);
if (line >= 0) {
self.setCurrentLine(line);
// TODO: switch to disasm?
}
}
}
function refreshListing() {
if (!dirtylisting) return;
dirtylisting = false;
var lst = current_project.getListingForFile(path);
if (lst && lst.sourcefile) {
self.updateListing(lst.sourcefile); // updates sourcefile variable
}
}
self.refresh = function() {
refreshListing();
refreshDebugState();
}
self.getLine = function(line) {
return editor.getLine(line-1);
}
self.getCurrentLine = function() {
return editor.getCursor().line+1;
}
self.getCursorPC = function() {
var line = self.getCurrentLine();
while (sourcefile && line >= 0) {
var pc = sourcefile.line2offset[line];
if (pc >= 0) return pc;
line--;
}
return -1;
}
// bitmap editor (TODO: refactor)
function handleWindowMessage(e) {
//console.log("window message", e.data);
if (e.data.bytes) {
editor.replaceSelection(e.data.bytestr);
}
if (e.data.close) {
$("#pixeditback").hide();
}
}
function openBitmapEditorWithParams(fmt, bytestr, palfmt, palstr) {
$("#pixeditback").show();
window.addEventListener("message", handleWindowMessage, false); // TODO: remove listener
pixeditframe.contentWindow.postMessage({fmt:fmt, bytestr:bytestr, palfmt:palfmt, palstr:palstr}, '*');
}
function lookBackwardsForJSONComment(line, req) {
var re = /[/;][*;]([{].+[}])[*;][/;]/;
while (--line >= 0) {
var s = editor.getLine(line);
var m = re.exec(s);
if (m) {
var jsontxt = m[1].replace(/([A-Za-z]+):/g, '"$1":'); // fix lenient JSON
var obj = JSON.parse(jsontxt);
if (obj[req]) {
var start = {obj:obj, line:line, ch:s.indexOf(m[0])+m[0].length};
var line0 = line;
var pos0 = start.ch;
line--;
while (++line < editor.lineCount()) {
var l = editor.getLine(line);
var endsection;
if (platform_id == 'verilog')
endsection = l.indexOf('end') >= pos0;
else
endsection = l.indexOf(';') >= pos0;
if (endsection) {
var end = {line:line, ch:editor.getLine(line).length};
return {obj:obj, start:start, end:end};
}
pos0 = 0;
}
line = line0;
}
}
}
}
self.openBitmapEditorAtCursor = function() {
if ($("#pixeditback").is(":visible")) {
$("#pixeditback").hide(250);
return;
}
var line = editor.getCursor().line + 1;
var data = lookBackwardsForJSONComment(self.getCurrentLine(), 'w');
if (data && data.obj && data.obj.w>0 && data.obj.h>0) {
var paldata = lookBackwardsForJSONComment(data.start.line-1, 'pal');
var palbytestr;
if (paldata) {
palbytestr = editor.getRange(paldata.start, paldata.end);
paldata = paldata.obj;
}
editor.setSelection(data.end, data.start);
openBitmapEditorWithParams(data.obj, editor.getSelection(), paldata, palbytestr);
} else {
alert("To edit graphics, move cursor to a constant array preceded by a comment in the format:\n\n/*{w:,h:,bpp:,count:...}*/\n\n(See code examples)");
}
}
}
///
function DisassemblerView() {
var self = this;
var disasmview;
self.getDisasmView = function() { return disasmview; }
self.createDiv = function(parent) {
var div = document.createElement('div');
div.setAttribute("class", "editor");
parent.appendChild(div);
newEditor(div);
return div;
}
function newEditor(parent) {
disasmview = CodeMirror(parent, {
mode: 'z80', // TODO: pick correct one
theme: 'cobalt',
tabSize: 8,
readOnly: true,
styleActiveLine: true
});
}
// TODO: too many globals
self.refresh = function() {
var state = lastDebugState || platform.saveState();
var pc = state.c ? state.c.PC : 0;
var curline = 0;
var selline = 0;
// TODO: not perfect disassembler
function disassemble(start, end) {
if (start < 0) start = 0;
if (end > 0xffff) end = 0xffff;
// TODO: use pc2visits
var a = start;
var s = "";
while (a < end) {
var disasm = platform.disassemble(a, platform.readAddress);
/* TODO: look thru all source files
var srclinenum = sourcefile && sourcefile.offset2line[a];
if (srclinenum) {
var srcline = getActiveEditor().getLine(srclinenum);
if (srcline && srcline.trim().length) {
s += "; " + srclinenum + ":\t" + srcline + "\n";
curline++;
}
}
*/
var bytes = "";
for (var i=0; i<disasm.nbytes; i++)
bytes += hex(platform.readAddress(a+i));
while (bytes.length < 14)
bytes += ' ';
var dline = hex(parseInt(a)) + "\t" + bytes + "\t" + disasm.line + "\n";
s += dline;
if (a == pc) selline = curline;
curline++;
a += disasm.nbytes || 1;
}
return s;
}
var text = disassemble(pc-96, pc) + disassemble(pc, pc+96);
disasmview.setValue(text);
disasmview.setCursor(selline, 0);
jumpToLine(disasmview, selline);
}
self.getCursorPC = function() {
var line = disasmview.getCursor().line;
if (line >= 0) {
var toks = disasmview.getLine(line).split(/\s+/);
if (toks && toks.length >= 1) {
var pc = parseInt(toks[0], 16);
if (pc >= 0) return pc;
}
}
return -1;
}
}
///
function ListingView(assemblyfile) {
var self = this;
this.__proto__ = new DisassemblerView();
self.refresh = function() {
var state = lastDebugState || platform.saveState();
var pc = state.c ? state.c.PC : 0;
var asmtext = assemblyfile.text;
var disasmview = self.getDisasmView();
if (platform_id == 'base_z80') { // TODO
asmtext = asmtext.replace(/[ ]+\d+\s+;.+\n/g, '');
asmtext = asmtext.replace(/[ ]+\d+\s+.area .+\n/g, '');
}
disasmview.setValue(asmtext);
var findPC = platform.getDebugCallback() ? pc : -1;
if (findPC >= 0) {
var lineno = assemblyfile.findLineForOffset(findPC);
if (lineno) {
// set cursor while debugging
if (platform.getDebugCallback())
disasmview.setCursor(lineno-1, 0);
jumpToLine(disasmview, lineno-1);
}
}
}
}
///
function MemoryView() {
var self = this;
var memorylist;
var dumplines;
var div;
// TODO?
function getVisibleEditorLineHeight() {
return $(".CodeMirror-line:visible").first().height();
}
self.createDiv = function(parent) {
div = document.createElement('div');
div.setAttribute("class", "memdump");
parent.appendChild(div);
showMemoryWindow(div);
return div;
}
function showMemoryWindow(parent) {
memorylist = new VirtualList({
w:$("#workspace").width(),
h:$("#workspace").height(),
itemHeight: getVisibleEditorLineHeight(),
totalRows: 0x1000,
generatorFn: function(row) {
var s = getMemoryLineAt(row);
var div = document.createElement("div");
if (dumplines) {
var dlr = dumplines[row];
if (dlr) div.classList.add('seg_' + getMemorySegment(dumplines[row].a));
}
div.appendChild(document.createTextNode(s));
return div;
}
});
$(parent).append(memorylist.container);
self.tick();
if (compparams && dumplines)
memorylist.scrollToItem(findMemoryWindowLine(compparams.data_start));
}
self.tick = function() {
if (memorylist) {
$(div).find('[data-index]').each(function(i,e) {
var div = $(e);
var row = div.attr('data-index');
var oldtext = div.text();
var newtext = getMemoryLineAt(row);
if (oldtext != newtext)
div.text(newtext);
});
}
}
function getMemoryLineAt(row) {
var offset = row * 16;
var n1 = 0;
var n2 = 16;
var sym;
if (getDumpLines()) {
var dl = dumplines[row];
if (dl) {
offset = dl.a & 0xfff0;
n1 = dl.a - offset;
n2 = n1 + dl.l;
sym = dl.s;
} else {
return '.';
}
}
var s = hex(offset,4) + ' ';
for (var i=0; i<n1; i++) s += ' ';
if (n1 > 8) s += ' ';
for (var i=n1; i<n2; i++) {
var read = platform.readAddress(offset+i);
if (i==8) s += ' ';
s += ' ' + (read>=0?hex(read,2):'??');
}
for (var i=n2; i<16; i++) s += ' ';
if (sym) s += ' ' + sym;
return s;
}
function getDumpLineAt(line) {
var d = dumplines[line];
if (d) {
return d.a + " " + d.s;
}
}
var IGNORE_SYMS = {s__INITIALIZER:true, /* s__GSINIT:true, */ _color_prom:true};
// TODO: addr2symbol for ca65; and make it work without symbols
function getDumpLines() {
if (!dumplines && addr2symbol) {
dumplines = [];
var ofs = 0;
var sym;
for (var nextofs in addr2symbol) {
nextofs |= 0;
var nextsym = addr2symbol[nextofs];
if (sym) {
if (IGNORE_SYMS[sym]) {
ofs = nextofs;
} else {
while (ofs < nextofs) {
var ofs2 = (ofs + 16) & 0xffff0;
if (ofs2 > nextofs) ofs2 = nextofs;
//if (ofs < 1000) console.log(ofs, ofs2, nextofs, sym);
dumplines.push({a:ofs, l:ofs2-ofs, s:sym});
ofs = ofs2;
}
}
}
sym = nextsym;
}
}
return dumplines;
}
function getMemorySegment(a) {
if (!compparams) return 'unknown';
if (a >= compparams.data_start && a < compparams.data_start+compparams.data_size) {
if (platform.getSP && a >= platform.getSP() - 15)
return 'stack';
else
return 'data';
}
else if (a >= compparams.code_start && a < compparams.code_start+compparams.code_size)
return 'code';
else
return 'unknown';
}
function findMemoryWindowLine(a) {
for (var i=0; i<dumplines.length; i++)
if (dumplines[i].a >= a)
return i;
}
}
/////
function ProjectWindows(containerdiv) {
var self = this;
var id2window = {};
var id2createfn = {};
var id2div = {};
var activewnd;
var activediv;
var lasterrors;
// TODO: delete windows ever?
this.setCreateFunc = function(id, createfn) {
id2createfn[id] = createfn;
}
this.createOrShow = function(id) {
var wnd = id2window[id];
if (!wnd) {
wnd = id2window[id] = id2createfn[id](id);
}
var div = id2div[id];
if (!div) {
div = id2div[id] = wnd.createDiv(containerdiv, current_project.getFile(id));
}
if (activewnd != wnd) {
if (activediv)
$(activediv).hide();
activediv = div;
activewnd = wnd;
$(div).show();
this.refresh();
this.refreshErrors();
}
return wnd;
}
this.put = function(id, window, category) {
id2window[id] = window;
if (!categories[category])
categories[category] = [];
// TODO: remove/replace window
if (!(id in categories[category]))
categories[category].push(id);
}
this.filesForCategory = function(id) {
return categories[id] || [];
}
this.refresh = function() {
if (activewnd && activewnd.refresh)
activewnd.refresh();
}
this.tick = function() {
if (activewnd && activewnd.tick)
activewnd.tick();
}
this.setErrors = function(errors) {
lasterrors = errors;
this.refreshErrors();
}
this.refreshErrors = function() {
if (activewnd && activewnd.markErrors) {
if (lasterrors && lasterrors.length)
activewnd.markErrors(lasterrors);
else
activewnd.clearErrors();
}
}
this.getActive = function() { return activewnd; }
this.getCurrentText = function() {
if (activewnd && activewnd.getValue)
return activewnd.getValue();
else
alert("Please switch to an editor window.");
}
};
var projectWindows = new ProjectWindows($("#workspace")[0]);
function refreshWindowList() {
var ul = $("#windowMenuList").empty();
var separate = false;
@ -781,7 +170,7 @@ function loadProject(preset_id) {
// set current file ID
main_file_id = preset_id;
setLastPreset(preset_id);
current_project.setMainPath(preset_id);
current_project.mainPath = preset_id;
// load files from storage or web URLs
current_project.loadFiles([preset_id], function(err, result) {
if (err) {

View File

@ -1,23 +1,23 @@
"use strict";
function lpad(s,n) { while(s.length<n) s=" "+s; return s; }
function lpad(s:string, n:number):string { while(s.length<n) s=" "+s; return s; }
function byte2signed(b) {
function byte2signed(b:number):number {
b &= 0xff;
return (b < 0x80) ? b : -(256-b);
}
function getFilenameForPath(s) {
function getFilenameForPath(s:string):string {
var toks = s.split('/');
return toks[toks.length-1];
}
function getFilenamePrefix(s) {
function getFilenamePrefix(s:string):string {
var pos = s.lastIndexOf('.');
return (pos > 0) ? s.substr(0, pos) : s;
}
function hex(v, nd) {
function hex(v:number, nd?:number) {
try {
if (!nd) nd = 2;
var s = v.toString(16).toUpperCase();
@ -29,7 +29,7 @@ function hex(v, nd) {
}
}
function arrayCompare(a,b) {
function arrayCompare(a:any[], b:any[]):boolean {
if (a == null && b == null) return true;
if (a == null) return false;
if (b == null) return false;
@ -40,7 +40,7 @@ function arrayCompare(a,b) {
return true;
}
function invertMap(m) {
function invertMap(m:{}):{} {
var r = {};
if (m) {
for (var k in m) r[m[k]] = k;
@ -48,7 +48,7 @@ function invertMap(m) {
return r;
}
function highlightDifferences(s1, s2) {
function highlightDifferences(s1:string, s2:string):string {
var split1 = s1.split(/(\S+\s+)/).filter(function(n) {return n});
var split2 = s2.split(/(\S+\s+)/).filter(function(n) {return n});
var i = 0;
@ -104,7 +104,7 @@ function lzgmini() {
// Decode LZG coded data. The function returns the size of the decoded data.
// Use any of the get* methods to retrieve the decoded data.
this.decode = function(data) {
this.decode = function(data:number[]):number[] {
// Start by clearing the decompressed array in this object
outdata = null;
@ -112,7 +112,7 @@ function lzgmini() {
if ((data.length < LZG_HEADER_SIZE) || (data[0] != 76) ||
(data[1] != 90) || (data[2] != 71))
{
return 0;
return null;
}
// Calculate & check the checksum
@ -122,7 +122,7 @@ function lzgmini() {
(data[14] & 0xff);
if (calcChecksum(data) != checksum)
{
return 0;
return null;
}
// Check which method to use
@ -223,13 +223,13 @@ function lzgmini() {
}
// Get the decoded byte array
this.getByteArray = function()
this.getByteArray = function():number[]
{
return outdata;
}
// Get the decoded string from a Latin 1 (or ASCII) encoded array
this.getStringLatin1 = function()
this.getStringLatin1 = function():string
{
var str = "";
if (outdata != null)
@ -245,7 +245,7 @@ function lzgmini() {
}
// Get the decoded string from an UTF-8 encoded array
this.getStringUTF8 = function()
this.getStringUTF8 = function():string
{
var str = "";
if (outdata != null)

547
src/views.ts Normal file
View File

@ -0,0 +1,547 @@
"use strict";
// TODO: move to different namespace
var CodeMirror;
var platform;
var platform_id : string;
var compparams;
var addr2symbol : {[addr:number]:string};
var current_project;
var VirtualList;
var lastDebugState;
var pixeditframe;
// TODO: functions
var inspectVariable;
var jumpToLine;
// TODO: remove some calls of global functions
function SourceEditor(path, mode) {
var self = this;
var editor;
var dirtylisting = true;
var sourcefile;
var currentDebugLine;
self.createDiv = function(parent, text) {
var div = document.createElement('div');
div.setAttribute("class", "editor");
parent.appendChild(div);
newEditor(div);
if (text)
self.setText(text); // TODO: this calls setCode() and builds... it shouldn't
return div;
}
function newEditor(parent) {
var isAsm = mode=='6502' || mode =='z80' || mode=='verilog' || mode=='gas'; // TODO
editor = CodeMirror(parent, {
theme: 'mbo',
lineNumbers: true,
matchBrackets: true,
tabSize: 8,
indentAuto: true,
gutters: isAsm ? ["CodeMirror-linenumbers", "gutter-offset", "gutter-bytes", "gutter-clock", "gutter-info"]
: ["CodeMirror-linenumbers", "gutter-offset", "gutter-info"],
});
var timer;
editor.on('changes', function(ed, changeobj) {
clearTimeout(timer);
timer = setTimeout(function() {
current_project.updateFile(path, editor.getValue(), false);
}, 200);
});
editor.on('cursorActivity', function(ed) {
var start = editor.getCursor(true);
var end = editor.getCursor(false);
if (start.line == end.line && start.ch < end.ch) {
var name = editor.getSelection();
inspectVariable(editor, name);
} else {
inspectVariable(editor);
}
});
//scrollProfileView(editor);
editor.setOption("mode", mode);
}
self.setText = function(text) {
editor.setValue(text); // calls setCode()
editor.clearHistory();
}
self.getValue = function() {
return editor.getValue();
}
self.getPath = function() { return path; }
var lines2errmsg = [];
self.addErrorMarker = function(line, msg) {
var div = document.createElement("div");
div.setAttribute("class", "tooltipbox tooltiperror");
div.appendChild(document.createTextNode("\u24cd"));
var tooltip = document.createElement("span");
tooltip.setAttribute("class", "tooltiptext");
if (lines2errmsg[line])
msg = lines2errmsg[line] + "\n" + msg;
tooltip.appendChild(document.createTextNode(msg));
lines2errmsg[line] = msg;
div.appendChild(tooltip);
editor.setGutterMarker(line, "gutter-info", div);
}
self.markErrors = function(errors) {
// TODO: move cursor to error line if offscreen?
self.clearErrors();
var numLines = editor.lineCount();
for (var info of errors) {
// only mark errors with this filename, or without any filename
if (!info.path || path.endsWith(info.path)) {
var line = info.line-1;
if (line < 0 || line >= numLines) line = 0;
self.addErrorMarker(line, info.msg);
}
}
}
self.clearErrors = function() {
editor.clearGutter("gutter-info");
refreshDebugState();
dirtylisting = true;
}
self.getSourceFile = function() { return sourcefile; }
// TODO: update gutter only when refreshing this window
self.updateListing = function(_sourcefile) {
sourcefile = _sourcefile;
// update editor annotations
editor.clearGutter("gutter-info");
editor.clearGutter("gutter-bytes");
editor.clearGutter("gutter-offset");
editor.clearGutter("gutter-clock");
var lstlines = sourcefile.lines || [];
for (var info of lstlines) {
if (info.offset >= 0) {
var textel = document.createTextNode(hex(info.offset,4));
editor.setGutterMarker(info.line-1, "gutter-offset", textel);
}
if (info.insns) {
var insnstr = info.insns.length > 9 ? ("...") : info.insns;
var textel = document.createTextNode(insnstr);
editor.setGutterMarker(info.line-1, "gutter-bytes", textel);
if (info.iscode) {
var opcode = parseInt(info.insns.split()[0], 16);
if (platform.getOpcodeMetadata) {
var meta = platform.getOpcodeMetadata(opcode, info.offset);
var clockstr = meta.minCycles+"";
var textel = document.createTextNode(clockstr);
editor.setGutterMarker(info.line-1, "gutter-clock", textel);
}
}
}
}
}
self.setGutterBytes = function(line, s) {
var textel = document.createTextNode(s);
editor.setGutterMarker(line-1, "gutter-bytes", textel);
}
self.setCurrentLine = function(line) {
function addCurrentMarker(line) {
var div = document.createElement("div");
div.style.color = '#66ffff';
div.appendChild(document.createTextNode("\u25b6"));
editor.setGutterMarker(line, "gutter-info", div);
}
self.clearCurrentLine();
if (line>0) {
addCurrentMarker(line-1);
editor.setSelection({line:line,ch:0}, {line:line-1,ch:0}, {scroll:true});
currentDebugLine = line;
}
}
self.clearCurrentLine = function() {
if (currentDebugLine) {
editor.clearGutter("gutter-info");
editor.setSelection(editor.getCursor());
currentDebugLine = 0;
}
}
function refreshDebugState() {
self.clearCurrentLine();
var state = lastDebugState;
if (state && state.c) {
var PC = state.c.PC;
var line = sourcefile.findLineForOffset(PC);
if (line >= 0) {
self.setCurrentLine(line);
// TODO: switch to disasm?
}
}
}
function refreshListing() {
if (!dirtylisting) return;
dirtylisting = false;
var lst = current_project.getListingForFile(path);
if (lst && lst.sourcefile) {
self.updateListing(lst.sourcefile); // updates sourcefile variable
}
}
self.refresh = function() {
refreshListing();
refreshDebugState();
}
self.getLine = function(line) {
return editor.getLine(line-1);
}
self.getCurrentLine = function() {
return editor.getCursor().line+1;
}
self.getCursorPC = function() {
var line = self.getCurrentLine();
while (sourcefile && line >= 0) {
var pc = sourcefile.line2offset[line];
if (pc >= 0) return pc;
line--;
}
return -1;
}
// bitmap editor (TODO: refactor)
function handleWindowMessage(e) {
//console.log("window message", e.data);
if (e.data.bytes) {
editor.replaceSelection(e.data.bytestr);
}
if (e.data.close) {
$("#pixeditback").hide();
}
}
function openBitmapEditorWithParams(fmt, bytestr, palfmt, palstr) {
$("#pixeditback").show();
window.addEventListener("message", handleWindowMessage, false); // TODO: remove listener
pixeditframe.contentWindow.postMessage({fmt:fmt, bytestr:bytestr, palfmt:palfmt, palstr:palstr}, '*');
}
function lookBackwardsForJSONComment(line, req) {
var re = /[/;][*;]([{].+[}])[*;][/;]/;
while (--line >= 0) {
var s = editor.getLine(line);
var m = re.exec(s);
if (m) {
var jsontxt = m[1].replace(/([A-Za-z]+):/g, '"$1":'); // fix lenient JSON
var obj = JSON.parse(jsontxt);
if (obj[req]) {
var start = {obj:obj, line:line, ch:s.indexOf(m[0])+m[0].length};
var line0 = line;
var pos0 = start.ch;
line--;
while (++line < editor.lineCount()) {
var l = editor.getLine(line);
var endsection;
if (platform_id == 'verilog')
endsection = l.indexOf('end') >= pos0;
else
endsection = l.indexOf(';') >= pos0;
if (endsection) {
var end = {line:line, ch:editor.getLine(line).length};
return {obj:obj, start:start, end:end};
}
pos0 = 0;
}
line = line0;
}
}
}
}
self.openBitmapEditorAtCursor = function() {
if ($("#pixeditback").is(":visible")) {
$("#pixeditback").hide(250);
return;
}
var line = editor.getCursor().line + 1;
var data = lookBackwardsForJSONComment(self.getCurrentLine(), 'w');
if (data && data.obj && data.obj.w>0 && data.obj.h>0) {
var paldata = lookBackwardsForJSONComment(data.start.line-1, 'pal');
var palbytestr;
if (paldata) {
palbytestr = editor.getRange(paldata.start, paldata.end);
paldata = paldata.obj;
}
editor.setSelection(data.end, data.start);
openBitmapEditorWithParams(data.obj, editor.getSelection(), paldata, palbytestr);
} else {
alert("To edit graphics, move cursor to a constant array preceded by a comment in the format:\n\n/*{w:,h:,bpp:,count:...}*/\n\n(See code examples)");
}
}
}
///
function DisassemblerView() {
var self = this;
var disasmview;
self.getDisasmView = function() { return disasmview; }
self.createDiv = function(parent) {
var div = document.createElement('div');
div.setAttribute("class", "editor");
parent.appendChild(div);
newEditor(div);
return div;
}
function newEditor(parent) {
disasmview = CodeMirror(parent, {
mode: 'z80', // TODO: pick correct one
theme: 'cobalt',
tabSize: 8,
readOnly: true,
styleActiveLine: true
});
}
// TODO: too many globals
self.refresh = function() {
var state = lastDebugState || platform.saveState();
var pc = state.c ? state.c.PC : 0;
var curline = 0;
var selline = 0;
// TODO: not perfect disassembler
function disassemble(start, end) {
if (start < 0) start = 0;
if (end > 0xffff) end = 0xffff;
// TODO: use pc2visits
var a = start;
var s = "";
while (a < end) {
var disasm = platform.disassemble(a, platform.readAddress);
/* TODO: look thru all source files
var srclinenum = sourcefile && sourcefile.offset2line[a];
if (srclinenum) {
var srcline = getActiveEditor().getLine(srclinenum);
if (srcline && srcline.trim().length) {
s += "; " + srclinenum + ":\t" + srcline + "\n";
curline++;
}
}
*/
var bytes = "";
for (var i=0; i<disasm.nbytes; i++)
bytes += hex(platform.readAddress(a+i));
while (bytes.length < 14)
bytes += ' ';
var dline = hex(parseInt(a)) + "\t" + bytes + "\t" + disasm.line + "\n";
s += dline;
if (a == pc) selline = curline;
curline++;
a += disasm.nbytes || 1;
}
return s;
}
var text = disassemble(pc-96, pc) + disassemble(pc, pc+96);
disasmview.setValue(text);
disasmview.setCursor(selline, 0);
jumpToLine(disasmview, selline);
}
self.getCursorPC = function() {
var line = disasmview.getCursor().line;
if (line >= 0) {
var toks = disasmview.getLine(line).split(/\s+/);
if (toks && toks.length >= 1) {
var pc = parseInt(toks[0], 16);
if (pc >= 0) return pc;
}
}
return -1;
}
}
///
function ListingView(assemblyfile) {
var self = this;
this.__proto__ = new DisassemblerView();
self.refresh = function() {
var state = lastDebugState || platform.saveState();
var pc = state.c ? state.c.PC : 0;
var asmtext = assemblyfile.text;
var disasmview = self.getDisasmView();
if (platform_id == 'base_z80') { // TODO
asmtext = asmtext.replace(/[ ]+\d+\s+;.+\n/g, '');
asmtext = asmtext.replace(/[ ]+\d+\s+.area .+\n/g, '');
}
disasmview.setValue(asmtext);
var findPC = platform.getDebugCallback() ? pc : -1;
if (findPC >= 0) {
var lineno = assemblyfile.findLineForOffset(findPC);
if (lineno) {
// set cursor while debugging
if (platform.getDebugCallback())
disasmview.setCursor(lineno-1, 0);
jumpToLine(disasmview, lineno-1);
}
}
}
}
///
function MemoryView() {
var self = this;
var memorylist;
var dumplines;
var div;
// TODO?
function getVisibleEditorLineHeight() {
return $(".CodeMirror-line:visible").first().height();
}
self.createDiv = function(parent) {
div = document.createElement('div');
div.setAttribute("class", "memdump");
parent.appendChild(div);
showMemoryWindow(div);
return div;
}
function showMemoryWindow(parent) {
memorylist = new VirtualList({
w:$("#workspace").width(),
h:$("#workspace").height(),
itemHeight: getVisibleEditorLineHeight(),
totalRows: 0x1000,
generatorFn: function(row) {
var s = getMemoryLineAt(row);
var div = document.createElement("div");
if (dumplines) {
var dlr = dumplines[row];
if (dlr) div.classList.add('seg_' + getMemorySegment(dumplines[row].a));
}
div.appendChild(document.createTextNode(s));
return div;
}
});
$(parent).append(memorylist.container);
self.tick();
if (compparams && dumplines)
memorylist.scrollToItem(findMemoryWindowLine(compparams.data_start));
}
self.tick = function() {
if (memorylist) {
$(div).find('[data-index]').each(function(i,e) {
var div = $(e);
var row = div.attr('data-index');
var oldtext = div.text();
var newtext = getMemoryLineAt(row);
if (oldtext != newtext)
div.text(newtext);
});
}
}
function getMemoryLineAt(row) {
var offset = row * 16;
var n1 = 0;
var n2 = 16;
var sym;
if (getDumpLines()) {
var dl = dumplines[row];
if (dl) {
offset = dl.a & 0xfff0;
n1 = dl.a - offset;
n2 = n1 + dl.l;
sym = dl.s;
} else {
return '.';
}
}
var s = hex(offset,4) + ' ';
for (var i=0; i<n1; i++) s += ' ';
if (n1 > 8) s += ' ';
for (var i=n1; i<n2; i++) {
var read = platform.readAddress(offset+i);
if (i==8) s += ' ';
s += ' ' + (read>=0?hex(read,2):'??');
}
for (var i=n2; i<16; i++) s += ' ';
if (sym) s += ' ' + sym;
return s;
}
function getDumpLineAt(line) {
var d = dumplines[line];
if (d) {
return d.a + " " + d.s;
}
}
var IGNORE_SYMS = {s__INITIALIZER:true, /* s__GSINIT:true, */ _color_prom:true};
// TODO: addr2symbol for ca65; and make it work without symbols
function getDumpLines() {
if (!dumplines && addr2symbol) {
dumplines = [];
var ofs = 0;
var sym;
for (const _nextofs of Object.keys(addr2symbol)) {
var nextofs = parseInt(_nextofs); // convert from string (stupid JS)
var nextsym = addr2symbol[nextofs];
if (sym) {
if (IGNORE_SYMS[sym]) {
ofs = nextofs;
} else {
while (ofs < nextofs) {
var ofs2 = (ofs + 16) & 0xffff0;
if (ofs2 > nextofs) ofs2 = nextofs;
//if (ofs < 1000) console.log(ofs, ofs2, nextofs, sym);
dumplines.push({a:ofs, l:ofs2-ofs, s:sym});
ofs = ofs2;
}
}
}
sym = nextsym;
}
}
return dumplines;
}
function getMemorySegment(a) {
if (!compparams) return 'unknown';
if (a >= compparams.data_start && a < compparams.data_start+compparams.data_size) {
if (platform.getSP && a >= platform.getSP() - 15)
return 'stack';
else
return 'data';
}
else if (a >= compparams.code_start && a < compparams.code_start+compparams.code_size)
return 'code';
else
return 'unknown';
}
function findMemoryWindowLine(a) {
for (var i=0; i<dumplines.length; i++)
if (dumplines[i].a >= a)
return i;
}
}

78
src/windows.ts Normal file
View File

@ -0,0 +1,78 @@
"use strict";
import $ = require("jquery");
import { CodeProject } from "./project";
function ProjectWindows(containerdiv:HTMLElement, project:CodeProject) {
var self = this;
var id2window = {};
var id2createfn = {};
var id2div = {};
var activewnd;
var activediv;
var lasterrors;
// TODO: delete windows ever?
this.setCreateFunc = function(id, createfn) {
id2createfn[id] = createfn;
}
this.createOrShow = function(id) {
var wnd = id2window[id];
if (!wnd) {
wnd = id2window[id] = id2createfn[id](id);
}
var div = id2div[id];
if (!div) {
div = id2div[id] = wnd.createDiv(containerdiv, project.getFile(id));
}
if (activewnd != wnd) {
if (activediv)
$(activediv).hide();
activediv = div;
activewnd = wnd;
$(div).show();
this.refresh();
this.refreshErrors();
}
return wnd;
}
this.put = function(id, window) {
id2window[id] = window;
}
this.refresh = function() {
if (activewnd && activewnd.refresh)
activewnd.refresh();
}
this.tick = function() {
if (activewnd && activewnd.tick)
activewnd.tick();
}
this.setErrors = function(errors) {
lasterrors = errors;
this.refreshErrors();
}
this.refreshErrors = function() {
if (activewnd && activewnd.markErrors) {
if (lasterrors && lasterrors.length)
activewnd.markErrors(lasterrors);
else
activewnd.clearErrors();
}
}
this.getActive = function() { return activewnd; }
this.getCurrentText = function() {
if (activewnd && activewnd.getValue)
return activewnd.getValue();
else
alert("Please switch to an editor window.");
}
};

View File

@ -7,7 +7,7 @@ var includeInThisContext = function(path) {
vm.runInThisContext(code, path);
};
includeInThisContext("src/util.js");
includeInThisContext("gen/util.js");
includeInThisContext("src/pixed/pixeleditor.js");
describe('Pixel editor', function() {

View File

@ -45,9 +45,9 @@ global.localStorage = {
};
includeInThisContext("localForage/dist/localforage.js");
includeInThisContext("src/util.js");
includeInThisContext("gen/util.js");
includeInThisContext("src/store.js");
includeInThisContext("src/project.js");
var prj = require("../../gen/project.js");
var test_platform_id = "_TEST";
@ -74,7 +74,7 @@ describe('Store', function() {
var store = createNewPersistentStore(test_platform_id, function() {
var worker = {};
var platform = {};
var project = new CodeProject(worker, test_platform_id, platform, store);
var project = new prj.CodeProject(worker, test_platform_id, platform, store);
project.loadFiles(['test'], function(err, result) {
assert.equal(null, err);
assert.deepEqual([ { path: 'test', filename: 'test', data: 'a' } ], result);
@ -106,7 +106,7 @@ describe('Store', function() {
var platform = {
getToolForFilename: function(fn) { return 'dasm'; },
};
var project = new CodeProject(worker, test_platform_id, platform, store);
var project = new prj.CodeProject(worker, test_platform_id, platform, store);
project.callbackBuildStatus = function(b) { msgs.push(b) };
project.updateFile('test.a', ' lda #0');
project.updateFile('test.a', ' lda #1'); // don't send twice (yet)
@ -129,7 +129,7 @@ describe('Store', function() {
};
var platform = {
};
var project = new CodeProject(worker, test_platform_id, platform, store);
var project = new prj.CodeProject(worker, test_platform_id, platform, store);
project.callbackBuildStatus = function(b) { msgs.push(b) };
var buildresult = {
listings: {
@ -143,8 +143,8 @@ describe('Store', function() {
var lst = buildresult.listings.test;
console.log(lst);
assert.equal(3, lst.sourcefile.findLineForOffset(61440+15));
assert.equal(0, lst.sourcefile.findLineForOffset(61440+16));
assert.equal(0, lst.sourcefile.findLineForOffset(61440-1));
assert.equal(null, lst.sourcefile.findLineForOffset(61440+16));
assert.equal(null, lst.sourcefile.findLineForOffset(61440-1));
assert.equal(1, lst.sourcefile.lineCount());
done();
});

15
tsconfig.json Normal file
View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"outDir": "./gen",
"allowJs": true,
"sourceMap": true,
"target": "es5",
"lib": [
"es2016",
"dom"
]
},
"include": [
"./src/*.ts"
]
}