diff --git a/.travis.yml b/.travis.yml
index 5c26caf6..455fb9b4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,3 +8,7 @@ before_install:
language: node_js
node_js:
- "6.5.0"
+script:
+ - npm run build
+ - npm test
+
\ No newline at end of file
diff --git a/Makefile b/Makefile
index a182cfad..a6d7b3f3 100644
--- a/Makefile
+++ b/Makefile
@@ -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 .
diff --git a/doc/notes.txt b/doc/notes.txt
index 2c943109..4781c0ad 100644
--- a/doc/notes.txt
+++ b/doc/notes.txt
@@ -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
diff --git a/index.html b/index.html
index f57f8027..7ec150a4 100644
--- a/index.html
+++ b/index.html
@@ -207,6 +207,13 @@ ga('send', 'pageview');
+
+
@@ -232,9 +239,11 @@ ga('send', 'pageview');
-
+
-
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..c4c1b360
--- /dev/null
+++ b/package-lock.json
@@ -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
+ }
+ }
+}
diff --git a/package.json b/package.json
index ee484f90..1d0bbf61 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/project.js b/src/project.js
deleted file mode 100644
index 099bf204..00000000
--- a/src/project.js
+++ /dev/null
@@ -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 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
- }
- };
-}
-
diff --git a/src/project.ts b/src/project.ts
new file mode 100644
index 00000000..c70f3aae
--- /dev/null
+++ b/src/project.ts
@@ -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 ((text).trim && (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 {
+ 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];
+ }
+ }
+ }
+}
+
diff --git a/src/ui.js b/src/ui.js
index c7c623ef..34822c9d 100644
--- a/src/ui.js
+++ b/src/ui.js
@@ -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= 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 8) s += ' ';
- for (var i=n1; i=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= 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) {
diff --git a/src/util.js b/src/util.ts
similarity index 91%
rename from src/util.js
rename to src/util.ts
index f539873c..63bdb189 100644
--- a/src/util.js
+++ b/src/util.ts
@@ -1,23 +1,23 @@
"use strict";
-function lpad(s,n) { while(s.length 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)
diff --git a/src/views.ts b/src/views.ts
new file mode 100644
index 00000000..0226dece
--- /dev/null
+++ b/src/views.ts
@@ -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= 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 8) s += ' ';
+ for (var i=n1; i=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= a)
+ return i;
+ }
+}
+
diff --git a/src/windows.ts b/src/windows.ts
new file mode 100644
index 00000000..68d322a7
--- /dev/null
+++ b/src/windows.ts
@@ -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.");
+ }
+};
+
diff --git a/test/cli/testpixelconvert.js b/test/cli/testpixelconvert.js
index a170b401..b83ac275 100644
--- a/test/cli/testpixelconvert.js
+++ b/test/cli/testpixelconvert.js
@@ -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() {
diff --git a/test/cli/teststore.js b/test/cli/teststore.js
index c061e026..c1dbf891 100644
--- a/test/cli/teststore.js
+++ b/test/cli/teststore.js
@@ -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();
});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..4a390bf5
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "outDir": "./gen",
+ "allowJs": true,
+ "sourceMap": true,
+ "target": "es5",
+ "lib": [
+ "es2016",
+ "dom"
+ ]
+ },
+ "include": [
+ "./src/*.ts"
+ ]
+}