diff --git a/Cargo.toml b/Cargo.toml
index 9c17cb7..8a28b5a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,6 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
chumsky = "0.10.1"
imbl = "3.0.0"
-ran = "2.0.1"
num-derive = "0.4.2"
num-traits = "0.2.19"
regex = "1.11.1"
@@ -19,5 +18,4 @@ wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4.50"
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
-tokio = {version = "1.45.1", features = ["macros", "rt-multi-thread"]}
-
+console_error_panic_hook = "0.1.7"
diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld
index 71b29a9..e59380c 100644
--- a/assets/test_prelude.ld
+++ b/assets/test_prelude.ld
@@ -1,3 +1,7 @@
+&&& buffers: shared memory with Rust
+box console = []
+box input = ""
+
& the very base: know something's type
fn type {
"Returns a keyword representing the type of the value passed in."
@@ -408,8 +412,6 @@ fn to_number {
(num as :string) -> base :number (num)
}
-box console = []
-
fn print! {
"Sends a text representation of Ludus values to the console."
(...args) -> {
@@ -1269,6 +1271,9 @@ fn sleep! {
link!
+ console
+ input
+
abs
abs
add
diff --git a/justfile b/justfile
index daf09c8..52fd6b2 100644
--- a/justfile
+++ b/justfile
@@ -1,11 +1,23 @@
-wasm:
+wasm: && clean-wasm-pack
+ # build with wasm-pack
wasm-pack build --target web
- rm pkg/.gitignore
- cp pkg/rudus.js pkg/rudus.js.backup
- echo 'import {io} from "../worker.js"' > rudus.js
- cat rudus.js.backup | tail -n+2>> rudus.js
- rm rudus.js.backup
+
+wasm-dev: && clean-wasm-pack
+ wasm-pack build --dev --target web
+
+clean-wasm-pack:
+ # delete cruft from wasm-pack
+ rm pkg/.gitignore pkg/package.json pkg/README.md
rm -rf pkg/snippets
+ # fix imports of rudus.js
+ cp pkg/rudus.js pkg/rudus.js.backup
+ echo 'import { io } from "./worker.js"' > pkg/rudus.js
+ cat pkg/rudus.js.backup | tail -n+2>> pkg/rudus.js
+ rm pkg/rudus.js.backup
+
+
+serve:
+ miniserve pkg && open http://localhost:8080/index.html
default:
@just --list
diff --git a/pkg/.gitignore b/pkg/.gitignore
deleted file mode 100644
index e69de29..0000000
diff --git a/pkg/index.html b/pkg/index.html
index 74f29a2..a79fb90 100644
--- a/pkg/index.html
+++ b/pkg/index.html
@@ -6,13 +6,7 @@
-
+
Open the console. All the action's in there.
diff --git a/pkg/ludus.js b/pkg/ludus.js
index b26f229..e964c7d 100644
--- a/pkg/ludus.js
+++ b/pkg/ludus.js
@@ -1,18 +1,72 @@
-import init, {ludus} from "./rudus.js";
+if (window) window.ludus = {run, kill, flush_console, p5, svg, turtle_commands, result, input}
+const worker = new Worker("worker.js", {type: "module"})
-let res = null
+let outbox = []
-let code = null
-
-export function run (source) {
- code = source
- const output = ludus(source)
- res = JSON.parse(output)
- return res
+worker.onmessage = async (e) => {
+ let msgs
+ try {
+ msgs = JSON.parse(e.data)
+ } catch {
+ console.log(e.data)
+ throw Error("bad json from Ludus")
+ }
+ for (const msg of msgs) {
+ console.log("Main: message received from worker:", msg);
+ switch (msg.verb) {
+ case "complete": {
+ console.log("completed ludus run!")
+ console.log("with", msg.data)
+ res = msg.data
+ running = false
+ break
+ }
+ case "console": {
+ console.log("console msg from msg.data")
+ console.log(msg.data)
+ break
+ }
+ }
+ }
}
-export function stdout () {
+let res = null
+let code = null
+let running = false
+let io_interval_id = null
+
+const io_poller = () => {
+ if (io_interval_id && !running) {
+ clearInterval(io_interval_id)
+ return
+ }
+ worker.postMessage(outbox)
+ outbox = []
+}
+
+function poll_io () {
+ io_interval_id = setInterval(io_poller, 10)
+}
+
+export function run (source) {
+ if (running) "TODO: handle this? should not be running"
+ running = true
+ code = source
+ outbox.push({verb: "run", data: source})
+ poll_io()
+}
+
+export function kill () {
+ running = false
+ outbox.push({verb: "kill"})
+}
+
+export function input (text) {
+ outbox.push({verb: "input", data: text})
+}
+
+export function flush_console () {
if (!res) return ""
return res.io.stdout.data
}
@@ -375,25 +429,4 @@ export function p5 (commands) {
return p5_calls
}
-window.ludus = {run, console, p5, svg, stdout, turtle_commands, result}
-await init()
-
-const worker = new Worker("worker.js", {type: "module"})
-
-let outbox = {}
-
-setInterval(() => {
- worker.postMessage(outbox)
- outbox = {}
-})
-
-worker.onmessage = async (msgs) => {
- for (const msg of msgs) {
- switch (msg[0]) {
- case "stdout": {
- stdout = msg[1]
- }
- }
- }
-}
diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts
index fc664bf..12ab49a 100644
--- a/pkg/rudus.d.ts
+++ b/pkg/rudus.d.ts
@@ -1,16 +1,21 @@
/* tslint:disable */
/* eslint-disable */
-export function ludus(src: string): string;
+export function ludus(src: string): Promise;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
- readonly ludus: (a: number, b: number) => [number, number];
- readonly __wbindgen_export_0: WebAssembly.Table;
+ readonly ludus: (a: number, b: number) => any;
+ readonly __wbindgen_exn_store: (a: number) => void;
+ readonly __externref_table_alloc: () => number;
+ readonly __wbindgen_export_2: WebAssembly.Table;
+ readonly __wbindgen_free: (a: number, b: number, c: number) => void;
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
- readonly __wbindgen_free: (a: number, b: number, c: number) => void;
+ readonly __wbindgen_export_6: WebAssembly.Table;
+ readonly closure347_externref_shim: (a: number, b: number, c: any) => void;
+ readonly closure371_externref_shim: (a: number, b: number, c: any, d: any) => void;
readonly __wbindgen_start: () => void;
}
diff --git a/pkg/rudus.js b/pkg/rudus.js
index 52f2363..ccdb85c 100644
--- a/pkg/rudus.js
+++ b/pkg/rudus.js
@@ -1,6 +1,41 @@
+import { io } from "./worker.js"
+
let wasm;
-let WASM_VECTOR_LEN = 0;
+function addToExternrefTable0(obj) {
+ const idx = wasm.__externref_table_alloc();
+ wasm.__wbindgen_export_2.set(idx, obj);
+ return idx;
+}
+
+function handleError(f, args) {
+ try {
+ return f.apply(this, args);
+ } catch (e) {
+ const idx = addToExternrefTable0(e);
+ wasm.__wbindgen_exn_store(idx);
+ }
+}
+
+function logError(f, args) {
+ try {
+ return f.apply(this, args);
+ } catch (e) {
+ let error = (function () {
+ try {
+ return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString();
+ } catch(_) {
+ return "";
+ }
+ }());
+ console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error);
+ throw e;
+ }
+}
+
+const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
+
+if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
let cachedUint8ArrayMemory0 = null;
@@ -11,6 +46,13 @@ function getUint8ArrayMemory0() {
return cachedUint8ArrayMemory0;
}
+function getStringFromWasm0(ptr, len) {
+ ptr = ptr >>> 0;
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
+}
+
+let WASM_VECTOR_LEN = 0;
+
const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
@@ -28,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
function passStringToWasm0(arg, malloc, realloc) {
+ if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`);
+
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
@@ -56,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) {
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
-
+ if (ret.read !== arg.length) throw new Error('failed to pass whole string');
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
@@ -65,31 +109,144 @@ function passStringToWasm0(arg, malloc, realloc) {
return ptr;
}
-const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
+let cachedDataViewMemory0 = null;
-if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
+function getDataViewMemory0() {
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
+ }
+ return cachedDataViewMemory0;
+}
-function getStringFromWasm0(ptr, len) {
- ptr = ptr >>> 0;
- return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
+function isLikeNone(x) {
+ return x === undefined || x === null;
+}
+
+function _assertBoolean(n) {
+ if (typeof(n) !== 'boolean') {
+ throw new Error(`expected a boolean argument, found ${typeof(n)}`);
+ }
+}
+
+const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
+ ? { register: () => {}, unregister: () => {} }
+ : new FinalizationRegistry(state => {
+ wasm.__wbindgen_export_6.get(state.dtor)(state.a, state.b)
+});
+
+function makeMutClosure(arg0, arg1, dtor, f) {
+ const state = { a: arg0, b: arg1, cnt: 1, dtor };
+ const real = (...args) => {
+ // First up with a closure we increment the internal reference
+ // count. This ensures that the Rust closure environment won't
+ // be deallocated while we're invoking it.
+ state.cnt++;
+ const a = state.a;
+ state.a = 0;
+ try {
+ return f(a, state.b, ...args);
+ } finally {
+ if (--state.cnt === 0) {
+ wasm.__wbindgen_export_6.get(state.dtor)(a, state.b);
+ CLOSURE_DTORS.unregister(state);
+ } else {
+ state.a = a;
+ }
+ }
+ };
+ real.original = state;
+ CLOSURE_DTORS.register(real, state, state);
+ return real;
+}
+
+function debugString(val) {
+ // primitive types
+ const type = typeof val;
+ if (type == 'number' || type == 'boolean' || val == null) {
+ return `${val}`;
+ }
+ if (type == 'string') {
+ return `"${val}"`;
+ }
+ if (type == 'symbol') {
+ const description = val.description;
+ if (description == null) {
+ return 'Symbol';
+ } else {
+ return `Symbol(${description})`;
+ }
+ }
+ if (type == 'function') {
+ const name = val.name;
+ if (typeof name == 'string' && name.length > 0) {
+ return `Function(${name})`;
+ } else {
+ return 'Function';
+ }
+ }
+ // objects
+ if (Array.isArray(val)) {
+ const length = val.length;
+ let debug = '[';
+ if (length > 0) {
+ debug += debugString(val[0]);
+ }
+ for(let i = 1; i < length; i++) {
+ debug += ', ' + debugString(val[i]);
+ }
+ debug += ']';
+ return debug;
+ }
+ // Test for built-in
+ const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
+ let className;
+ if (builtInMatches && builtInMatches.length > 1) {
+ className = builtInMatches[1];
+ } else {
+ // Failed to match the standard '[object ClassName]'
+ return toString.call(val);
+ }
+ if (className == 'Object') {
+ // we're a user defined class or Object
+ // JSON.stringify avoids problems with cycles, and is generally much
+ // easier than looping through ownProperties of `val`.
+ try {
+ return 'Object(' + JSON.stringify(val) + ')';
+ } catch (_) {
+ return 'Object';
+ }
+ }
+ // errors
+ if (val instanceof Error) {
+ return `${val.name}: ${val.message}\n${val.stack}`;
+ }
+ // TODO we could test for more things here, like `Set`s and `Map`s.
+ return className;
}
/**
* @param {string} src
- * @returns {string}
+ * @returns {Promise}
*/
export function ludus(src) {
- let deferred2_0;
- let deferred2_1;
- try {
- const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- const len0 = WASM_VECTOR_LEN;
- const ret = wasm.ludus(ptr0, len0);
- deferred2_0 = ret[0];
- deferred2_1 = ret[1];
- return getStringFromWasm0(ret[0], ret[1]);
- } finally {
- wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
- }
+ const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ const ret = wasm.ludus(ptr0, len0);
+ return ret;
+}
+
+function _assertNum(n) {
+ if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`);
+}
+function __wbg_adapter_22(arg0, arg1, arg2) {
+ _assertNum(arg0);
+ _assertNum(arg1);
+ wasm.closure347_externref_shim(arg0, arg1, arg2);
+}
+
+function __wbg_adapter_50(arg0, arg1, arg2, arg3) {
+ _assertNum(arg0);
+ _assertNum(arg1);
+ wasm.closure371_externref_shim(arg0, arg1, arg2, arg3);
}
async function __wbg_load(module, imports) {
@@ -126,8 +283,150 @@ async function __wbg_load(module, imports) {
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
+ imports.wbg.__wbg_call_672a4d21634d4a24 = function() { return handleError(function (arg0, arg1) {
+ const ret = arg0.call(arg1);
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_call_7cccdd69e0791ae2 = function() { return handleError(function (arg0, arg1, arg2) {
+ const ret = arg0.call(arg1, arg2);
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) {
+ let deferred0_0;
+ let deferred0_1;
+ try {
+ deferred0_0 = arg0;
+ deferred0_1 = arg1;
+ console.error(getStringFromWasm0(arg0, arg1));
+ } finally {
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
+ }
+ }, arguments) };
+ imports.wbg.__wbg_io_4b41f8089de924df = function() { return logError(function (arg0, arg1) {
+ let deferred0_0;
+ let deferred0_1;
+ try {
+ deferred0_0 = arg0;
+ deferred0_1 = arg1;
+ const ret = io(getStringFromWasm0(arg0, arg1));
+ return ret;
+ } finally {
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
+ }
+ }, arguments) };
+ imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) {
+ let deferred0_0;
+ let deferred0_1;
+ try {
+ deferred0_0 = arg0;
+ deferred0_1 = arg1;
+ console.log(getStringFromWasm0(arg0, arg1));
+ } finally {
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
+ }
+ }, arguments) };
+ imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) {
+ console.log(getStringFromWasm0(arg0, arg1));
+ }, arguments) };
+ imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) {
+ try {
+ var state0 = {a: arg0, b: arg1};
+ var cb0 = (arg0, arg1) => {
+ const a = state0.a;
+ state0.a = 0;
+ try {
+ return __wbg_adapter_50(a, state0.b, arg0, arg1);
+ } finally {
+ state0.a = a;
+ }
+ };
+ const ret = new Promise(cb0);
+ return ret;
+ } finally {
+ state0.a = state0.b = 0;
+ }
+ }, arguments) };
+ imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () {
+ const ret = new Error();
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) {
+ const ret = new Function(getStringFromWasm0(arg0, arg1));
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () {
+ const ret = Date.now();
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) {
+ queueMicrotask(arg0);
+ }, arguments) };
+ imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) {
+ const ret = arg0.queueMicrotask;
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () {
+ const ret = Math.random();
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) {
+ const ret = Promise.resolve(arg0);
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) {
+ const ret = arg1.stack;
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len1 = WASM_VECTOR_LEN;
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
+ }, arguments) };
+ imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () {
+ const ret = typeof global === 'undefined' ? null : global;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ }, arguments) };
+ imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () {
+ const ret = typeof globalThis === 'undefined' ? null : globalThis;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ }, arguments) };
+ imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () {
+ const ret = typeof self === 'undefined' ? null : self;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ }, arguments) };
+ imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () {
+ const ret = typeof window === 'undefined' ? null : window;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ }, arguments) };
+ imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) {
+ const ret = arg0.then(arg1);
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) {
+ const ret = arg0.then(arg1, arg2);
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbindgen_cb_drop = function(arg0) {
+ const obj = arg0.original;
+ if (obj.cnt-- == 1) {
+ obj.a = 0;
+ return true;
+ }
+ const ret = false;
+ _assertBoolean(ret);
+ return ret;
+ };
+ imports.wbg.__wbindgen_closure_wrapper7663 = function() { return logError(function (arg0, arg1, arg2) {
+ const ret = makeMutClosure(arg0, arg1, 348, __wbg_adapter_22);
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
+ const ret = debugString(arg1);
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len1 = WASM_VECTOR_LEN;
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
+ };
imports.wbg.__wbindgen_init_externref_table = function() {
- const table = wasm.__wbindgen_export_0;
+ const table = wasm.__wbindgen_export_2;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
@@ -136,6 +435,31 @@ function __wbg_get_imports() {
table.set(offset + 3, false);
;
};
+ imports.wbg.__wbindgen_is_function = function(arg0) {
+ const ret = typeof(arg0) === 'function';
+ _assertBoolean(ret);
+ return ret;
+ };
+ imports.wbg.__wbindgen_is_undefined = function(arg0) {
+ const ret = arg0 === undefined;
+ _assertBoolean(ret);
+ return ret;
+ };
+ imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
+ const obj = arg1;
+ const ret = typeof(obj) === 'string' ? obj : undefined;
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ var len1 = WASM_VECTOR_LEN;
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
+ };
+ imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
+ const ret = getStringFromWasm0(arg0, arg1);
+ return ret;
+ };
+ imports.wbg.__wbindgen_throw = function(arg0, arg1) {
+ throw new Error(getStringFromWasm0(arg0, arg1));
+ };
return imports;
}
@@ -147,6 +471,7 @@ function __wbg_init_memory(imports, memory) {
function __wbg_finalize_init(instance, module) {
wasm = instance.exports;
__wbg_init.__wbindgen_wasm_module = module;
+ cachedDataViewMemory0 = null;
cachedUint8ArrayMemory0 = null;
diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm
index 94f0652..1897f95 100644
Binary files a/pkg/rudus_bg.wasm and b/pkg/rudus_bg.wasm differ
diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts
index 0d287b6..9a87573 100644
--- a/pkg/rudus_bg.wasm.d.ts
+++ b/pkg/rudus_bg.wasm.d.ts
@@ -1,9 +1,14 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
-export const ludus: (a: number, b: number) => [number, number];
-export const __wbindgen_export_0: WebAssembly.Table;
+export const ludus: (a: number, b: number) => any;
+export const __wbindgen_exn_store: (a: number) => void;
+export const __externref_table_alloc: () => number;
+export const __wbindgen_export_2: WebAssembly.Table;
+export const __wbindgen_free: (a: number, b: number, c: number) => void;
export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
-export const __wbindgen_free: (a: number, b: number, c: number) => void;
+export const __wbindgen_export_6: WebAssembly.Table;
+export const closure347_externref_shim: (a: number, b: number, c: any) => void;
+export const closure371_externref_shim: (a: number, b: number, c: any, d: any) => void;
export const __wbindgen_start: () => void;
diff --git a/pkg/worker.js b/pkg/worker.js
index fcc4212..3631a53 100644
--- a/pkg/worker.js
+++ b/pkg/worker.js
@@ -1,15 +1,40 @@
-import init from "./rudus.js";
+import init, {ludus} from "./rudus.js";
console.log("Worker: starting Ludus VM.")
export function io (out) {
- if (Object.keys(out).length > 0) postMessage(out)
+ if (out.length > 0) postMessage(out)
return new Promise((resolve, _) => {
- onmessage = (e) => resolve(e.data)
+ onmessage = (e) => {
+ console.log(e.data)
+ resolve(JSON.stringify(e.data))
+ }
})
}
-await init()
+let loaded_wasm = false
+
+async function run(e) {
+ if (!loaded_wasm) {
+ await init()
+ loaded_wasm = true
+ }
+ let msgs = e.data
+ for (const msg of msgs) {
+ if (msg.verb === "run" && typeof msg.data === 'string') {
+ console.log("running ludus!")
+ onmessage = () => {}
+ let result = await ludus(msg.data)
+ console.log(result)
+ onmessage = run
+ } else {
+ console.log("Did not get valid startup message. Instead got:")
+ console.log(e.data)
+ }
+ }
+}
+
+onmessage = run
console.log("Worker: Ludus VM is running.")
diff --git a/sandbox.ld b/sandbox.ld
index 4052cc3..08f6528 100644
--- a/sandbox.ld
+++ b/sandbox.ld
@@ -1,31 +1 @@
-fn agent (val) -> receive {
- (:set, new) -> agent (new)
- (:get, pid) -> {
- send (pid, (:response, val))
- agent (val)
- }
- (:update, f) -> agent (f (val))
-}
-
-fn agent/set (pid, val) -> {
- send (pid, (:set, val))
- val
-}
-
-fn agent/get (pid) -> {
- send (pid, (:get, self ()))
- receive {
- (:response, val) -> val
- }
-}
-
-fn agent/update (pid, f) -> {
- send (pid, (:update, f))
- agent/get (pid)
-}
-
-let myagent = spawn! (fn () -> agent (42))
-
-print! ("incrementing agent value to", agent/update (myagent, inc))
-
-:done!
+:foobar
diff --git a/src/base.rs b/src/base.rs
index d3ed96e..b553a6b 100644
--- a/src/base.rs
+++ b/src/base.rs
@@ -1,7 +1,7 @@
use crate::value::*;
use imbl::*;
-use ran::ran_f64;
use std::rc::Rc;
+use wasm_bindgen::prelude::*;
#[derive(Clone, Debug)]
pub enum BaseFn {
@@ -481,8 +481,14 @@ pub fn floor(x: &Value) -> Value {
}
}
-pub fn random() -> Value {
- Value::Number(ran_f64())
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_namespace = Math)]
+ fn random() -> f64;
+}
+
+pub fn base_random() -> Value {
+ Value::Number(random())
}
pub fn round(x: &Value) -> Value {
@@ -610,7 +616,10 @@ pub fn make_base() -> Value {
("pi", Value::Number(std::f64::consts::PI)),
("print!", Value::BaseFn(BaseFn::Unary("print!", print))),
("process", Value::Process),
- ("random", Value::BaseFn(BaseFn::Nullary("random", random))),
+ (
+ "random",
+ Value::BaseFn(BaseFn::Nullary("random", base_random)),
+ ),
("range", Value::BaseFn(BaseFn::Binary("range", range))),
("rest", Value::BaseFn(BaseFn::Unary("rest", rest))),
("round", Value::BaseFn(BaseFn::Unary("round", round))),
diff --git a/src/io.rs b/src/io.rs
index 3a406ff..0a36535 100644
--- a/src/io.rs
+++ b/src/io.rs
@@ -11,6 +11,12 @@ extern "C" {
async fn io (output: String) -> JsValue;
}
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_namespace = console)]
+ fn log(s: String);
+}
+
type Lines = Value; // expect a list of values
type Commands = Value; // expect a list of values
type Url = Value; // expect a string representing a URL
@@ -25,7 +31,7 @@ pub enum MsgOut {
Console(Lines),
Commands(Commands),
SlurpRequest(Url),
- Complete(Result),
+ Complete(FinalValue),
}
impl std::fmt::Display for MsgOut {
@@ -38,7 +44,10 @@ impl MsgOut {
pub fn to_json(&self) -> String {
match self {
MsgOut::Complete(value) => match value {
- Ok(value) => make_json_payload("complete", value.to_json().unwrap()),
+ Ok(value) => {
+ log(format!("value is: {}", value.show()));
+ make_json_payload("complete", serde_json::to_string(&value.show()).unwrap())
+ },
Err(_) => make_json_payload("complete", "\"null\"".to_string())
},
MsgOut::Commands(commands) => {
@@ -55,7 +64,7 @@ impl MsgOut {
}
MsgOut::Console(lines) => {
let lines = lines.as_list();
- let json_lines = lines.iter().map(|line| line.stringify()).collect::>().join("\n");
+ let json_lines = lines.iter().map(|line| line.stringify()).collect::>().join("\\n");
let json_lines = format!("\"{json_lines}\"");
make_json_payload("console", json_lines)
}
@@ -72,6 +81,16 @@ pub enum MsgIn {
Keyboard(Vec),
}
+impl std::fmt::Display for MsgIn {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ MsgIn::Input(str) => write!(f, "input: {str}"),
+ MsgIn::Kill => write!(f, "kill"),
+ _ => todo!()
+ }
+ }
+}
+
impl MsgIn {
pub fn to_value(self) -> Value {
match self {
@@ -99,8 +118,14 @@ pub async fn do_io (msgs: Vec) -> Vec {
let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::>().join(","));
let inbox = io (outbox).await;
let inbox = inbox.as_string().expect("response should be a string");
+ log(format!("response is: {inbox}"));
let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid");
+ if !inbox.is_empty() {
+ log("got messages in ludus!".to_string());
+ for msg in inbox.iter() {
+ log(format!("{}", msg));
+ }
+ }
inbox
-
}
diff --git a/src/lib.rs b/src/lib.rs
index 9a57659..e4e1cea 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,8 @@
use chumsky::{input::Stream, prelude::*};
use imbl::HashMap;
use wasm_bindgen::prelude::*;
+use std::rc::Rc;
+use std::cell::RefCell;
const DEBUG_SCRIPT_COMPILE: bool = false;
const DEBUG_SCRIPT_RUN: bool = false;
@@ -15,7 +17,7 @@ use crate::ast::Ast;
mod base;
mod world;
-use crate::world::World;
+use crate::world::{World, Zoo};
mod spans;
use crate::spans::Spanned;
@@ -42,10 +44,11 @@ mod value;
use value::Value;
mod vm;
+use vm::Creature;
const PRELUDE: &str = include_str!("../assets/test_prelude.ld");
-async fn prelude() -> HashMap<&'static str, Value> {
+fn prelude() -> HashMap<&'static str, Value> {
let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap();
let (parsed, parse_errors) = parser()
.parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s)))
@@ -71,7 +74,7 @@ async fn prelude() -> HashMap<&'static str, Value> {
if !validator.errors.is_empty() {
println!("VALIDATION ERRORS IN PRLUDE:");
report_invalidation(validator.errors);
- panic!();
+ panic!("validator errors in prelude");
}
let parsed: &'static Spanned = Box::leak(Box::new(parsed));
@@ -88,25 +91,37 @@ async fn prelude() -> HashMap<&'static str, Value> {
compiler.compile();
let chunk = compiler.chunk;
- let mut world = World::new(chunk, DEBUG_PRELUDE_RUN);
- let stub_console = Value::r#box(Value::new_list());
- world.run(stub_console).await;
- let prelude = world.result.unwrap().unwrap();
+ log("compiled prelude");
+ let stub_zoo = Rc::new(RefCell::new(Zoo::new()));
+ let mut prld_sync = Creature::new(chunk, stub_zoo, DEBUG_PRELUDE_RUN);
+ prld_sync.interpret();
+ log("run prelude synchronously");
+ let prelude = prld_sync.result.unwrap().unwrap();
match prelude {
Value::Dict(hashmap) => *hashmap,
_ => unreachable!(),
}
}
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_namespace = console)]
+ fn log(s: &str);
+}
+
#[wasm_bindgen]
pub async fn ludus(src: String) -> String {
+ console_error_panic_hook::set_once();
+ log("successfully entered ludus fn in Rust");
let src = src.to_string().leak();
+ log(src);
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
if !lex_errs.is_empty() {
return format!("{:?}", lex_errs);
}
let tokens = tokens.unwrap();
+ log("successfully tokenized source");
let (parse_result, parse_errors) = parser()
.parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s)))
@@ -119,8 +134,10 @@ pub async fn ludus(src: String) -> String {
// This simplifies lifetimes, and
// in any event, the AST should live forever
let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap()));
+ log("successfully parsed source");
- let prelude = prelude().await;
+ let prelude = prelude();
+ log("successfully loaded prelude");
let postlude = prelude.clone();
// let prelude = imbl::HashMap::new();
@@ -146,6 +163,7 @@ pub async fn ludus(src: String) -> String {
// compiler.bind("base");
compiler.compile();
+ log("successfully compiled source");
if DEBUG_SCRIPT_COMPILE {
println!("=== source code ===");
println!("{src}");
@@ -159,13 +177,16 @@ pub async fn ludus(src: String) -> String {
let vm_chunk = compiler.chunk;
- let mut world = World::new(vm_chunk, DEBUG_SCRIPT_RUN);
+ let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN);
let console = prelude
.get("console")
.expect("prelude must have a console")
.clone();
- world.run(console).await;
+ log("loaded world and console");
+ world.run().await;
let result = world.result.clone().unwrap();
+ log("ran script");
+ log(format!("{:?}", result).as_str());
let console = postlude.get("console").unwrap();
let Value::Box(console) = console else {
diff --git a/src/main.rs b/src/main.rs
index 8fe07b6..210846e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,10 +2,9 @@ use rudus::ludus;
use std::env;
use std::fs;
-#[tokio::main]
-pub async fn main() {
+pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
let src = fs::read_to_string("sandbox.ld").unwrap();
- let json = ludus(src).await;
- println!("{json}");
+ let json = ludus(src);
+ // println!("{json}");
}
diff --git a/src/value.rs b/src/value.rs
index 5258141..9618c67 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -259,7 +259,9 @@ impl Value {
pub fn to_json(&self) -> Option {
use Value::*;
match self {
- True | False | String(..) | Interned(..) | Number(..) => Some(self.show()),
+ True | False | Number(..) => Some(self.show()),
+ String(string) => Some(serde_json::to_string(string.as_ref()).unwrap()),
+ Interned(str) => Some(serde_json::to_string(str).unwrap()),
Keyword(str) => Some(format!("\"{str}\"")),
List(members) => {
let mut joined = "".to_string();
diff --git a/src/vm.rs b/src/vm.rs
index e6e0b9b..8ad7e86 100644
--- a/src/vm.rs
+++ b/src/vm.rs
@@ -339,7 +339,7 @@ impl Creature {
let Value::Number(ms) = args[1] else {
unreachable!()
};
- self.zoo.as_ref().borrow_mut().sleep(self.pid, ms as usize);
+ self.zoo.as_ref().borrow_mut().sleep(self.pid, ms);
self.r#yield = true;
self.push(Value::Keyword("ok"));
}
diff --git a/src/world.rs b/src/world.rs
index c7fc3eb..d0c7426 100644
--- a/src/world.rs
+++ b/src/world.rs
@@ -2,14 +2,26 @@ use crate::chunk::Chunk;
use crate::value::Value;
use crate::vm::{Creature, Panic};
use crate::io::{MsgOut, MsgIn, do_io};
-use ran::ran_u8;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::mem::swap;
use std::rc::Rc;
-use std::time::{Duration, Instant};
+use wasm_bindgen::prelude::*;
-const ANIMALS: [&str; 24] = [
+// Grab some JS stuff
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_namespace = console)]
+ fn log(s: &str);
+
+ #[wasm_bindgen(js_namespace = Math)]
+ fn random() -> f64;
+
+ #[wasm_bindgen(js_namespace = Date)]
+ fn now() -> f64;
+}
+
+const ANIMALS: [&str; 32] = [
"tortoise",
"hare",
"squirrel",
@@ -31,9 +43,17 @@ const ANIMALS: [&str; 24] = [
"zebra",
"hyena",
"giraffe",
- "leopard",
- "lion",
"hippopotamus",
+ "capybara",
+ "python",
+ "gopher",
+ "crab",
+ "trout",
+ "osprey",
+ "lemur",
+ "wobbegong",
+ "walrus",
+ "opossum",
];
#[derive(Debug, Clone, PartialEq)]
@@ -70,7 +90,7 @@ pub struct Zoo {
ids: HashMap<&'static str, usize>,
dead: HashSet<&'static str>,
kill_list: Vec<&'static str>,
- sleeping: HashMap<&'static str, (Instant, Duration)>,
+ sleeping: HashMap<&'static str, f64>,
active_idx: usize,
active_id: &'static str,
}
@@ -90,20 +110,27 @@ impl Zoo {
}
fn random_id(&self) -> String {
- let rand = ran_u8() as usize % 24;
+ log("generating random id");
+ let rand_idx = (random() * 32.0) as usize;
+ log("random number!");
let idx = self.procs.len();
- format!("{}_{idx}", ANIMALS[rand])
+ log("procs len");
+ format!("{}_{idx}", ANIMALS[rand_idx])
}
fn new_id(&self) -> &'static str {
+ log("creating new id");
let mut new = self.random_id();
+ log("got new ramdom id");
while self.dead.iter().any(|old| *old == new) {
new = self.random_id();
}
+ log(format!("got new id: {}", new).as_str());
new.leak()
}
pub fn put(&mut self, mut proc: Creature) -> &'static str {
+ log("putting creature");
if self.empty.is_empty() {
let id = self.new_id();
let idx = self.procs.len();
@@ -113,7 +140,7 @@ impl Zoo {
id
} else {
let idx = self.empty.pop().unwrap();
- let rand = ran_u8() as usize % 24;
+ let rand = (random() * 32.0) as usize;
let id = format!("{}_{idx}", ANIMALS[rand]).leak();
proc.pid = id;
self.ids.insert(id, idx);
@@ -126,9 +153,9 @@ impl Zoo {
self.kill_list.push(id);
}
- pub fn sleep(&mut self, id: &'static str, ms: usize) {
+ pub fn sleep(&mut self, id: &'static str, ms: f64) {
self.sleeping
- .insert(id, (Instant::now(), Duration::from_millis(ms as u64)));
+ .insert(id, now() + ms);
}
pub fn is_alive(&self, id: &'static str) -> bool {
@@ -161,7 +188,7 @@ impl Zoo {
}
self.sleeping
- .retain(|_, (instant, duration)| instant.elapsed() < *duration);
+ .retain(|_, wakeup_time| now() < *wakeup_time);
println!(
"currently sleeping processes: {}",
@@ -232,25 +259,72 @@ impl Zoo {
}
}
+#[derive(Debug, Clone, PartialEq)]
+pub struct Buffers {
+ console: Value,
+ // commands: Value,
+ // fetch_outbox: Value,
+ // fetch_inbox: Value,
+ input: Value,
+}
+
+impl Buffers {
+ pub fn new (prelude: imbl::HashMap<&'static str, Value>) -> Buffers {
+ Buffers {
+ console: prelude.get("console").unwrap().clone(),
+ // commands: prelude.get("commands").unwrap().clone(),
+ // fetch_outbox: prelude.get("fetch_outbox").unwrap().clone(),
+ // fetch_inbox: prelude.get("fetch_inbox").unwrap().clone(),
+ input: prelude.get("input").unwrap().clone(),
+ }
+ }
+
+ pub fn console (&self) -> Rc> {
+ self.console.as_box()
+ }
+
+ pub fn input (&self) -> Rc> {
+ self.input.as_box()
+ }
+
+ // pub fn commands (&self) -> Rc> {
+ // self.commands.as_box()
+ // }
+ // pub fn fetch_outbox (&self) -> Rc> {
+ // self.fetch_outbox.as_box()
+ // }
+ // pub fn fetch_inbox (&self) -> Rc> {
+ // self.fetch_inbox.as_box()
+ // }
+
+}
+
#[derive(Debug, Clone, PartialEq)]
pub struct World {
zoo: Rc>,
active: Option,
main: &'static str,
pub result: Option>,
+ buffers: Buffers,
+ last_io: f64,
+ kill_signal: bool,
}
impl World {
- pub fn new(chunk: Chunk, debug: bool) -> World {
+ pub fn new(chunk: Chunk, prelude: imbl::HashMap<&'static str, Value>, debug: bool) -> World {
let zoo = Rc::new(RefCell::new(Zoo::new()));
let main = Creature::new(chunk, zoo.clone(), debug);
- let id = zoo.as_ref().borrow_mut().put(main);
-
+ let id = zoo.borrow_mut().put(main);
+ let buffers = Buffers::new(prelude);
+
World {
zoo,
active: None,
main: id,
result: None,
+ buffers,
+ last_io: 0.0,
+ kill_signal: false,
}
}
@@ -266,111 +340,100 @@ impl World {
swap(&mut new_active_opt, &mut self.active);
}
- pub fn activate_main(&mut self) {
- let main = self.zoo.as_ref().borrow_mut().catch(self.main);
+ fn activate_main(&mut self) {
+ let main = self.zoo.borrow_mut().catch(self.main);
self.active = Some(main);
}
- pub fn active_id(&mut self) -> &'static str {
+ fn active_id(&mut self) -> &'static str {
self.active.as_ref().unwrap().pid
}
- pub fn kill_active(&mut self) {
+ fn kill_active(&mut self) {
let id = self.active_id();
self.zoo.as_ref().borrow_mut().kill(id);
}
- pub fn active_result(&mut self) -> &Option> {
+ fn active_result(&mut self) -> &Option> {
&self.active.as_ref().unwrap().result
}
- // TODO: add memory io places to this signature
- // * console
- // * input
- // * commands
- // * slurp
- pub async fn run(
- &mut self,
- console: Value,
- // input: Value,
- // commands: Value,
- // slurp_out: Value,
- // slurp_in: Value,
- ) {
+ fn flush_buffers(&mut self) -> Vec {
+ let mut outbox = vec![];
+ if let Some(console) = self.flush_console() {
+ outbox.push(console);
+ }
+ outbox
+ }
+
+ fn flush_console(&self) -> Option {
+ let console = self.buffers.console();
+ let working_copy = RefCell::new(Value::new_list());
+ console.swap(&working_copy);
+ let working_value = working_copy.borrow();
+ if working_value.as_list().is_empty() { return None; }
+ Some(MsgOut::Console(working_value.clone()))
+ }
+
+ fn complete_main(&mut self) -> Vec {
+ let mut outbox = self.flush_buffers();
+ // TODO: if we have a panic, actually add the panic message to the console
+ let result = self.active_result().clone().unwrap();
+ self.result = Some(result.clone());
+ outbox.push(MsgOut::Complete(result));
+ outbox
+ }
+
+ fn interpret_active(&mut self) {
+ self.active.as_mut().unwrap().interpret();
+ }
+
+ async fn maybe_do_io(&mut self) {
+ if self.last_io + 10.0 > now () {
+ let outbox = self.flush_buffers();
+ let inbox = do_io(outbox).await;
+ self.fill_buffers(inbox);
+ }
+ self.last_io = now();
+ }
+
+ fn fill_input(&mut self, str: String) {
+ let value = Value::string(str);
+ let working = RefCell::new(value);
+ let input = self.buffers.input();
+ input.swap(&working);
+ }
+
+ fn fill_buffers(&mut self, inbox: Vec) {
+ for msg in inbox {
+ match msg {
+ MsgIn::Input(str) => self.fill_input(str),
+ MsgIn::Kill => self.kill_signal = true,
+ _ => todo!()
+ }
+ }
+ }
+
+ pub async fn run(&mut self) {
self.activate_main();
- // let Value::Box(input) = input else {unreachable!()};
- // let Value::Box(commands) = commands else {unreachable!()};
- // let Value::Box(slurp) = slurp else {
- // unreachable!()};
- let mut last_io = Instant::now();
- let mut kill_signal = false;
loop {
- if kill_signal {
- // TODO: send a last message to the console
- println!("received KILL signal");
+ if self.kill_signal {
+ let outbox = self.flush_buffers();
+ do_io(outbox).await;
return;
}
- println!(
- "entering world loop; active process is {}",
- self.active_id()
- );
- self.active.as_mut().unwrap().interpret();
- println!("yielded from {}", self.active_id());
- match self.active_result() {
- None => (),
- Some(_) => {
- if self.active_id() == self.main {
- let result = self.active_result().clone().unwrap();
- self.result = Some(result.clone());
-
- //TODO: capture any remaining console or command values
- do_io(vec![MsgOut::Complete(result)]);
- return;
- }
- println!(
- "process {} died with {:?}",
- self.active_id(),
- self.active_result().clone()
- );
- self.kill_active();
+ self.interpret_active();
+ if self.active_result().is_some() {
+ if self.active_id() == self.main {
+ let outbox = self.complete_main();
+ do_io(outbox).await;
+ return;
}
+ self.kill_active();
}
- println!("getting next process");
self.next();
- // TODO:: if enough time has elapsed (how much?) run i/o
- // 10 ms is 100hz, so that's a nice refresh rate
- if Instant::now().duration_since(last_io) > Duration::from_millis(10) {
- // gather io
- // compile it into messages
- // serialize it
- let mut outbox = vec![];
- if let Some(console) = flush_console(&console) {
- outbox.push(console);
- };
- // TODO: slurp
- // TODO: commands
- // send it
- // await the response
- let inbox = do_io(outbox).await;
- // unpack the response into messages
- for msg in inbox {
- match msg {
- MsgIn::Kill => kill_signal = true,
- _ => todo!()
- }
- }
- // update
- last_io = Instant::now();
- }
+ self.maybe_do_io().await;
}
}
}
-fn flush_console(console: &Value) -> Option {
- let console = console.as_box();
- let working_copy = RefCell::new(Value::new_list());
- console.swap(&working_copy);
- let working_value = working_copy.borrow();
- if working_value.as_list().is_empty() { return None; }
- Some(MsgOut::Console(working_value.clone()))
-}