scripting: print(), css, palette layout, flex

make syncdev/prod: fixed mime type upload
This commit is contained in:
Steven Hugg 2021-08-18 15:44:13 -05:00
parent 9076ede5c1
commit 6cee4e26e4
12 changed files with 269 additions and 74 deletions

View File

@ -52,7 +52,7 @@ VERSION := $(shell git tag -l --points-at HEAD)
syncdev: distro
cp config.js $(TMP)
#aws --profile pzp s3 sync --follow-symlinks $(TMP)/ s3://8bitworkshop.com/dev/
s3cmd -c ~/.s3pzp sync -MFP $(TMP)/ s3://8bitworkshop.com/dev/
s3cmd -c ~/.s3pzp sync -MFP --no-mime-magic $(TMP)/ s3://8bitworkshop.com/dev/
rsync --stats -riltz --delete --chmod=a+rx -e "ssh" $(TMP)/ config.js $(RSYNC_PATH)/dev/
syncprod: distro
@ -63,5 +63,5 @@ syncprod: distro
read
cp config.js $(TMP)
#aws --profile pzp s3 sync --follow-symlinks $(TMP)/ s3://8bitworkshop.com/v$(VERSION)/
s3cmd -c ~/.s3pzp sync -MFP $(TMP)/ config.js s3://8bitworkshop.com/v$(VERSION)/
s3cmd -c ~/.s3pzp sync -MFP --no-mime-magic $(TMP)/ config.js s3://8bitworkshop.com/v$(VERSION)/
rsync --stats -riltz --chmod=a+rx -e "ssh" $(TMP)/ config.js $(RSYNC_PATH)/v$(VERSION)/

View File

@ -718,6 +718,7 @@ div.asset_toolbar {
padding-top: 0.5em;
}
.tree-header {
display: flex;
border: 2px solid #555;
border-radius:8px;
color: #fff;
@ -730,8 +731,11 @@ div.asset_toolbar {
padding-right:0.75em;
font-size: small;
}
.tree-key {
padding-right:1em;
}
.tree-value {
float:right;
margin-left:auto;
font-weight:normal;
padding-right:1em;
}
@ -779,24 +783,45 @@ div.asset_toolbar {
pointer-events: auto;
}
.scripting-cell canvas {
height: 20vw;
min-height: 20em;
max-width: 95%;
border: 2px solid #222;
outline-color: #ccc;
background: #000;
padding: 6px;
margin: 6px;
padding: 4px;
margin: 1px;
image-rendering: pixelated;
image-rendering: crisp-edges;
}
.scripting-cell canvas:hover {
outline:none;
border-color:#ccc;
}
.scripting-flex canvas {
min-height: 2vw;
max-height: 20vw;
}
div.scripting-editor {
width: 100%;
}
.scripting-editor canvas {
min-height: 30vw;
max-height: 50vw;
}
div.scripting-color {
padding:0.1em;
margin:0.1em;
padding: 0.5em;
}
div.scripting-color span {
visibility: hidden;
}
div.scripting-color:hover span {
visibility: visible;
}
div.scripting-grid {
display: grid;
grid-template-columns: repeat( auto-fit, minmax(2em, 1fr) );
grid-template-columns: repeat( auto-fill, minmax(4em, 1fr) );
justify-items: center;
}
}
div.scripting-flex {
display: flex;
flex-wrap: wrap;
}

View File

@ -77,7 +77,8 @@
"test-worker": "NODE_PATH=$(pwd) mocha --timeout 60000 test/cli/testworker.js",
"test-platforms": "NODE_PATH=$(pwd) mocha --timeout 60000 test/cli/testplatforms.js",
"test-verilog": "NODE_PATH=$(pwd) mocha --timeout 60000 --reporter mocha-simple-html-reporter --reporter-options output=test/output/verilog.html test/cli/testverilog.js",
"test-web": "nightwatch -e chrome test/web",
"test-web-quick": "nightwatch -e chrome test/web/testembed.js",
"test-web-all": "nightwatch -e chrome test/web",
"start": "electron .",
"fuzzbasic": "jsfuzz gen/common/basic/fuzz.js ~/basic/corpus/ --versifier false",
"fuzzhdl": "jsfuzz -r binaryen gen/common/hdl/fuzz.js ~/verilator/corpus/ --versifier false",

