removed script platform

This commit is contained in:
Steven Hugg 2023-11-05 12:16:39 -06:00
parent fbd9089a8d
commit a2d818c3e5
15 changed files with 28 additions and 2086 deletions

134
package-lock.json generated
View File

@ -9,27 +9,20 @@
"version": "3.11.0",
"license": "GPL-3.0",
"dependencies": {
"@types/chroma-js": "^2.1.3",
"@types/dompurify": "^2.3.4",
"@types/emscripten": "^1.39.5",
"@types/js-yaml": "^4.0.5",
"atob": "^2.1.x",
"binaryen": "^101.0.0",
"btoa": "^1.2.x",
"chroma-js": "^2.1.2",
"clipboard": "^2.0.6",
"dompurify": "^2.4.0",
"error-stack-parser": "^2.0.6",
"fast-png": "^5.0.4",
"file-saver": "^2.0.5",
"jquery": "^3.6.3",
"jszip": "^3.7.0",
"localforage": "^1.9.0",
"mousetrap": "^1.6.5",
"octokat": "^0.10.0",
"preact": "^10.5.14",
"split.js": "^1.6.2",
"yufka": "^2.0.1"
"split.js": "^1.6.2"
},
"devDependencies": {
"@types/bootbox": "^5.1.3",
@ -45,6 +38,7 @@
"cors": "^2.8.5",
"esbuild": "^0.12.29",
"express": "^4.18.2",
"fast-png": "^5.0.4",
"jsdom": "^21.1.0",
"lzg": "^1.0.x",
"mocha": "^9.2.0",
@ -865,11 +859,6 @@
"integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==",
"optional": true
},
"node_modules/@types/chroma-js": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.5.tgz",
"integrity": "sha512-LnJmElng1zoH7GOYqIo/EuL7L0/vEh5rc+fKaF4rsylJyjwOkX0pXeBemH25FQAWHifKJWqaRwR0EhC+yDod9A=="
},
"node_modules/@types/dompurify": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz",
@ -962,11 +951,6 @@
"@types/sizzle": "*"
}
},
"node_modules/@types/js-yaml": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
"integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA=="
},
"node_modules/@types/lodash": {
"version": "4.14.200",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz",
@ -988,7 +972,8 @@
"node_modules/@types/pako": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz",
"integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA=="
"integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==",
"dev": true
},
"node_modules/@types/selenium-webdriver": {
"version": "4.1.15",
@ -1084,6 +1069,7 @@
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
"devOptional": true,
"bin": {
"acorn": "bin/acorn"
},
@ -1735,11 +1721,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/chroma-js": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
},
"node_modules/chromedriver": {
"version": "119.0.0",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-119.0.0.tgz",
@ -2624,6 +2605,7 @@
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
"optional": true,
"dependencies": {
"stackframe": "^1.3.4"
}
@ -2851,6 +2833,7 @@
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-5.0.4.tgz",
"integrity": "sha512-vTNj6yixRnclW6sTlCeH6sNRLBOhM5ITmlo1LSU5ojKEc2e9kZkqXPo2xzBxKb61MBCXRXBcr8qJztOHr2O6WQ==",
"dev": true,
"dependencies": {
"@types/pako": "^1.0.1",
"iobuffer": "^5.0.2",
@ -2860,7 +2843,8 @@
"node_modules/fast-png/node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"dev": true
},
"node_modules/fd-slicer": {
"version": "1.1.0",
@ -3617,7 +3601,8 @@
"node_modules/iobuffer": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.3.2.tgz",
"integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw=="
"integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw==",
"dev": true
},
"node_modules/ip-regex": {
"version": "4.3.0",
@ -4668,14 +4653,6 @@
"node": ">=0.8.0"
}
},
"node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"dependencies": {
"sourcemap-codec": "^1.4.8"
}
},
"node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@ -6174,15 +6151,6 @@
"node": ">= 0.4.0"
}
},
"node_modules/preact": {
"version": "10.12.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -6903,12 +6871,6 @@
"source-map": "^0.6.0"
}
},
"node_modules/sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"deprecated": "Please use @jridgewell/sourcemap-codec instead"
},
"node_modules/spawn-wrap": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz",
@ -7002,7 +6964,8 @@
"node_modules/stackframe": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
"optional": true
},
"node_modules/stacktrace-parser": {
"version": "0.1.10",
@ -7983,18 +7946,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yufka": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/yufka/-/yufka-2.1.1.tgz",
"integrity": "sha512-/8gX42mxcoJ21pdGbEg2w/6l0xlL7XFkFarcLmq2upxt6PTf7ehHsPnQQibGd6yLP1odMh0F04eZMh/gZhQHIQ==",
"dependencies": {
"acorn": "^8.3.0",
"magic-string": "^0.25.2"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/yup": {
"version": "0.32.11",
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
@ -8692,11 +8643,6 @@
"integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==",
"optional": true
},
"@types/chroma-js": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.5.tgz",
"integrity": "sha512-LnJmElng1zoH7GOYqIo/EuL7L0/vEh5rc+fKaF4rsylJyjwOkX0pXeBemH25FQAWHifKJWqaRwR0EhC+yDod9A=="
},
"@types/dompurify": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz",
@ -8788,11 +8734,6 @@
"@types/sizzle": "*"
}
},
"@types/js-yaml": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
"integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA=="
},
"@types/lodash": {
"version": "4.14.200",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz",
@ -8814,7 +8755,8 @@
"@types/pako": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz",
"integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA=="
"integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==",
"dev": true
},
"@types/selenium-webdriver": {
"version": "4.1.15",
@ -8906,7 +8848,8 @@
"acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
"devOptional": true
},
"acorn-globals": {
"version": "7.0.1",
@ -9400,11 +9343,6 @@
"readdirp": "~3.6.0"
}
},
"chroma-js": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
},
"chromedriver": {
"version": "119.0.0",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-119.0.0.tgz",
@ -10096,6 +10034,7 @@
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
"optional": true,
"requires": {
"stackframe": "^1.3.4"
}
@ -10270,6 +10209,7 @@
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-5.0.4.tgz",
"integrity": "sha512-vTNj6yixRnclW6sTlCeH6sNRLBOhM5ITmlo1LSU5ojKEc2e9kZkqXPo2xzBxKb61MBCXRXBcr8qJztOHr2O6WQ==",
"dev": true,
"requires": {
"@types/pako": "^1.0.1",
"iobuffer": "^5.0.2",
@ -10279,7 +10219,8 @@
"pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"dev": true
}
}
},
@ -10859,7 +10800,8 @@
"iobuffer": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.3.2.tgz",
"integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw=="
"integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw==",
"dev": true
},
"ip-regex": {
"version": "4.3.0",
@ -11707,14 +11649,6 @@
}
}
},
"magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"requires": {
"sourcemap-codec": "^1.4.8"
}
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@ -12871,11 +12805,6 @@
"integrity": "sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ==",
"dev": true
},
"preact": {
"version": "10.12.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg=="
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -13463,11 +13392,6 @@
"source-map": "^0.6.0"
}
},
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"spawn-wrap": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz",
@ -13556,7 +13480,8 @@
"stackframe": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
"optional": true
},
"stacktrace-parser": {
"version": "0.1.10",
@ -14309,15 +14234,6 @@
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"devOptional": true
},
"yufka": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/yufka/-/yufka-2.1.1.tgz",
"integrity": "sha512-/8gX42mxcoJ21pdGbEg2w/6l0xlL7XFkFarcLmq2upxt6PTf7ehHsPnQQibGd6yLP1odMh0F04eZMh/gZhQHIQ==",
"requires": {
"acorn": "^8.3.0",
"magic-string": "^0.25.2"
}
},
"yup": {
"version": "0.32.11",
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",

View File

@ -10,27 +10,20 @@
},
"license": "GPL-3.0",
"dependencies": {
"@types/chroma-js": "^2.1.3",
"@types/dompurify": "^2.3.4",
"@types/emscripten": "^1.39.5",
"@types/js-yaml": "^4.0.5",
"atob": "^2.1.x",
"binaryen": "^101.0.0",
"btoa": "^1.2.x",
"chroma-js": "^2.1.2",
"clipboard": "^2.0.6",
"dompurify": "^2.4.0",
"error-stack-parser": "^2.0.6",
"fast-png": "^5.0.4",
"file-saver": "^2.0.5",
"jquery": "^3.6.3",
"jszip": "^3.7.0",
"localforage": "^1.9.0",
"mousetrap": "^1.6.5",
"octokat": "^0.10.0",
"preact": "^10.5.14",
"split.js": "^1.6.2",
"yufka": "^2.0.1"
"split.js": "^1.6.2"
},
"devDependencies": {
"@types/bootbox": "^5.1.3",
@ -46,6 +39,7 @@
"cors": "^2.8.5",
"esbuild": "^0.12.29",
"express": "^4.18.2",
"fast-png": "^5.0.4",
"jsdom": "^21.1.0",
"lzg": "^1.0.x",
"mocha": "^9.2.0",

View File

@ -1,294 +0,0 @@
import { WorkerError } from "../workertypes";
import ErrorStackParser = require("error-stack-parser");
import yufka from 'yufka';
import * as bitmap from "./lib/bitmap";
import * as io from "./lib/io";
import * as output from "./lib/output";
import * as color from "./lib/color";
import * as scriptui from "./lib/scriptui";
export const PROP_CONSTRUCTOR_NAME = "$$consname";
export interface Cell {
id: string;
object?: any;
}
export interface RunResult {
cells: Cell[];
state: {};
}
const IMPORTS = {
'bitmap': bitmap,
'io': io,
'output': output,
'color': color,
'ui': scriptui,
}
const LINE_NUMBER_OFFSET = 3; // TODO: shouldnt need?
const GLOBAL_BADLIST = [
'eval'
]
const GLOBAL_GOODLIST = [
'eval', // 'eval' can't be defined or assigned to in strict mode code
'Math', 'JSON',
'parseFloat', 'parseInt', 'isFinite', 'isNaN',
'String', 'Symbol', 'Number', 'Object', 'Boolean', 'NaN', 'Infinity', 'Date', 'BigInt',
'Set', 'Map', 'RegExp', 'Array', 'ArrayBuffer', 'DataView',
'Float32Array', 'Float64Array',
'Int8Array', 'Int16Array', 'Int32Array',
'Uint8Array', 'Uint16Array', 'Uint32Array', 'Uint8ClampedArray',
]
class RuntimeError extends Error {
constructor(public loc: acorn.SourceLocation, msg: string) {
super(msg);
}
}
function setConstructorName(o: object) : void {
let name = Object.getPrototypeOf(o)?.constructor?.name;
if (name != null && name != 'Object') {
o[PROP_CONSTRUCTOR_NAME] = name;
}
}
export class Environment {
preamble: string;
postamble: string;
obj: {};
seq: number;
declvars : {[name : string] : acorn.Node};
builtins : {}
constructor(
public readonly globalenv: any,
public readonly path: string
) {
var badlst = Object.getOwnPropertyNames(this.globalenv).filter(name => GLOBAL_GOODLIST.indexOf(name) < 0);
this.builtins = {
print: (...args) => this.print(args),
...IMPORTS
}
this.preamble = `'use strict';var ${badlst.join(',')};`;
for (var impname in this.builtins) {
this.preamble += `var ${impname}=$$.${impname};`
}
this.preamble += '{\n';
this.postamble = '\n}';
}
error(varname: string, msg: string) {
let obj = this.declvars && this.declvars[varname];
console.log('ERROR', varname, obj, this);
throw new RuntimeError(obj && obj.loc, msg);
}
print(args: any[]) {
if (args && args.length > 0 && args[0] != null) {
this.obj[`$print__${this.seq++}`] = args.length == 1 ? args[0] : args;
}
}
preprocess(code: string): string {
this.declvars = {};
this.seq = 0;
let options = {
// https://www.npmjs.com/package/magic-string#sgeneratemap-options-
sourceMap: {
file: this.path,
source: this.path,
hires: false,
includeContent: false
},
// https://github.com/acornjs/acorn/blob/master/acorn/README.md
acorn: {
ecmaVersion: 6 as any,
locations: true,
allowAwaitOutsideFunction: true,
allowReturnOutsideFunction: true,
allowReserved: true,
}
};
const result = yufka(code, options, (node, { update, source, parent }) => {
const isTopLevel = () => {
return parent() && parent().type === 'ExpressionStatement' && parent(2) && parent(2).type === 'Program';
}
const convertTopToPrint = () => {
if (isTopLevel()) {
let printkey = `$print__${this.seq++}`;
update(`this.${printkey} = io.data.load(${source()}, ${JSON.stringify(printkey)})`);
//update(`print(${source()});`)
}
}
const left = node['left'];
switch (node.type) {
// add preamble, postamble
case 'Program':
update(`${this.preamble}${source()}${this.postamble}`)
break;
// error on forbidden keywords
case 'Identifier':
if (GLOBAL_BADLIST.indexOf(source()) >= 0) {
update(`__FORBIDDEN__KEYWORD__${source()}__`) // TODO? how to preserve line number?
} else {
convertTopToPrint();
}
break;
// x = expr --> var x = expr (first use)
case 'AssignmentExpression':
if (isTopLevel()) {
if (left && left.type === 'Identifier') {
if (!this.declvars[left.name]) {
update(`var ${left.name}=io.data.load(this.${source()}, ${JSON.stringify(left.name)})`)
this.declvars[left.name] = left;
} else {
update(`${left.name}=this.${source()}`)
}
}
}
break;
// convert lone expressions to print()
case 'UnaryExpression':
case 'BinaryExpression':
case 'CallExpression':
case 'MemberExpression':
convertTopToPrint();
break;
// literal comments
case 'Literal':
if (typeof node['value'] === 'string' && isTopLevel()) {
update(`this.$doc__${this.seq++} = { literaltext: ${source()} };`);
} else {
convertTopToPrint();
}
break;
}
});
return result.toString();
}
async run(code: string): Promise<void> {
// TODO: split into cells based on "--" linebreaks?
code = this.preprocess(code);
this.obj = {};
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
const fn = new AsyncFunction('$$', code).bind(this.obj, this.builtins);
await fn.call(this);
this.checkResult(this.obj, new Set(), []);
}
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
// TODO: return initial location of thingie
checkResult(o, checked: Set<object>, fullkey: string[]) {
if (o == null) return;
if (checked.has(o)) return;
if (typeof o === 'object') {
setConstructorName(o);
delete o.$$callback; // clear callbacks (TODO? put somewhere else?)
if (o.length > 100) return; // big array, don't bother
if (o.BYTES_PER_ELEMENT > 0) return; // typed array, don't bother
checked.add(o); // so we don't recurse if cycle
function prkey() { return fullkey.join('.') }
// go through all object properties recursively
for (var [key, value] of Object.entries(o)) {
if (value == null && fullkey.length == 0 && !key.startsWith("$")) {
this.error(key, `"${key}" has no value.`)
}
fullkey.push(key);
if (typeof value === 'function') {
if (fullkey.length == 1)
this.error(fullkey[0], `"${prkey()}" is a function. Did you forget to pass parameters?`); // TODO? did you mean (needs to see entire expr)
else
this.error(fullkey[0], `This expression may be incomplete, or it contains a function object: ${prkey()}`); // TODO? did you mean (needs to see entire expr)
}
if (typeof value === 'symbol') {
this.error(fullkey[0], `"${prkey()}" is a Symbol, and can't be used.`) // TODO?
}
if (value instanceof Promise) {
this.error(fullkey[0], `"${prkey()}" is unresolved. Use "await" before expression.`) // TODO?
}
this.checkResult(value, checked, fullkey);
fullkey.pop();
}
}
}
render(): Cell[] {
var cells = [];
for (var [key, value] of Object.entries(this.obj)) {
if (typeof value === 'function') {
// TODO: find other values, functions embedded in objects?
} else {
var cell: Cell = { id: key, object: value };
cells.push(cell);
}
}
return cells;
}
extractErrors(e: Error): WorkerError[] {
let loc = e['loc'];
if (loc && loc.start && loc.end) {
return [{
path: this.path,
msg: e.message,
line: loc.start.line,
start: loc.start.column,
end: loc.end.line,
}]
}
if (loc && loc.line != null) {
return [{
path: this.path,
msg: e.message,
line: loc.line,
start: loc.column,
}]
}
// TODO: Cannot parse given Error object?
let frames = ErrorStackParser.parse(e);
let frame = frames.findIndex(f => f.functionName === 'anonymous');
let errors = [];
// if ErrorStackParser fails, resort to regex
if (frame < 0 && e.stack != null) {
let m = /.anonymous.:(\d+):(\d+)/g.exec(e.stack);
if (m != null) {
errors.push( {
path: this.path,
msg: e.message,
line: parseInt(m[1]) - LINE_NUMBER_OFFSET,
});
}
}
// otherwise iterate thru all the frames
while (frame >= 0) {
console.log(frames[frame]);
if (frames[frame].fileName.endsWith('Function')) {
// TODO: use source map
errors.push( {
path: this.path,
msg: e.message,
line: frames[frame].lineNumber - LINE_NUMBER_OFFSET,
//start: frames[frame].columnNumber,
} );
}
--frame;
}
// if no stack frames parsed, last resort error msg
if (errors.length == 0) {
errors.push( {
path: this.path,
msg: e.message,
line: 0
} );
}
return errors;
}
commitLoadableState() {
// TODO: visit children?
for (let [key, value] of Object.entries(this.obj)) {
let loadable = <any>value as io.Loadable;
io.data.save(loadable, key);
}
return io.$$getData();
}
}

View File

@ -1,474 +0,0 @@
// TODO: dynamic import
import * as fastpng from 'fast-png';
import { Palette } from './color';
import * as io from './io'
import * as color from './color'
import { coerceToArray, findIntegerFactors, RGBA } from '../../util';
export type PixelMapFunction = (x: number, y: number) => number;
export abstract class AbstractBitmap<T> {
aspect? : number; // aspect ratio, null == default == 1:1
style? : {} = {}; // CSS styles (TODO: other elements?)
constructor(
public readonly width: number,
public readonly height: number,
) {
}
abstract blank(width: number, height: number) : AbstractBitmap<T>;
abstract setarray(arr: ArrayLike<number>) : void;
abstract set(x: number, y: number, val: number) : void;
abstract get(x: number, y: number): number;
abstract getrgba(x: number, y: number): number;
inbounds(x: number, y: number): boolean {
return (x >= 0 && x < this.width && y >= 0 && y < this.height);
}
assign(fn: ArrayLike<number> | PixelMapFunction) : void {
if (typeof fn === 'function') {
for (let y=0; y<this.height; y++) {
for (let x=0; x<this.width; x++) {
this.set(x, y, fn(x, y));
}
}
} else if (fn && fn['length'] != null) {
this.setarray(fn);
} else {
throw new Error(`Illegal argument to assign(): ${fn}`)
}
}
clone() : AbstractBitmap<T> {
let bmp = this.blank(this.width, this.height);
bmp.assign((x,y) => this.get(x,y));
return bmp;
}
crop(srcx: number, srcy: number, width: number, height: number) {
let dest = this.blank(width, height);
dest.assign((x, y) => this.get(x + srcx, y + srcy));
return dest;
}
blit(src: BitmapType,
destx: number, desty: number,
srcx: number, srcy: number)
{
destx |= 0;
desty |= 0;
srcx |= 0;
srcy |= 0;
for (var y=0; y<src.height; y++) {
for (var x=0; x<src.width; x++) {
let rgba = src.getrgba(x+srcx, y+srcy);
this.set(x+destx, y+desty, rgba);
}
}
}
fill(destx: number, desty: number, width:number, height:number, value:number) {
for (var y=0; y<height; y++) {
for (var x=0; x<width; x++) {
this.set(x+destx, y+desty, value);
}
}
}
}
export class RGBABitmap extends AbstractBitmap<RGBABitmap> {
public readonly rgba: Uint32Array
constructor(
width: number,
height: number,
initial?: Uint32Array | PixelMapFunction
) {
super(width, height);
this.rgba = new Uint32Array(this.width * this.height);
if (initial) this.assign(initial);
}
setarray(arr: ArrayLike<number>) {
this.rgba.set(arr);
}
set(x: number, y: number, rgba: number) {
if (this.inbounds(x,y)) this.rgba[y * this.width + x] = rgba;
}
get(x: number, y: number): number {
return this.inbounds(x,y) ? this.rgba[y * this.width + x] : 0;
}
getrgba(x: number, y: number): number {
return this.get(x, y);
}
blank(width?: number, height?: number) : RGBABitmap {
return new RGBABitmap(width || this.width, height || this.height);
}
clone() : RGBABitmap {
let bitmap = this.blank(this.width, this.height);
bitmap.rgba.set(this.rgba);
return bitmap;
}
}
export abstract class MappedBitmap extends AbstractBitmap<MappedBitmap> {
public readonly pixels: Uint8Array
constructor(
width: number,
height: number,
public readonly bpp: number,
initial?: Uint8Array | PixelMapFunction
) {
super(width, height);
if (bpp != 1 && bpp != 2 && bpp != 4 && bpp != 8)
throw new Error(`Invalid bits per pixel: ${bpp}`);
this.pixels = new Uint8Array(this.width * this.height);
if (initial) this.assign(initial);
}
setarray(arr: ArrayLike<number>) {
this.pixels.set(arr);
}
set(x: number, y: number, index: number) {
if (this.inbounds(x,y)) this.pixels[y * this.width + x] = index;
}
get(x: number, y: number): number {
return this.inbounds(x,y) ? this.pixels[y * this.width + x] : 0;
}
}
function getbpp(x : number | Palette) : number {
if (typeof x === 'number') return x;
if (x instanceof Palette) {
if (x.colors.length <= 2) return 1;
else if (x.colors.length <= 4) return 2;
else if (x.colors.length <= 16) return 4;
}
return 8;
}
export class IndexedBitmap extends MappedBitmap {
public palette: Palette;
constructor(
width: number,
height: number,
bppOrPalette: number | Palette,
initial?: Uint8Array | PixelMapFunction
) {
super(width, height, getbpp(bppOrPalette), initial);
this.palette = bppOrPalette instanceof Palette
? bppOrPalette
: color.palette.colors(1 << this.bpp);
}
getrgba(x: number, y: number): number {
return this.palette && this.palette.colors[this.get(x, y)];
}
blank(width?: number, height?: number, newPalette?: Palette) : IndexedBitmap {
let bitmap = new IndexedBitmap(width || this.width, height || this.height, newPalette || this.palette);
return bitmap;
}
clone() : IndexedBitmap {
let bitmap = this.blank(this.width, this.height);
bitmap.pixels.set(this.pixels);
return bitmap;
}
}
export function rgba(width: number, height: number, initial?: Uint32Array | PixelMapFunction) {
return new RGBABitmap(width, height, initial);
}
export function indexed(width: number, height: number, bpp: number, initial?: Uint8Array | PixelMapFunction) {
return new IndexedBitmap(width, height, bpp, initial);
}
export type BitmapType = RGBABitmap | IndexedBitmap;
// TODO: check arguments
export function decode(arr: Uint8Array, fmt: PixelEditorImageFormat) {
var pixels = convertWordsToImages(arr, fmt);
// TODO: guess if missing w/h/count?
// TODO: reverse mapping
// TODO: maybe better composable functions
let bpp = (fmt.bpp||1) * (fmt.np||1);
return pixels.map(data => new IndexedBitmap(fmt.w, fmt.h, bpp, data));
}
export interface BitmapAnalysis {
min: {w: number, h: number};
max: {w: number, h: number};
}
export function analyze(bitmaps: BitmapType[]) {
bitmaps = coerceToArray(bitmaps);
let r = {min:{w:0,h:0}, max:{w:0,h:0}};
for (let bmp of bitmaps) {
if (!(bmp instanceof AbstractBitmap)) return null;
r.min.w = Math.min(bmp.width);
r.max.w = Math.max(bmp.width);
r.min.h = Math.min(bmp.height);
r.max.h = Math.max(bmp.height);
}
return r;
}
export interface MontageOptions {
analysis?: BitmapAnalysis;
gap?: number;
aspect?: number;
}
export function montage(bitmaps: BitmapType[], options?: MontageOptions) {
bitmaps = coerceToArray(bitmaps);
let minmax = (options && options.analysis) || analyze(bitmaps);
if (minmax == null) throw new Error(`Expected an array of bitmaps`);
let hitrects = [];
let aspect = (options && options.aspect) || 1;
let gap = (options && options.gap) || 0;
if (minmax.min.w == minmax.max.w && minmax.min.h == minmax.max.h) {
let totalPixels = minmax.min.w * minmax.min.h * bitmaps.length;
let factors = findIntegerFactors(totalPixels, minmax.max.w, minmax.max.h, aspect);
let columns = Math.ceil(factors.a / minmax.min.w); // TODO: rounding?
let rows = Math.ceil(factors.b / minmax.min.h);
let result = new RGBABitmap(factors.a + gap * (columns-1), factors.b + gap * (rows-1));
let x = 0;
let y = 0;
bitmaps.forEach((bmp) => {
result.blit(bmp, x, y, 0, 0);
hitrects.push({x, y, w: bmp.width, h: bmp.height })
x += bmp.width + gap;
if (x >= result.width) {
x = 0;
y += bmp.height + gap;
}
})
return result;
} else {
throw new Error(`combine() only supports uniformly-sized images right now`); // TODO
}
}
/////
export namespace png {
export function read(url: string): BitmapType {
return decode(io.readbin(url));
}
export function decode(data: Uint8Array): BitmapType {
let png = fastpng.decode(data);
return convertToBitmap(png);
}
function convertToBitmap(png: fastpng.IDecodedPNG): BitmapType {
if (png.palette && png.depth <= 8) {
return convertIndexedToBitmap(png);
} else {
return convertRGBAToBitmap(png);
}
}
function convertIndexedToBitmap(png: fastpng.IDecodedPNG): IndexedBitmap {
var palarr = <any>png.palette as [number, number, number, number][];
var palette = new Palette(palarr);
let bitmap = new IndexedBitmap(png.width, png.height, png.depth);
if (png.depth == 8) {
bitmap.pixels.set(png.data);
} else {
let pixperbyte = Math.floor(8 / png.depth);
let mask = (1 << png.depth) - 1;
for (let i = 0; i < bitmap.pixels.length; i++) {
var bofs = (i % pixperbyte) * png.depth;
let val = png.data[Math.floor(i / pixperbyte)];
bitmap.pixels[i] = (val >> bofs) & mask;
}
}
bitmap.palette = palette;
// TODO: aspect etc
return bitmap;
}
function convertRGBAToBitmap(png: fastpng.IDecodedPNG): RGBABitmap {
const bitmap = new RGBABitmap(png.width, png.height);
const rgba : [number,number,number,number] = [0, 0, 0, 0];
for (let i = 0; i < bitmap.rgba.length; i++) {
for (let j = 0; j < 4; j++)
rgba[j] = png.data[i * 4 + j];
bitmap.rgba[i] = color.rgba(rgba);
}
// TODO: aspect etc
return bitmap;
}
}
export namespace font {
interface Font {
maxheight: number;
glyphs: { [code: number]: Glyph };
properties: {};
}
class Glyph extends IndexedBitmap {
constructor(width: number, height: number, bpp: number,
public readonly code: number,
public readonly yoffset: number) {
super(width, height, bpp);
}
}
export function read(url: string) {
if (url.endsWith('.yaff')) return decodeyafflines(io.readlines(url));
if (url.endsWith('.draw')) return decodedrawlines(io.readlines(url));
throw new Error(`Can't figure out font format for "${url}"`);
}
export function decodeglyph(glines: string[], curcode: number, yoffset: number): Glyph {
let width = 0;
for (var gline of glines) width = Math.max(width, gline.length);
let g = new Glyph(width, glines.length, 1, curcode, yoffset);
for (var y = 0; y < glines.length; y++) {
let gline = glines[y];
for (var x = 0; x < gline.length; x++) {
let ch = gline[x];
g.set(x, y, ch==='@' || ch==='#' ? 1 : 0); // TODO: provide mapping
}
}
return g;
}
// https://github.com/robhagemans/monobit
export function decodeyafflines(lines: string[]): Font {
let maxheight = 0;
let properties = {};
let glyphs = {};
let yoffset = 0;
let curcode = -1;
let curglyph: string[] = [];
const re_prop = /^([\w-]+):\s+(.+)/i;
const re_label = /^0x([0-9a-f]+):|u[+]([0-9a-f]+):|(\w+):/i;
const re_gline = /^\s+([.@]+)/
function addfont() {
if (curcode >= 0 && curglyph.length) {
glyphs[curcode] = decodeglyph(curglyph, curcode, yoffset);
curcode = -1;
curglyph = [];
}
}
for (let line of lines) {
let m: RegExpExecArray;
if (m = re_prop.exec(line)) {
properties[m[1]] = m[2];
if (m[1] === 'bottom') yoffset = parseInt(m[2]);
if (m[1] === 'size') maxheight = parseInt(m[2]);
} else if (m = re_label.exec(line)) {
addfont();
if (m[1] != null) curcode = parseInt(m[1], 16);
else if (m[2] != null) curcode = parseInt(m[2], 16);
else if (m[3] != null) curcode = null; // text labels not supported
} else if (m = re_gline.exec(line)) {
curglyph.push(m[1]);
}
if (isNaN(curcode + yoffset + maxheight))
throw new Error(`couldn't decode .yaff: ${JSON.stringify(line)}`)
}
addfont();
return { maxheight, properties, glyphs };
}
// https://github.com/robhagemans/monobit
export function decodedrawlines(lines: string[]): Font {
let maxheight = 0;
let properties = {};
let glyphs = {};
let curcode = -1;
let curglyph: string[] = [];
const re_gline = /^([0-9a-f]+)?[:]?\s*([-#]+)/i;
function addfont() {
if (curcode >= 0 && curglyph.length) {
glyphs[curcode] = decodeglyph(curglyph, curcode, 0);
maxheight = Math.max(maxheight, curglyph.length);
curcode = -1;
curglyph = [];
}
}
for (let line of lines) {
let m: RegExpExecArray;
if (m = re_gline.exec(line)) {
if (m[1] != null) {
addfont();
curcode = parseInt(m[1], 16);
if (isNaN(curcode))
throw new Error(`couldn't decode .draw: ${JSON.stringify(line)}`)
}
curglyph.push(m[2]);
}
}
addfont();
return { maxheight, properties, glyphs };
}
}
// TODO: merge w/ pixeleditor
export type PixelEditorImageFormat = {
w:number
h:number
count?:number
bpp?:number
np?:number
bpw?:number
sl?:number
pofs?:number
remap?:number[]
reindex?:number[]
brev?:boolean
flip?:boolean
destfmt?:PixelEditorImageFormat
xform?:string
skip?:number
aspect?:number
};
function remapBits(x:number, arr:number[]) : number {
if (!arr) return x;
var y = 0;
for (var i=0; i<arr.length; i++) {
var s = arr[i];
if (s < 0) {
s = -s-1;
y ^= 1 << s;
}
if (x & (1 << i)) {
y ^= 1 << s;
}
}
return y;
}
export function convertWordsToImages(words:ArrayLike<number>, fmt:PixelEditorImageFormat) : Uint8Array[] {
var width = fmt.w;
var height = fmt.h;
var count = fmt.count || 1;
var bpp = fmt.bpp || 1;
var nplanes = fmt.np || 1;
var bitsperword = fmt.bpw || 8;
var wordsperline = fmt.sl || Math.ceil(width * bpp / bitsperword);
var mask = (1 << bpp)-1;
var pofs = fmt.pofs || wordsperline*height*count;
var skip = fmt.skip || 0;
var images = [];
for (var n=0; n<count; n++) {
var imgdata = [];
for (var y=0; y<height; y++) {
var yp = fmt.flip ? height-1-y : y;
var ofs0 = n*wordsperline*height + yp*wordsperline;
var shift = 0;
for (var x=0; x<width; x++) {
var color = 0;
var ofs = remapBits(ofs0, fmt.remap);
// TODO: if (fmt.reindex) { [ofs, shift] = reindexMask(x, fmt.reindex); ofs += ofs0; }
for (var p=0; p<nplanes; p++) {
var byte = words[ofs + p*pofs + skip];
color |= ((fmt.brev ? byte>>(bitsperword-shift-bpp) : byte>>shift) & mask) << (p*bpp);
}
imgdata.push(color);
shift += bpp;
if (shift >= bitsperword && !fmt.reindex) {
ofs0 += 1;
shift = 0;
}
}
}
images.push(new Uint8Array(imgdata));
}
return images;
}

View File

@ -1,163 +0,0 @@
import _chroma from 'chroma-js'
import { isArray, rgb2bgr } from '../../util';
export type Chroma = { _rgb: [number,number,number,number] };
export type ColorSource = number | [number,number,number] | [number,number,number,number] | string | Chroma;
function checkCount(count) {
if (count < 0 || count > 65536) {
throw new Error("Palettes cannot have more than 2^16 (65536) colors.");
}
}
export function isPalette(object): object is Palette {
return object['colors'] instanceof Uint32Array;
}
export function isChroma(object): object is Chroma {
return object['_rgb'] instanceof Array;
}
export class Palette {
readonly colors: Uint32Array;
constructor(arg: number | any[] | Uint32Array) {
// TODO: more array types
if (typeof arg === 'number') {
checkCount(arg);
this.colors = new Uint32Array(arg);
} else if (arg instanceof Uint32Array) {
this.colors = new Uint32Array(arg);
} else if (isArray(arg)) {
this.colors = new Uint32Array(arg.map(rgb));
} else
throw new Error(`Invalid Palette constructor`)
}
get(index: number) {
return this.colors[index];
}
chromas() {
return Array.from(this.colors).map((rgba) => from(rgba & 0xffffff));
}
}
export const chroma = _chroma;
export function from(obj: ColorSource) {
if (typeof obj === 'number')
return _chroma(rgb2bgr(obj & 0xffffff));
else
return _chroma(obj as any);
}
export function rgb(obj: ColorSource) : number;
export function rgb(r: number, g: number, b: number) : number;
export function rgb(obj: any, g?: number, b?: number) : number {
return rgba(obj, g, b, 0xff) | 0xff000000;
}
export function rgba(obj: ColorSource) : number;
export function rgba(r: number, g: number, b: number, a: number) : number;
export function rgba(obj: ColorSource, g?: number, b?: number, a?: number) : number {
if (isChroma(obj)) {
return rgba(obj._rgb[0], obj._rgb[1], obj._rgb[2], obj._rgb[3]);
}
if (typeof obj === 'number') {
let r = obj;
if (typeof g === 'number' && typeof b === 'number')
return ((r & 0xff) << 0) | ((g & 0xff) << 8) | ((b & 0xff) << 16) | ((a & 0xff) << 24);
else
return obj;
}
if (typeof obj !== 'string' && isArray(obj) && typeof obj[0] === 'number') {
let arr = obj;
let v = 0;
v |= (arr[0] & 0xff) << 0;
v |= (arr[1] & 0xff) << 8;
v |= (arr[2] & 0xff) << 16;
v |= (arr[3] & 0xff) << 24;
return v;
}
return rgba(from(obj).rgb());
}
export function rgba2arr(v: number): number[] {
return [
(v >> 0) & 0xff,
(v >> 8) & 0xff,
(v >> 16) & 0xff,
(v >> 24) & 0xff,
]
}
export function rgb2arr(v: number): number[] {
return rgba2arr(v).slice(0,3);
}
type ColorGenFunc = (index: number) => number;
export namespace palette {
export function from(obj: number | any[] | Uint32Array | ColorGenFunc, count?: number) {
checkCount(count);
if (typeof obj === 'function') {
if (!count) throw new Error(`You must also pass the number of colors to generate.`)
var pal = new Palette(count);
for (var i = 0; i < pal.colors.length; i++) {
pal.colors[i] = rgba(obj(i));
}
return pal;
} else {
return new Palette(obj);
}
}
export function mono() {
return greys(2);
}
function rgb2() {
return new Palette([
rgb(0, 0, 0),
rgb(0, 0, 255),
rgb(255, 0, 0),
rgb(0, 255, 0),
]);
}
function rgb3() {
return new Palette([
rgb(0, 0, 0),
rgb(0, 0, 255),
rgb(255, 0, 0),
rgb(255, 0, 255),
rgb(0, 255, 0),
rgb(0, 255, 255),
rgb(255, 255, 0),
rgb(255, 255, 255),
]);
}
export function greys(count: number) {
return from((i) => {
let v = 255 * i / (count - 1);
return rgb(v,v,v);
}, count);
}
export function colors(count: number) {
switch (count) {
case 2: return mono();
case 4: return rgb2();
case 8: return rgb3();
default: return factors(count); // TODO
}
}
export function helix(count: number) {
checkCount(count);
return new Palette(chroma.cubehelix().scale().colors(count));
}
export function factors(count: number, mult?: number) {
mult = mult || 0x031f0f;
return from((i) => rgb(i * mult), count);
}
// TODO: https://www.iquilezles.org/www/articles/palettes/palettes.htm
}

View File

@ -1,178 +0,0 @@
import { FileDataCache } from "../../util";
import { FileData, WorkingStore } from "../../workertypes";
// remote resource cache
var $$cache = new FileDataCache(); // TODO: better cache?
// file read/write interface
var $$store: WorkingStore;
// backing store for data
var $$data: {} = {};
// module cache
var $$modules: Map<string,{}> = new Map();
export function $$setupFS(store: WorkingStore) {
$$store = store;
}
export function $$getData() {
return $$data;
}
export function $$loadData(data: {}) {
Object.assign($$data, data);
}
// object that can load state from backing store
export interface Loadable {
// called during script, from io.data.load()
$$setstate?(newstate: {}) : void;
// called after script, from io.data.save()
$$getstate() : {};
}
export namespace data {
export function load(object: Loadable, key: string): Loadable {
if (object == null) return object;
let override = $$data && $$data[key];
if (override && object.$$setstate) {
object.$$setstate(override);
} else if (override) {
Object.assign(object, override);
}
return object;
}
export function save(object: Loadable, key: string): Loadable {
if ($$data && object && object.$$getstate) {
$$data[key] = object.$$getstate();
}
return object;
}
export function get(key: string) {
return $$data && $$data[key];
}
export function set(key: string, value: object) {
if ($$data) {
$$data[key] = value;
}
}
}
export class IOWaitError extends Error {
}
export function canonicalurl(url: string) : string {
// get raw resource URL for github
if (url.startsWith('https://github.com/')) {
let toks = url.split('/');
if (toks[5] === 'blob') {
return `https://raw.githubusercontent.com/${toks[3]}/${toks[4]}/${toks.slice(6).join('/')}`
}
}
return url;
}
export function fetchurl(url: string, type?: 'binary' | 'text'): FileData {
// TODO: only works in web worker
var xhr = new XMLHttpRequest();
xhr.responseType = type === 'text' ? 'text' : 'arraybuffer';
xhr.open("GET", url, false); // synchronous request
xhr.send(null);
if (xhr.response != null && xhr.status == 200) {
if (type === 'text') {
return xhr.response as string;
} else {
return new Uint8Array(xhr.response);
}
} else {
throw new Error(`The resource at "${url}" responded with status code of ${xhr.status}.`)
}
}
export function readnocache(url: string, type?: 'binary' | 'text'): FileData {
if (url.startsWith('http:') || url.startsWith('https:')) {
return fetchurl(url, type);
}
if ($$store) {
return $$store.getFileData(url);
}
}
// TODO: read files too
export function read(url: string, type?: 'binary' | 'text'): FileData {
// canonical-ize url
url = canonicalurl(url);
// check cache first
let cachekey = url;
let data = $$cache.get(cachekey);
if (data != null) return data;
// not in cache, read it
data = readnocache(url, type);
if (data == null) throw new Error(`Cannot find resource "${url}"`);
if (type === 'text' && typeof data !== 'string') throw new Error(`Resource "${url}" is not a string`);
if (type === 'binary' && !(data instanceof Uint8Array)) throw new Error(`Resource "${url}" is not a binary file`);
// store in cache
$$cache.put(cachekey, data);
return data;
}
export function readbin(url: string): Uint8Array {
var data = read(url, 'binary');
if (data instanceof Uint8Array)
return data;
else
throw new Error(`The resource at "${url}" is not a binary file.`);
}
export function readlines(url: string) : string[] {
return (read(url, 'text') as string).split('\n');
}
export function splitlines(text: string) : string[] {
return text.split(/\n|\r\n/g);
}
export function module(url: string) {
// find module in cache?
let key = `${url}::${url.length}`;
let exports = $$modules.get(key);
if (exports == null) {
let code = readnocache(url, 'text') as string;
let func = new Function('exports', 'module', code);
let module = {}; // TODO?
exports = {};
func(exports, module);
$$modules.set(key, exports);
}
return exports;
}
///
// TODO: what if this isn't top level?
export class Mutable<T> implements Loadable {
value : T;
constructor(initial : T) {
this.value = initial;
}
$$setstate(newstate) {
this.value = newstate.value;
}
$$getstate() {
return { value: this.value };
}
}
export function mutable<T>(obj: object) : object {
Object.defineProperty(obj, '$$setstate', {
value: function(newstate) {
Object.assign(this, newstate);
},
enumerable: false
});
Object.defineProperty(obj, '$$getstate', {
value: function() {
return this;
},
enumerable: false
});
return obj;
}

View File

@ -1,85 +0,0 @@
enum DataType {
unknown,
u8,
s8,
u16,
s16,
u32,
s32,
f32,
f64,
};
function getArrayDataType(value: any) : DataType {
if (value instanceof Uint8Array) {
return DataType.u8;
} else if (value instanceof Int8Array) {
return DataType.s8;
} else if (value instanceof Uint16Array) {
return DataType.u16;
} else if (value instanceof Int16Array) {
return DataType.s16;
} else if (value instanceof Uint32Array) {
return DataType.u32;
} else if (value instanceof Int32Array) {
return DataType.s32;
} else if (value instanceof Float32Array) {
return DataType.f32;
} else if (value instanceof Float64Array) {
return DataType.f64;
}
}
export abstract class OutputFile {
constructor(
public readonly path : string,
public readonly decls : {}
) {
}
abstract declToText(label: string, value: any) : string;
toString() : string {
return Object.entries(this.decls).map(entry => this.declToText(entry[0],entry[1])).join('\n\n');
}
}
export class COutputFile extends OutputFile {
toString() : string {
return `#include <stdint.h>\n\n${super.toString()}\n`;
}
dataTypeToString(dtype: DataType) {
switch (dtype) {
case DataType.u8: return 'uint8_t';
case DataType.s8: return 'int8_t';
case DataType.u16: return 'uint16_t';
case DataType.s16: return 'int16_t';
case DataType.u32: return 'uint32_t';
case DataType.s32: return 'int32_t';
case DataType.f32: return 'float';
case DataType.f64: return 'double';
default:
throw new Error('Cannot convert data type'); // TODO
}
}
valueToString(value, atype: DataType) : string {
// TODO: round, check value
return value+"";
}
declToText(label: string, value: any) : string {
if (Array.isArray(value) || value['BYTES_PER_ELEMENT']) {
let atype = getArrayDataType(value);
if (atype != null) {
let dtypestr = this.dataTypeToString(atype);
let dtext = value.map(elem => this.valueToString(elem, atype)).join(',');
let len = value.length;
return `${dtypestr} ${label}[${len}] = { ${dtext} };`;
}
}
throw new Error(`Cannot convert array "${label}"`); // TODO
}
}
// TODO: header file, auto-detect tool?
export function file(path: string, decls: {}) {
return new COutputFile(path, decls);
}

View File

@ -1,193 +0,0 @@
import * as io from "./io";
// sequence counter
var $$seq : number = 0;
// if an event is specified, it goes here
export const EVENT_KEY = "$$event";
// an object that can become interactive, identified by ID
export interface Interactive {
$$interact: InteractionRecord;
}
export interface InteractEvent {
interactid : number;
type: string;
x?: number;
y?: number;
button?: boolean;
}
export type InteractCallback = (event: InteractEvent) => void;
// InteractionRecord maps a target object to an interaction ID
// the $$callback is used once per script eval, then gets nulled
// whether or not it's invoked
// event comes from $$data.$$event
export class InteractionRecord implements io.Loadable {
readonly interacttarget: Interactive;
interactid : number;
lastevent : {} = null;
constructor(
interacttarget: Interactive,
private $$callback: InteractCallback
) {
this.interacttarget = interacttarget || (<any>this as Interactive);
this.interactid = ++$$seq;
}
$$setstate(newstate: {interactid: number}) {
this.interactid = newstate.interactid;
this.interacttarget.$$interact = this;
let event : InteractEvent = io.data.get(EVENT_KEY);
if (event && event.interactid == this.interactid) {
if (this.$$callback) {
this.$$callback(event);
}
this.lastevent = event;
io.data.set(EVENT_KEY, null);
}
this.$$callback = null;
}
$$getstate() {
//TODO: this isn't always cleared before we serialize (e.g. if exception or move element)
//and we do it in checkResult() too
this.$$callback = null;
return {interactid: this.interactid};
}
}
export function isInteractive(obj: object): obj is Interactive {
return !!((obj as Interactive).$$interact);
}
export function interact(object: any, callback) : InteractionRecord {
// TODO: limit to Bitmap, etc
if (typeof object === 'object') {
return new InteractionRecord(object, callback);
}
throw new Error(`This object is not capable of interaction.`);
}
///
export interface ScriptUIType {
uitype : string;
}
export class ScriptUISliderType implements ScriptUIType {
readonly uitype = 'slider';
value: number;
constructor(
readonly min: number,
readonly max: number,
readonly step: number
) {
this.value = min;
}
}
export class ScriptUISlider extends ScriptUISliderType implements io.Loadable {
initial(value: number) {
this.value = value;
return this;
}
$$getstate() {
return { value: this.value };
}
}
export function slider(min: number, max: number, step?: number) {
return new ScriptUISlider(min, max, step || 1);
}
///
export class ScriptUISelectType<T> implements ScriptUIType {
readonly uitype = 'select';
value: T;
index: number;
constructor(
readonly options: T[]
) {
this.index = 0;
this.value = this.options[this.index];
}
}
export class ScriptUISelect<T> extends ScriptUISelectType<T> implements io.Loadable {
initial(index: number) {
this.index = index;
this.value = this.options[index];
return this;
}
$$getstate() {
return { value: this.value, index: this.index };
}
}
export function select(options: any[]) {
return new ScriptUISelect(options);
}
///
export class ScriptUIButtonType extends InteractionRecord implements ScriptUIType, Interactive {
readonly uitype = 'button';
$$interact: InteractionRecord;
enabled?: boolean;
constructor(
readonly label: string,
callback: InteractCallback
) {
super(null, callback);
this.$$interact = this;
}
}
export class ScriptUIButton extends ScriptUIButtonType {
}
export function button(name: string, callback: InteractCallback) {
return new ScriptUIButton(name, callback);
}
export class ScriptUIToggle extends ScriptUIButton implements io.Loadable {
// share with InteractionRecord
$$getstate() {
let state = super.$$getstate() as any;
state.enabled = this.enabled;
return state;
}
$$setstate(newstate: any) {
this.enabled = newstate.enabled;
super.$$setstate(newstate);
}
}
export function toggle(name: string) {
return new ScriptUIToggle(name, function(e) {
this.enabled = !this.enabled;
});
}
///
export class ScriptUIShortcut extends InteractionRecord implements ScriptUIType, Interactive {
readonly uitype = 'shortcut';
$$interact: InteractionRecord;
constructor(
readonly key: string,
callback: InteractCallback
) {
super(null, callback);
this.$$interact = this;
}
}
export function key(key: string, callback: InteractCallback) {
return new ScriptUIShortcut(key, callback);
}

View File

@ -1,479 +0,0 @@
import { Component, render, h, createRef, VNode } from 'preact';
import { Cell, PROP_CONSTRUCTOR_NAME } from "../env";
import { findIntegerFactors, hex, isArray, rgb2bgr } from "../../util";
import { dumpRAM } from "../../emu";
import { current_project } from "../../../ide/ui";
// TODO: can't call methods from this end (e.g. Palette, Bitmap)
import * as bitmap from "../lib/bitmap";
import * as color from "../lib/color";
import * as scriptui from "../lib/scriptui";
const MAX_STRING_LEN = 100;
const DEFAULT_ASPECT = 1;
function sendInteraction(iobj: scriptui.Interactive, type: string, event: Event, xtraprops: {}) {
let irec = iobj.$$interact;
let ievent : scriptui.InteractEvent = {interactid: irec.interactid, type, ...xtraprops};
if (event instanceof PointerEvent) {
const canvas = event.target as HTMLCanvasElement;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = (event.clientX - rect.left) * scaleX;
const y = (event.clientY - rect.top) * scaleY;
ievent.x = Math.floor(x);
ievent.y = Math.floor(y);
// TODO: pressure, etc.
} else {
console.log("Unknown event type", event);
}
// TODO: add events to queue?
current_project.updateDataItems([{
key: scriptui.EVENT_KEY,
value: ievent
}]);
}
interface ColorComponentProps {
rgbavalue: number;
}
class ColorComponent extends Component<ColorComponentProps> {
render(virtualDom, containerNode, replaceNode) {
let rgb = this.props.rgbavalue & 0xffffff;
let bgr = rgb2bgr(rgb);
var htmlcolor = `#${hex(bgr,6)}`;
var textcolor = (rgb & 0x008000) ? '#222' : '#ddd';
var printcolor = hex(rgb & 0xffffff, 6); // TODO: show index instead?
return h('div', {
class: 'scripting-item scripting-color',
style: `background-color: ${htmlcolor}; color: ${textcolor}`,
alt: htmlcolor, // TODO
}, [
//h('span', { }, printcolor )
]);
}
}
interface BitmapComponentProps {
bitmap: bitmap.BitmapType;
width: number;
height: number;
}
class BitmapComponent extends Component<BitmapComponentProps> {
ref = createRef(); // TODO: can we use the ref?
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
imageData: ImageData;
datau32: Uint32Array;
pressed = false;
constructor(props: BitmapComponentProps) {
super(props);
}
render(virtualDom, containerNode, replaceNode) {
let props = {
class: 'scripting-item',
ref: this.ref,
width: this.props.width,
height: this.props.height,
style: this.props.bitmap.style
}
let obj : any = this.props.bitmap;
if (scriptui.isInteractive(obj)) {
return h('canvas', {
onPointerMove: (e: PointerEvent) => {
sendInteraction(obj, 'move', e, { pressed: this.pressed });
},
onPointerDown: (e: PointerEvent) => {
this.pressed = true;
this.canvas.setPointerCapture(e.pointerId);
sendInteraction(obj, 'down', e, { pressed: true });
},
onPointerUp: (e: PointerEvent) => {
this.pressed = false;
sendInteraction(obj, 'up', e, { pressed: false });
},
onPointerOut: (e: PointerEvent) => {
this.pressed = false;
sendInteraction(obj, 'out', e, { pressed: false });
},
...props
});
} else {
return h('canvas', props);
}
}
componentDidMount() {
this.refresh();
}
componentWillUnmount() {
this.canvas = null;
this.imageData = null;
this.datau32 = null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
this.refresh();
}
prepare() {
this.canvas = this.base as HTMLCanvasElement;
this.ctx = this.canvas.getContext('2d');
this.imageData = this.ctx.createImageData(this.canvas.width, this.canvas.height);
this.datau32 = new Uint32Array(this.imageData.data.buffer);
}
refresh() {
// preact can reuse this component but it can change shape :^P
if (this.canvas !== this.base
|| this.imageData.width != this.props.width
|| this.imageData.height != this.props.height) {
this.prepare();
}
this.updateCanvas(this.datau32, this.props.bitmap);
this.ctx.putImageData(this.imageData, 0, 0);
}
updateCanvas(vdata: Uint32Array, bmp: bitmap.BitmapType) {
if (bmp['palette']) {
this.updateCanvasIndexed(vdata, bmp as bitmap.IndexedBitmap);
}
if (bmp['rgba']) {
this.updateCanvasRGBA(vdata, bmp as bitmap.RGBABitmap);
}
}
updateCanvasRGBA(vdata: Uint32Array, bmp: bitmap.RGBABitmap) {
vdata.set(bmp.rgba);
}
updateCanvasIndexed(vdata: Uint32Array, bmp: bitmap.IndexedBitmap) {
let pal = bmp.palette.colors;
for (var i = 0; i < bmp.pixels.length; i++) {
vdata[i] = pal[bmp.pixels[i]];
}
}
}
interface ObjectTreeComponentProps {
name?: string;
object: {} | [];
objpath: string;
}
interface ObjectTreeComponentState {
expanded: boolean;
}
class ObjectKeyValueComponent extends Component<ObjectTreeComponentProps, ObjectTreeComponentState> {
render(virtualDom, containerNode, replaceNode) {
let expandable = typeof this.props.object === 'object';
let hdrclass = '';
if (expandable) hdrclass = this.state.expanded ? 'tree-expanded' : 'tree-collapsed'
let propName = this.props.name || null;
return h('div', {
class: 'tree-content',
key: `${this.props.objpath}__tree`
}, [
h('div', {
class: 'tree-header ' + hdrclass,
onClick: expandable ? () => this.toggleExpand() : null
}, [
propName != null ? h('span', { class: 'tree-key' }, [ propName, expandable ]) : null,
h('span', { class: 'tree-value scripting-item' }, [
getShortName(this.props.object)
])
]),
this.state.expanded ? objectToContentsDiv(this.props.object, this.props.objpath) : null
]);
}
toggleExpand() {
this.setState({ expanded: !this.state.expanded });
}
}
function getShortName(object: any) {
if (typeof object === 'object') {
try {
var s = object[PROP_CONSTRUCTOR_NAME] || Object.getPrototypeOf(object).constructor.name;
if (object.length > 0) {
s += `[${object.length}]`
}
return s;
} catch (e) {
console.log(e);
return e + "";
}
} else {
return primitiveToString(object);
}
}
function primitiveToString(obj) {
var text = "";
// is it a function? call it first, if we are expanded
// TODO: only call functions w/ signature
if (obj && obj.$$ && typeof obj.$$ == 'function' && this._content != null) {
obj = obj.$$();
}
// check null first
if (obj == null) {
text = obj + "";
// primitive types
} else if (typeof obj == 'number') {
if (obj != (obj | 0)) text = obj.toString(); // must be a float
else text = obj + "\t($" + hex(obj) + ")";
} else if (typeof obj == 'string') {
text = obj;
} else {
text = JSON.stringify(obj);
}
if (text.length > MAX_STRING_LEN)
text = text.substring(0, MAX_STRING_LEN) + "...";
return text;
}
function isIndexedBitmap(object): object is bitmap.IndexedBitmap {
return object['bpp'] && object['pixels'] && object['palette'];
}
function isRGBABitmap(object): object is bitmap.RGBABitmap {
return object['rgba'] instanceof Uint32Array;
}
function objectToChildren(object: any) : any[] {
if (color.isPalette(object)) {
return new color.Palette(object.colors).chromas();
} else if (isArray(object)) {
return Array.from(object);
} else if (object != null) {
return [ object ]
} else {
return [ ]
}
}
function objectToChild(object: any, index: number) : any {
if (color.isPalette(object)) {
return color.from(object.colors[index]);
} else if (isArray(object)) {
return object[index];
} else if (object != null) {
return object
} else {
return null
}
}
function objectToDiv(object: any, name: string, objpath: string): VNode<any> {
// don't view any keys that start with "$"
if (name && name.startsWith("$")) {
// don't view any values that start with "$$"
if (name.startsWith("$$")) return;
// don't print if null or undefined
if (object == null) return;
// don't print key in any case
name = null;
}
// TODO: limit # of items
// TODO: detect table
if (object == null) {
return h('span', { }, object + "");
} else if (object['uitype']) {
let cons = UI_COMPONENTS[object['uitype']];
if (!cons) throw new Error(`Unknown UI component type: ${object['uitype']}`);
return h(cons, { iokey: objpath, uiobject: object });
} else if (object['literaltext']) {
return h("pre", { }, [ object['literaltext'] ]);
} else if (isIndexedBitmap(object) || isRGBABitmap(object)) {
return h(BitmapComponent, { bitmap: object, width: object.width, height: object.height });
} else if (color.isChroma(object)) {
return h(ColorComponent, { rgbavalue: color.rgb(object) });
} else if (color.isPalette(object)) {
// TODO?
if (object.colors.length <= 256) {
let children = [];
let props = { class: '', key: `${objpath}__obj` };
props.class += ' scripting-flex ';
object.colors.forEach((val) => {
children.push(h(ColorComponent, { rgbavalue: val }));
})
return h('div', props, children);
} else {
let {a,b} = findIntegerFactors(object.colors.length, 1, 1, DEFAULT_ASPECT);
return objectToDiv({ rgba: object.colors, width: a, height: b }, name, objpath);
}
} else {
return h(ObjectKeyValueComponent, { name, object, objpath }, []);
}
}
function fixedArrayToDiv(tyarr: Array<number>, bpel: number, objpath: string) {
const maxBytes = 0x100;
if (tyarr.length <= maxBytes) {
// TODO
let dumptext = dumpRAM(tyarr, 0, tyarr.length);
return h('pre', {}, dumptext);
} else {
let children = [];
for (var ofs = 0; ofs < tyarr.length; ofs += maxBytes) {
children.push(objectToDiv(tyarr.slice(ofs, ofs + maxBytes), '$' + hex(ofs), `${objpath}.${ofs}`));
}
return h('div', {}, children);
}
}
function objectToContentsDiv(object: {} | [], objpath: string) {
// is typed array?
let bpel = object['BYTES_PER_ELEMENT'];
if (typeof bpel === 'number') {
return fixedArrayToDiv(object as Array<number>, bpel, objpath);
}
let objectEntries = Object.entries(object);
let objectDivs = objectEntries.map(entry => objectToDiv(entry[1], entry[0], `${objpath}.${entry[1]}`));
return h('div', { class: 'scripting-flex' }, objectDivs);
}
///
interface UIComponentProps {
iokey: string;
uiobject: scriptui.ScriptUIType;
dropdown?: boolean;
}
class UISliderComponent extends Component<UIComponentProps> {
render(virtualDom, containerNode, replaceNode) {
let slider = this.props.uiobject as scriptui.ScriptUISliderType;
return h('div', {}, [
this.props.iokey,
h('input', {
type: 'range',
min: slider.min / slider.step,
max: slider.max / slider.step,
value: slider.value / slider.step,
onInput: (ev) => {
let target = ev.target as HTMLInputElement; // TODO
let newUIValue = { value: parseFloat(target.value) * slider.step };
this.setState(this.state);
current_project.updateDataItems([{key: this.props.iokey, value: newUIValue}]);
}
}),
h('span', { }, getShortName(slider.value)),
]);
}
}
class UISelectComponent extends Component<UIComponentProps, ObjectTreeComponentState> {
ref = createRef();
render(virtualDom, containerNode, replaceNode) {
let select = this.props.uiobject as scriptui.ScriptUISelectType<any>;
let children = objectToChildren(select.options);
this.props.dropdown = children.length > 16;
let showselections = !this.props.dropdown || this.state.expanded;
let seldiv = null;
if (showselections) {
seldiv = h('div', {
class: 'scripting-select scripting-flex',
ref: this.ref,
onClick: (e) => {
// select object -- iterate parents until we find select div, then find index of child
let target = e.target as HTMLElement;
while (target.parentElement && target.parentElement != this.ref.current) {
target = target.parentElement;
}
if (target.parentElement) {
const selindex = Array.from(target.parentElement.children).indexOf(target);
if (selindex >= 0 && selindex < children.length) {
let newUIValue = { value: children[selindex], index: selindex };
this.setState({ expanded: false });
current_project.updateDataItems([{key: this.props.iokey, value: newUIValue}]);
} else {
throw new Error(`Could not find click target of ${this.props.iokey}`);
}
}
}
},
children.map((child, index) => {
let div = objectToDiv(child, null, `${this.props.iokey}__select_${index}`);
let selected = (index == select.index);
return h('div', { class: selected ? 'scripting-selected' : '' }, [ div ]);
}));
}
if (this.props.dropdown) {
let selectedDiv = objectToDiv(objectToChild(select.options, select.index), null, `${this.props.iokey}__selected`);
return h('div', {
class: 'tree-content',
key: `${this.props.iokey}__tree`
}, [
h('div', {
class: 'tree-header ' + (this.state.expanded ? 'tree-expanded' : 'tree-collapsed'),
onClick: () => this.toggleExpand()
}, [
this.props.iokey,
h('span', { class: 'tree-value scripting-item' }, [
selectedDiv,
//getShortName(select.options)
])
]),
seldiv
]);
} else {
return seldiv;
}
}
toggleExpand() {
this.setState({ expanded: !this.state.expanded });
}
}
class UIButtonComponent extends Component<UIComponentProps> {
render(virtualDom, containerNode, replaceNode) {
let button = this.props.uiobject as scriptui.ScriptUIButtonType;
return h('button', {
class: button.enabled ? 'scripting-button scripting-enabled' : 'scripting-button',
onClick: (e: MouseEvent) => {
sendInteraction(button, 'click', e, { });
},
}, [
button.label
])
}
}
class UIShortcutComponent extends Component<UIComponentProps> {
render(virtualDom, containerNode, replaceNode) {
let shortcut = this.props.uiobject as scriptui.ScriptUIShortcut;
// TODO: needs to fire on container node
return h('div', {
onKeyDown: (e: KeyboardEvent) => {
sendInteraction(shortcut, 'key', e, { });
},
}, [ ])
}
}
const UI_COMPONENTS = {
'slider': UISliderComponent,
'select': UISelectComponent,
'button': UIButtonComponent,
'shortcut': UIShortcutComponent,
}
///
export class Notebook {
constructor(
public readonly maindoc: HTMLDocument,
public readonly maindiv: HTMLElement
) {
maindiv.classList.add('vertical-scroll');
}
updateCells(cells: Cell[]) {
let hTree = cells.map(cell => {
return h('div', {
class: 'scripting-cell',
key: `${cell.id}__cell`
}, [
objectToDiv(cell.object, cell.id, cell.id)
])
});
render(hTree, this.maindiv);
}
}

View File

@ -165,13 +165,7 @@ export class CodeProject {
parseIncludeDependencies(text:string):string[] {
let files = [];
let m;
if (this.platform_id.startsWith('script')) { // TODO
let re1 = /\b\w+[.]read\(["'](.+?)["']/gmi;
while (m = re1.exec(text)) {
if (m[1] && m[1].indexOf(':/') < 0) // TODO: ignore URLs
this.pushAllFiles(files, m[1]);
}
} else if (this.platform_id.startsWith('verilog')) {
if (this.platform_id.startsWith('verilog')) {
// include verilog includes
let re1 = /^\s*(`include|[.]include)\s+"(.+?)"/gmi;
while (m = re1.exec(text)) {

View File

@ -1,4 +1,3 @@
import { hex } from "chroma-js";
import { MasterAudio, WorkerSoundChannel } from "../common/audio";
import { MemoryBus } from "../common/baseplatform";
import { CPU6809 } from "../common/cpu/6809";

View File

@ -19,7 +19,6 @@ export function importPlatform(name: string) : Promise<any> {
case "mw8080bw": return import("../platform/mw8080bw");
case "nes": return import("../platform/nes");
case "pce": return import("../platform/pce");
case "script": return import("../platform/script");
case "sms": return import("../platform/sms");
case "sound_konami": return import("../platform/sound_konami");
case "sound_williams": return import("../platform/sound_williams");

View File

@ -1,46 +0,0 @@
import { PLATFORMS } from "../common/emu";
import { Platform } from "../common/baseplatform";
import { RunResult } from "../common/script/env";
import { Notebook } from "../common/script/ui/notebook";
class ScriptingPlatform implements Platform {
mainElement: HTMLElement;
iframe: HTMLIFrameElement;
notebook: Notebook;
constructor(mainElement: HTMLElement) {
this.mainElement = mainElement;
this.notebook = new Notebook(document, mainElement);
}
start() {
}
reset() {
}
pause() {
}
resume() {
}
loadROM(title, run: RunResult) {
this.notebook.updateCells(run.cells);
// TODO: save state file
}
isRunning() {
return true;
}
isDebugging(): boolean {
return false;
}
getToolForFilename(fn: string): string {
return "js";
}
getDefaultExtension(): string {
return ".js";
}
getPresets() {
return [
];
}
}
PLATFORMS['script'] = ScriptingPlatform;

View File

@ -1,46 +0,0 @@
import { BuildStep, BuildStepResult, emglobal, store } from "../workermain";
import { Environment, RunResult } from "../../common/script/env";
import * as io from "../../common/script/lib/io";
import { createNewPersistentStore } from "../../ide/project";
// cache environments
var environments: { [path: string]: Environment } = {};
function getEnv(path: string): Environment {
var env = environments[path];
if (!env) {
env = environments[path] = new Environment(emglobal, path);
// TODO: load environment from store?
}
return env;
}
export async function runJavascript(step: BuildStep): Promise<BuildStepResult> {
var env = getEnv(step.path);
var code = store.getFileAsString(step.path);
var lstore = createNewPersistentStore(step.platform + "//items");
// load items from persistent storage (TODO)
const itemskey = step.path;
if (store.items == null) {
store.items = (await lstore.getItem(itemskey)) || {}; // TODO
console.log(store.items);
}
io.$$setupFS(store);
io.$$loadData(store.items);
try {
await env.run(code);
let cells = env.render();
let state = env.commitLoadableState(); // TODO: doesn't work
let output : RunResult = { cells, state };
// save items to persistent storage (TODO)
lstore.setItem(itemskey, state); // TODO
store.items = state; // TODO: why????
return { output: output };
} catch (e) {
console.log(e);
return { errors: env.extractErrors(e) };
} finally {
io.$$setupFS(null);
}
}

View File

@ -1133,7 +1133,6 @@ import * as m6502 from './tools/m6502'
import * as z80 from './tools/z80'
import * as x86 from './tools/x86'
import * as arm from './tools/arm'
import * as script from './tools/script'
import * as ecs from './tools/ecs'
import * as remote from './tools/remote'
@ -1171,7 +1170,6 @@ var TOOLS = {
'wiz': misc.compileWiz,
'armips': arm.assembleARMIPS,
'vasmarm': arm.assembleVASMARM,
//'js': script.runJavascript,
'ecs': ecs.assembleECS,
'remote': remote.buildRemote
}