maybe figure out the wasm thing?
This commit is contained in:
parent
b78063ba07
commit
44f7ce7b06
10
Cargo.toml
10
Cargo.toml
|
@ -12,13 +12,13 @@ crate-type = ["cdylib", "rlib"]
|
|||
ariadne = { git = "https://github.com/zesterer/ariadne" }
|
||||
chumsky = { git = "https://github.com/zesterer/chumsky", features = ["label"] }
|
||||
imbl = "3.0.0"
|
||||
# struct_scalpel = "0.1.1"
|
||||
ran = "2.0.1"
|
||||
# rust-embed = "8.5.0"
|
||||
# boxing = "0.1.2"
|
||||
# ordered-float = "4.5.0"
|
||||
# index_vec = "0.1.4"
|
||||
num-derive = "0.4.2"
|
||||
num-traits = "0.2.19"
|
||||
regex = "1.11.1"
|
||||
wasm-bindgen = "0.2"
|
||||
# struct_scalpel = "0.1.1"
|
||||
# rust-embed = "8.5.0"
|
||||
# boxing = "0.1.2"
|
||||
# ordered-float = "4.5.0"
|
||||
# index_vec = "0.1.4"
|
||||
|
|
|
@ -522,117 +522,6 @@ SOLUTION: test to see if the function has been forward-declared, and if it has,
|
|||
NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls.
|
||||
|
||||
NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack.
|
||||
<<<<<<< Updated upstream
|
||||
This is the thing I am about to do
|
||||
||||||| Stash base
|
||||
This is the thing I am about to do.
|
||||
|
||||
### I think the interpreter, uh, works?
|
||||
#### 2025-06-24
|
||||
I'm sure I'll find some small problems.
|
||||
But right now the thing works.
|
||||
At the moment, I'm thinking about how to get good error messages.
|
||||
Panics are difficult.
|
||||
And I'm worried about ariadne as the error reporting crate.
|
||||
Since it writes to stdout, it has all kinds of escape codes.
|
||||
I need a plain ass string, at least for the web frontend.
|
||||
So.
|
||||
|
||||
Current task, however, is how to get reasonable panic error messages.
|
||||
Let's simplify the problem.
|
||||
|
||||
First, let's get tracebacks and line numbers.
|
||||
We use Chumsky spanned ASTs.
|
||||
The span is just a range into an str.
|
||||
What I can do is pretty stupidly just generate line numbers from the spans in the compiler, and from there, get a reasonable traceback.
|
||||
So instead of using Ariadne's fancy report builder, let's just do something more what we already have here:
|
||||
|
||||
```
|
||||
Ludus panicked! no match
|
||||
on line 1 in input,
|
||||
calling: add
|
||||
with arguments: ("foo")
|
||||
expected match with one of:
|
||||
()
|
||||
(x as :number)
|
||||
(x as :number, y as :number)
|
||||
(x, y, ...zs)
|
||||
((x1, y1), (x2, y2))
|
||||
>>> add ("foo")
|
||||
......^
|
||||
```
|
||||
We need:
|
||||
* the location of the function call in terms of the line number
|
||||
* the arguments used
|
||||
* the patterns expected to match (special cases: `let` vs `match` vs `fn`)
|
||||
|
||||
That means, for bookkeeping, we need:
|
||||
* In the compiler, line number
|
||||
* In the VM, the arguments
|
||||
* In the VM, the pattern AST node.
|
||||
|
||||
In Janet-Ludus, there are only a few types of panic:
|
||||
* `fn-no-match`: no match against a function call
|
||||
* `let-no-match`: no match against a `let` binding
|
||||
* `match-no-match`: no match against a `match` form
|
||||
* `generic-panic`: everything else
|
||||
* `runtime-error`: an internal Ludus error
|
||||
|
||||
The first three are simply formatting differences.
|
||||
There are no tracebacks.
|
||||
|
||||
Tracebacks should be easy enough, although there's some fiddly bits.
|
||||
While it's nice to have the carret, the brutalist attempt here should be just to give us the line--since the carret isn't exactly precise in the Janet interpereter.
|
||||
And the traceback should look something like:
|
||||
|
||||
```
|
||||
calling foo with (:bar, :baz)
|
||||
at line 12 in input
|
||||
calling bar with ()
|
||||
at line 34 in prelude
|
||||
calling baz with (1, 2, 3)
|
||||
at line 12 in input
|
||||
```
|
||||
|
||||
Which means, again: function names, ip->line conversion, and arguments.
|
||||
|
||||
The runtime needs a representation of the patterns in _any_ matching form.
|
||||
The speed is so much greater now that I'm not so concerned about little optimizations.
|
||||
So: a chunk needs a vec of patterns-representations. (I'm thinking simply a `Vec<String>`.)
|
||||
So does a function, for `doc!`.
|
||||
Same same re: `Vec<String>`.
|
||||
A VM needs a register for the scrutinee (which with function calls is just the arguments, already captured).
|
||||
A VM also needs a register for the pattern.
|
||||
So when there's a no match, we just yank the pattern and the scrutinee out of these registers.
|
||||
|
||||
This all seems very straightforward compared to the compiling & VM stuff.
|
||||
|
||||
Here's some stub code I wrote for dealing with ranges, source, line numbers:
|
||||
|
||||
```rust
|
||||
let str = "foo bar baz\nquux frob\nthing thing thing";
|
||||
let range = 0..4;
|
||||
|
||||
println!("{}", str.get(range).unwrap());
|
||||
|
||||
let lines: Vec<&str> = str.split_terminator("\n").collect();
|
||||
|
||||
println!("{:?}", lines);
|
||||
|
||||
let idx = 20;
|
||||
|
||||
let mut line_no = 1;
|
||||
for i in 0..idx {
|
||||
if str.chars().nth(i).unwrap() == '\n' {
|
||||
line_no += 1;
|
||||
}
|
||||
}
|
||||
|
||||
println!("line {line_no}: {}", lines[line_no - 1]);
|
||||
```
|
||||
|
||||
|
||||
=======
|
||||
This is the thing I am about to do.
|
||||
|
||||
### I think the interpreter, uh, works?
|
||||
|
@ -756,4 +645,23 @@ println!("line {line_no}: {}", lines[line_no - 1]);
|
|||
* Users can create their own (public) repos and put stuff in there.
|
||||
* We still want saving text output from web Ludus
|
||||
* Later, with perceptrons & the book, we'll need additional solutions.
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
#### Integration hell
|
||||
As predicted, Javascript is the devil.
|
||||
|
||||
wasm-pack has several targets:
|
||||
* nodejs -> this should be what we want
|
||||
* web -> this could be what we want
|
||||
* bundler -> webpack confuses me
|
||||
|
||||
The simplest, shortest route should be to create a viable nodejs library.
|
||||
It works.
|
||||
I can wire up the wasm-pack output with a package.json, pull it down from npm, and it work.
|
||||
However, because of course, vite, which svelte uses, doesn't like this.
|
||||
We get an error that `TextEncoder is not a constructor`.
|
||||
This, apparently, has something to do with the way that vite packages up node libraries?
|
||||
See https://github.com/vitejs/vite/discussions/12826.
|
||||
|
||||
Web, in some ways, is even more straightforward.
|
||||
It produces an ESM that just works in the browser.
|
||||
And
|
||||
|
|
1594
package-lock.json
generated
Normal file
1594
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -8,6 +8,9 @@
|
|||
"keywords": [],
|
||||
"author": "Scott Richmond",
|
||||
"license": "GPL-3.0",
|
||||
"scripts": {
|
||||
"build": "webpack"
|
||||
},
|
||||
"files": [
|
||||
"pkg/rudus.js",
|
||||
"pkg/ludus.js",
|
||||
|
@ -15,5 +18,9 @@
|
|||
"pkg/rudus_bg.wasm.d.ts",
|
||||
"pkg/rudus.d.ts"
|
||||
],
|
||||
"devDependencies": {}
|
||||
"devDependencies": {
|
||||
"@wasm-tool/wasm-pack-plugin": "^1.7.0",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack-cli": "^6.0.1"
|
||||
}
|
||||
}
|
||||
|
|
2
pkg/.gitignore
vendored
2
pkg/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
*.wasm
|
||||
*.wasm*
|
3
pkg/README.md
Normal file
3
pkg/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# rudus
|
||||
|
||||
A Rust implementation of Ludus.
|
21
pkg/index.html
Normal file
21
pkg/index.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||
<title>Testing Ludus/WASM integration</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
import {run} from "./ludus.js";
|
||||
|
||||
window.ludus = run;
|
||||
|
||||
console.log(run(":foobar"));
|
||||
</script>
|
||||
<p>
|
||||
Open the console. All the action's in there.
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
29
pkg/ludus.js
29
pkg/ludus.js
|
@ -1,27 +1,29 @@
|
|||
const mod = require("./rudus.js");
|
||||
import init, {ludus} from "./rudus.js";
|
||||
|
||||
await init();
|
||||
|
||||
let res = null
|
||||
|
||||
let code = null
|
||||
|
||||
function run (source) {
|
||||
export function run (source) {
|
||||
code = source
|
||||
const output = mod.run(source)
|
||||
const output = ludus(source)
|
||||
res = JSON.parse(output)
|
||||
return res
|
||||
}
|
||||
|
||||
function stdout () {
|
||||
export function stdout () {
|
||||
if (!res) return ""
|
||||
return res.io.stdout.data
|
||||
}
|
||||
|
||||
function turtle_commands () {
|
||||
export function turtle_commands () {
|
||||
if (!res) return []
|
||||
return res.io.turtle.data
|
||||
}
|
||||
|
||||
function result () {
|
||||
export function result () {
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -240,7 +242,7 @@ function escape_svg (svg) {
|
|||
.replace(/'/g, "'")
|
||||
}
|
||||
|
||||
function extract_ludus (svg) {
|
||||
export function extract_ludus (svg) {
|
||||
const code = svg.split("<ludus>")[1]?.split("</ludus>")[0] ?? ""
|
||||
return code
|
||||
.replace(/&/g, "&")
|
||||
|
@ -280,7 +282,7 @@ function svg_render_turtle (state) {
|
|||
`
|
||||
}
|
||||
|
||||
function svg (commands) {
|
||||
export function svg (commands) {
|
||||
// console.log(commands)
|
||||
const states = [turtle_init]
|
||||
commands.reduce((prev_state, command) => {
|
||||
|
@ -349,7 +351,7 @@ function p5_render_turtle (state, calls) {
|
|||
return calls
|
||||
}
|
||||
|
||||
function p5 (commands) {
|
||||
export function p5 (commands) {
|
||||
const states = [turtle_init]
|
||||
commands.reduce((prev_state, command) => {
|
||||
const new_state = command_to_state(prev_state, command)
|
||||
|
@ -374,12 +376,3 @@ function p5 (commands) {
|
|||
return p5_calls
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
run,
|
||||
stdout,
|
||||
turtle_commands,
|
||||
result,
|
||||
extract_ludus,
|
||||
p5,
|
||||
svg,
|
||||
}
|
||||
|
|
15
pkg/package.json
Normal file
15
pkg/package.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "rudus",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"files": [
|
||||
"rudus_bg.wasm",
|
||||
"rudus.js",
|
||||
"rudus.d.ts"
|
||||
],
|
||||
"main": "rudus.js",
|
||||
"types": "rudus.d.ts",
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
]
|
||||
}
|
35
pkg/rudus.d.ts
vendored
35
pkg/rudus.d.ts
vendored
|
@ -1,3 +1,36 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export function run(src: string): string;
|
||||
export function ludus(src: string): string;
|
||||
|
||||
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 __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_start: () => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
/**
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||
|
|
130
pkg/rudus.js
130
pkg/rudus.js
|
@ -1,8 +1,4 @@
|
|||
|
||||
let imports = {};
|
||||
imports['__wbindgen_placeholder__'] = module.exports;
|
||||
let wasm;
|
||||
const { TextEncoder, TextDecoder } = require(`util`);
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
|
@ -15,7 +11,7 @@ function getUint8ArrayMemory0() {
|
|||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
let cachedTextEncoder = new TextEncoder('utf-8');
|
||||
const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
|
||||
|
||||
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
|
||||
? function (arg, view) {
|
||||
|
@ -69,9 +65,9 @@ function passStringToWasm0(arg, malloc, realloc) {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
|
||||
|
||||
cachedTextDecoder.decode();
|
||||
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
|
@ -81,22 +77,56 @@ function getStringFromWasm0(ptr, len) {
|
|||
* @param {string} src
|
||||
* @returns {string}
|
||||
*/
|
||||
module.exports.run = function(src) {
|
||||
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.run(ptr0, len0);
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.__wbindgen_init_externref_table = function() {
|
||||
async function __wbg_load(module, imports) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
try {
|
||||
return await WebAssembly.instantiateStreaming(module, imports);
|
||||
|
||||
} catch (e) {
|
||||
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
|
||||
} else {
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return { instance, module };
|
||||
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbindgen_init_externref_table = function() {
|
||||
const table = wasm.__wbindgen_export_0;
|
||||
const offset = table.grow(4);
|
||||
table.set(0, undefined);
|
||||
|
@ -105,15 +135,77 @@ module.exports.__wbindgen_init_externref_table = function() {
|
|||
table.set(offset + 2, true);
|
||||
table.set(offset + 3, false);
|
||||
;
|
||||
};
|
||||
};
|
||||
|
||||
const path = require('path').join(__dirname, 'rudus_bg.wasm');
|
||||
const bytes = require('fs').readFileSync(path);
|
||||
return imports;
|
||||
}
|
||||
|
||||
const wasmModule = new WebAssembly.Module(bytes);
|
||||
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
|
||||
wasm = wasmInstance.exports;
|
||||
module.exports.__wasm = wasm;
|
||||
function __wbg_init_memory(imports, memory) {
|
||||
|
||||
wasm.__wbindgen_start();
|
||||
}
|
||||
|
||||
function __wbg_finalize_init(instance, module) {
|
||||
wasm = instance.exports;
|
||||
__wbg_init.__wbindgen_wasm_module = module;
|
||||
cachedUint8ArrayMemory0 = null;
|
||||
|
||||
|
||||
wasm.__wbindgen_start();
|
||||
return wasm;
|
||||
}
|
||||
|
||||
function initSync(module) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
if (Object.getPrototypeOf(module) === Object.prototype) {
|
||||
({module} = module)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
__wbg_init_memory(imports);
|
||||
|
||||
if (!(module instanceof WebAssembly.Module)) {
|
||||
module = new WebAssembly.Module(module);
|
||||
}
|
||||
|
||||
const instance = new WebAssembly.Instance(module, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
async function __wbg_init(module_or_path) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (typeof module_or_path !== 'undefined') {
|
||||
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
||||
({module_or_path} = module_or_path)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module_or_path === 'undefined') {
|
||||
module_or_path = new URL('rudus_bg.wasm', import.meta.url);
|
||||
}
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
||||
module_or_path = fetch(module_or_path);
|
||||
}
|
||||
|
||||
__wbg_init_memory(imports);
|
||||
|
||||
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
export { initSync };
|
||||
export default __wbg_init;
|
||||
|
|
BIN
pkg/rudus_bg.wasm
Normal file
BIN
pkg/rudus_bg.wasm
Normal file
Binary file not shown.
9
pkg/rudus_bg.wasm.d.ts
vendored
Normal file
9
pkg/rudus_bg.wasm.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* 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 __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_start: () => void;
|
5
pkg/test.js
Normal file
5
pkg/test.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import * as mod from "./ludus.js";
|
||||
|
||||
console.log(mod.run(`
|
||||
:foobar
|
||||
`));
|
|
@ -90,7 +90,7 @@ fn prelude() -> HashMap<&'static str, Value> {
|
|||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn run(src: String) -> String {
|
||||
pub fn ludus(src: String) -> String {
|
||||
let src = src.to_string().leak();
|
||||
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
|
||||
if !lex_errs.is_empty() {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use rudus::run;
|
||||
use rudus::ludus;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
pub fn main() {
|
||||
env::set_var("RUST_BACKTRACE", "1");
|
||||
let src = fs::read_to_string("sandbox.ld").unwrap();
|
||||
let json = run(src);
|
||||
let json = ludus(src);
|
||||
println!("{json}");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user