View File

@ -55,14 +55,19 @@ export class Environment {
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 IMPORTS) {
for (var impname in this.builtins) {
this.preamble += `var ${impname}=$$.${impname};`
}
this.preamble += '{\n';
@ -73,6 +78,11 @@ export class Environment {
console.log(varname, obj);
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;
@ -94,15 +104,20 @@ export class Environment {
}
};
const result = yufka(code, options, (node, { update, source, parent }) => {
function isTopLevel() {
const isTopLevel = () => {
return parent() && parent().type === 'ExpressionStatement' && parent(2) && parent(2).type === 'Program';
}
let left = node['left'];
const convertTopToPrint = () => {
if (isTopLevel()) update(`print(${source()});`)
}
const left = node['left'];
switch (node.type) {
// 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)
@ -118,10 +133,19 @@ export class Environment {
}
}
break;
// convert lone expressions to print()
case 'UnaryExpression':
case 'BinaryExpression':
case 'CallExpression':
case 'MemberExpression':
convertTopToPrint();
break;
// literal comments
case 'Literal':
if (isTopLevel() && typeof node['value'] === 'string') {
if (typeof node['value'] === 'string' && isTopLevel()) {
update(`this.$$doc__${this.seq++} = { literaltext: ${source()} };`);
} else {
convertTopToPrint();
}
break;
}
@ -133,7 +157,7 @@ export class Environment {
code = this.preprocess(code);
this.obj = {};
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
const fn = new AsyncFunction('$$', this.preamble + code + this.postamble).bind(this.obj, IMPORTS);
const fn = new AsyncFunction('$$', this.preamble + code + this.postamble).bind(this.obj, this.builtins);
await fn.call(this);
this.checkResult(this.obj, new Set(), []);
}

View File

@ -4,6 +4,7 @@ import * as fastpng from 'fast-png';
import { Palette } from './color';
import * as io from './io'
import * as color from './color'
import { findIntegerFactors, RGBA } from '../../util';
export type PixelMapFunction = (x: number, y: number) => number;
@ -19,6 +20,7 @@ export abstract class AbstractBitmap<T> {
abstract setarray(arr: ArrayLike<number>) : AbstractBitmap<T>;
abstract set(x: number, y: number, val: number) : AbstractBitmap<T>;
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);
@ -45,6 +47,17 @@ export abstract class AbstractBitmap<T> {
dest.assign((x, y) => this.get(x + srcx, y + srcy));
return dest;
}
blit(src: BitmapType, dest: BitmapType,
destx: number, desty: number,
srcx: number, srcy: number)
{
for (var y=0; y<src.height; y++) {
for (var x=0; x<src.width; x++) {
let rgba = src.getrgba(x+srcx, y+srcy);
dest.set(x+destx, y+desty, rgba);
}
}
}
}
export class RGBABitmap extends AbstractBitmap<RGBABitmap> {
@ -70,6 +83,9 @@ export class RGBABitmap extends AbstractBitmap<RGBABitmap> {
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, height);
}
@ -106,7 +122,6 @@ export abstract class MappedBitmap extends AbstractBitmap<MappedBitmap> {
get(x: number, y: number): number {
return this.inbounds(x,y) ? this.pixels[y * this.width + x] : 0;
}
abstract getRGBAForIndex(index: number): number;
}
export class IndexedBitmap extends MappedBitmap {
@ -122,8 +137,8 @@ export class IndexedBitmap extends MappedBitmap {
this.palette = color.palette.colors(1 << this.bitsPerPixel);
}
getRGBAForIndex(index: number): number {
return this.palette.colors[index];
getrgba(x: number, y: number): number {
return this.palette && this.palette.colors[this.get(x, y)];
}
blank(width: number, height: number) : IndexedBitmap {
let bitmap = new IndexedBitmap(width, height, this.bitsPerPixel);
@ -153,9 +168,64 @@ export function decode(arr: Uint8Array, fmt: PixelEditorImageFormat) {
// TODO: guess if missing w/h/count?
// TODO: reverse mapping
// TODO: maybe better composable functions
return pixels.map(data => new IndexedBitmap(fmt.w, fmt.h, fmt.bpp | 1, data));
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[]) {
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) {
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, result, 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));
@ -325,6 +395,22 @@ export type PixelEditorImageFormat = {
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;
@ -346,7 +432,7 @@ export type PixelEditorImageFormat = {
var shift = 0;
for (var x=0; x<width; x++) {
var color = 0;
var ofs = ofs0; // TODO: remapBits(ofs0, fmt.remap);
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];

View File

@ -4,12 +4,19 @@ import { isArray } from '../../util';
export type ColorSource = number | [number,number,number] | [number,number,number,number] | string;
function checkCount(count) {
if (count < 0 || count > 65536) {
throw new Error("Palettes cannot have more than 2^16 (65536) colors.");
}
}
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);
@ -42,9 +49,10 @@ 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 (typeof obj === 'number') {
let r = obj;
if (g != null && b != null)
if (typeof g === 'number' && typeof b === 'number')
return ((r & 0xff) << 0) | ((g & 0xff) << 8) | ((b & 0xff) << 16) | ((a & 0xff) << 24);
return obj;
else
return obj;
}
if (typeof obj !== 'string' && isArray(obj) && typeof obj[0] === 'number') {
let arr = obj;
@ -75,6 +83,7 @@ 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);
@ -87,7 +96,7 @@ export namespace palette {
}
}
export function mono() {
return greys(1);
return greys(2);
}
function rgb2() {
return new Palette([
@ -124,6 +133,7 @@ export namespace palette {
}
}
export function helix(count: number) {
checkCount(count);
return new Palette(chroma.cubehelix().scale().colors(count));
}
export function factors(count: number, mult?: number) {

View File

@ -76,7 +76,7 @@ export function fetchurl(url: string, type?: 'binary' | 'text'): FileData {
export function readnocache(url: string, type?: 'binary' | 'text'): FileData {
if (url.startsWith('http:') || url.startsWith('https:')) {
return fetchurl(url);
return fetchurl(url, type);
}
if ($$store) {
return $$store.getFileData(url);
@ -93,6 +93,8 @@ export function read(url: string, type?: 'binary' | 'text'): FileData {
}
let 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`);
$$cache.set(cachekey, data);
return data;
}

View File

@ -6,7 +6,8 @@ export class ScriptUISliderType {
value: number;
constructor(
readonly min: number,
readonly max: number
readonly max: number,
readonly step: number
) {
}
}
@ -25,6 +26,6 @@ export class ScriptUISlider extends ScriptUISliderType implements io.Loadable {
}
}
export function slider(min: number, max: number) {
return new ScriptUISlider(min, max);
export function slider(min: number, max: number, step?: number) {
return new ScriptUISlider(min, max, step || 1);
}

View File

@ -2,7 +2,7 @@
import { BitmapType, IndexedBitmap, RGBABitmap } from "../lib/bitmap";
import { Component, render, h, ComponentType } from 'preact';
import { Cell } from "../env";
import { hex, rgb2bgr, RGBA } from "../../util";
import { findIntegerFactors, hex, rgb2bgr, RGBA } from "../../util";
import { dumpRAM } from "../../emu";
// TODO: can't call methods from this end
import { Palette } from "../lib/color";
@ -10,6 +10,30 @@ import { ScriptUISlider, ScriptUISliderType } from "../lib/scriptui";
import { current_project } from "../../../ide/ui";
const MAX_STRING_LEN = 100;
const DEFAULT_ASPECT = 1;
interface ObjectStats {
type: 'prim' | 'complex' | 'bitmap'
width: number
height: number
units: 'em' | 'px'
}
// TODO
class ObjectAnalyzer {
recurse(obj: any) : ObjectStats {
if (typeof obj === 'string') {
return { type: 'prim', width: obj.length, height: 1, units: 'em' }
} else if (obj instanceof Uint8Array) {
return { type: 'complex', width: 60, height: Math.ceil(obj.length / 16), units: 'em' }
} else if (typeof obj === 'object') {
let stats : ObjectStats = { type: 'complex', width: 0, height: 0, units: 'em'};
return stats; // TODO
} else {
return { type: 'prim', width: 12, height: 1, units: 'em' }
}
}
}
interface ColorComponentProps {
rgbavalue: number;
@ -18,13 +42,17 @@ interface ColorComponentProps {
class ColorComponent extends Component<ColorComponentProps> {
render(virtualDom, containerNode, replaceNode) {
let rgb = this.props.rgbavalue & 0xffffff;
var htmlcolor = `#${hex(rgb2bgr(rgb),6)}`;
var textcol = (rgb & 0x008000) ? 'black' : 'white';
let bgr = rgb2bgr(rgb);
var htmlcolor = `#${hex(bgr,6)}`;
var textcolor = (rgb & 0x008000) ? 'black' : 'white';
var printcolor = hex(rgb & 0xffffff, 6); // TODO: show index instead?
return h('div', {
class: 'scripting-color',
style: `background-color: ${htmlcolor}; color: ${textcol}`,
style: `background-color: ${htmlcolor}; color: ${textcolor}`,
alt: htmlcolor, // TODO
}, '\u00a0');
}, [
h('span', { }, printcolor )
]);
}
}
@ -45,7 +73,6 @@ class BitmapComponent extends Component<BitmapComponentProps> {
}
render(virtualDom, containerNode, replaceNode) {
return h('canvas', {
class: 'pixelated',
width: this.props.width,
height: this.props.height,
...this.props
@ -124,7 +151,7 @@ class BitmapEditor extends Component<BitmapComponentProps, BitmapEditorState> {
}
return h('div', {
tabIndex: 0,
class: this.state.isEditing ? 'scripting-cell' : '' // TODO
class: this.state.isEditing ? 'scripting-editor' : '' // TODO
}, [
bitmapRender,
okCancel,
@ -163,6 +190,8 @@ class ObjectKeyValueComponent extends Component<ObjectTreeComponentProps, Object
let hdrclass = '';
if (expandable)
hdrclass = this.state.expanded ? 'tree-expanded' : 'tree-collapsed'
let propName = this.props.name || null;
if (propName.startsWith("$$")) propName = null;
return h('div', {
class: 'tree-content',
key: `${this.props.objpath}__tree`
@ -171,10 +200,8 @@ class ObjectKeyValueComponent extends Component<ObjectTreeComponentProps, Object
class: 'tree-header ' + hdrclass,
onClick: expandable ? () => this.toggleExpand() : null
}, [
this.props.name + "",
h('span', { class: 'tree-value' }, [
getShortName(this.props.object)
])
h('span', { class: 'tree-key' }, [ propName, expandable ]),
h('span', { class: 'tree-value' }, [ getShortName(this.props.object) ])
]),
this.state.expanded ? objectToContentsDiv(this.props.object, this.props.objpath) : null
]);
@ -243,26 +270,25 @@ function objectToDiv(object: any, name: string, objpath: string) {
if (object == null) {
return object + "";
} else if (object['uitype']) {
children.push(h(UISliderComponent, { iokey: objpath, uiobject: object }));
return h(UISliderComponent, { iokey: objpath, uiobject: object });
} else if (object['literaltext']) {
children.push(h("pre", { }, [ object['literaltext'] ])); // TODO
} else if (isIndexedBitmap(object)) {
//Object.setPrototypeOf(object, IndexedBitmap.prototype); // TODO: use Object.create()?
addBitmapComponent(children, object);
} else if (isRGBABitmap(object)) {
//Object.setPrototypeOf(object, RGBABitmap.prototype); // TODO: use Object.create()?
addBitmapComponent(children, object);
return h("pre", { }, [ object['literaltext'] ]);
} else if (isIndexedBitmap(object) || isRGBABitmap(object)) {
return h(BitmapEditor, { bitmap: object, width: object.width, height: object.height });
} else if (isPalette(object)) {
// TODO: make sets of 2/4/8/16/etc
props.class += ' scripting-grid ';
object.colors.forEach((val) => {
children.push(h(ColorComponent, { rgbavalue: val }));
})
if (object.colors.length <= 64) {
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 }, []);
}
let div = h('div', props, children);
return div;
}
function fixedArrayToDiv(tyarr: Array<number>, bpel: number, objpath: string) {
@ -288,11 +314,7 @@ function objectToContentsDiv(object: {} | [], objpath: string) {
}
let objectEntries = Object.entries(object);
let objectDivs = objectEntries.map(entry => objectToDiv(entry[1], entry[0], `${objpath}.${entry[1]}`));
return h('div', {}, objectDivs);
}
function addBitmapComponent(children, bitmap: BitmapType) {
children.push(h(BitmapEditor, { bitmap: bitmap, width: bitmap.width, height: bitmap.height }));
return h('div', { class: 'scripting-flex' }, objectDivs);
}
interface UISliderComponentProps {
@ -307,17 +329,16 @@ class UISliderComponent extends Component<UISliderComponentProps> {
this.props.iokey,
h('input', {
type: 'range',
min: slider.min,
max: slider.max,
value: this.props.uiobject.value,
min: slider.min / slider.step,
max: slider.max / slider.step,
value: this.props.uiobject.value / slider.step,
onInput: (ev) => {
let newValue = { value: ev.target.value };
slider.value = parseFloat(ev.target.value);
let newValue = { value: parseFloat(ev.target.value) * slider.step };
this.setState(this.state);
current_project.updateDataItems([{key: this.props.iokey, value: newValue}]);
}
}, []),
slider.value
getShortName(slider.value)
]);
}
}
@ -328,22 +349,15 @@ export class Notebook {
public readonly maindiv: HTMLElement
) {
maindiv.classList.add('vertical-scroll');
//maindiv.classList.add('container')
}
updateCells(cells: Cell[]) {
let hTree = cells.map(cell => {
//return objectToDiv(cell.object, cell.id)
return h('div', {
class: 'scripting-cell',
key: `${cell.id}__cell`
}, [
objectToDiv(cell.object, cell.id, cell.id)
])
/*
let cellDiv = objectToDiv(cell.object, cell.id);
cellDiv.props['class'] += ' scripting-cell ';
return cellDiv;
*/
});
render(hTree, this.maindiv);
}

View File

@ -631,3 +631,34 @@ export function escapeHTML(s: string): string {
return s.replace(/[&]/g, '&amp;').replace(/[<]/g, '&lt;').replace(/[>]/g, '&gt;');
}
// lame factorization for displaying bitmaps
// returns a > b such that a * b == x (or higher), a >= mina, b >= minb
export function findIntegerFactors(x: number, mina: number, minb: number, aspect: number) : {a: number, b: number} {
let a = x;
let b = 1;
if (minb > 1 && minb < a) {
a = Math.ceil(x / minb);
b = minb;
}
while (a > b) {
let a2 = a;
let b2 = b;
if ((a & 1) == 0) {
b2 = b * 2;
a2 = a / 2;
}
if ((a % 3) == 0) {
b2 = b * 3;
a2 = a / 3;
}
if ((a % 5) == 0) {
b2 = b * 5;
a2 = a / 5;
}
if (a2 < mina) break;
if (a2 < b2 * aspect) break;
a = a2;
b = b2;
}
return {a, b};
}

View File

@ -70,7 +70,7 @@ class VCSPlatform extends BasePlatform {
async start() {
var self : VCSPlatform = this;
// load Javatari and configure settings
await loadScript("javatari.js/release/javatari/javatari.js");
await loadScript("javatari/javatari.js");
Javatari.AUTO_START = false;
Javatari.SHOW_ERRORS = false;
Javatari.CARTRIDGE_CHANGE_DISABLED = true;

View File

@ -27,6 +27,7 @@ export async function runJavascript(step: BuildStep): Promise<BuildStepResult> {
let output : RunResult = { cells, state };
return { output: output };
} catch (e) {
console.log(e);
return { errors: env.extractErrors(e) };
} finally {
io.$$setupFS(null);