Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 3 additions & 90 deletions bend2/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ type File = {
segs: Seg[];
spins: Seg[];
spun: Map<string, string>;
marsh: Map<string, string>;
clos: Set<string>;
tabs: Map<string, number>;
tails: Map<Name, Set<Name>>;
Expand Down Expand Up @@ -473,14 +472,6 @@ function nat_chk(n) {
return n;
}

function nat_host(n) {
const int = typeof n === "bigint" || Number.isInteger(n);
if (int && n >= 0 && n <= 2 ** 53) {
return Number(n);
}
return { [Symbol.toPrimitive]() { throw "bend: ${ERRS[5]}"; } };
}

function f32_show(x) {
if (x !== x) {
return "nan";
Expand Down Expand Up @@ -1334,7 +1325,6 @@ function file_new(book: Bend.Book, js: boolean): File {
segs: [],
spins: [],
spun: new Map(),
marsh: new Map(),
clos: new Set(),
tabs: new Map(),
tails: new Map(),
Expand Down Expand Up @@ -3109,11 +3099,8 @@ function js_def(sc: Scope, k: Name, def: Bend.Def): void {
}
block(sc, `function ${js_sat(k)}(${params.join(", ")}) {`, () => {
if (def_foreign(def)) {
const doms = [...live, tele_unbind(def.T).doms.at(-1)!];
const xs = params.map((p, i) =>
`${js_marshal(doms[i][2], true)}(${p})`);
return file_push(sc, `return { $: ${JSON.stringify(Bend.name_key(k))
}, args: [${xs.slice(0, -1).join(", ")}], kont: ${xs.at(-1)} };`);
}, args: [${params.slice(0, -1).join(", ")}], kont: ${params.at(-1)} };`);
}
if (loop.length === 0) {
return js_func(sc, h!, def.T, params);
Expand All @@ -3137,80 +3124,6 @@ function js_def(sc: Scope, k: Name, def: Bend.Def): void {
file_push(sc, "");
}

// A part with a converter of its own waits on one work stack, so no depth
// grows the JS call stack; a Nat or function part converts on the spot.
// Arrays stay in-place; ADT nodes are copied.
function js_marshal(A: HTerm | null, out: boolean): string {
const t = ty_wnf(A);
if (t?.$ === "All") {
const y = js_marshal(t.B(DUMMY), out);
if (!quant_live(t.q)) {
return y;
}
const x = js_marshal(t.A, !out);
return x + y === "" ? y : `((f) => (x) => ${y}(f(${x}(x))))`;
}
const seen = new Set<Name>();
const nat = (u: HTerm | null): boolean => u?.$ === "All"
? [u.A, u.B(DUMMY)].some((v) => ty_holds(v, nat, seen))
: u?.$ === "ADT" && u.k === "Nat";
if (t?.$ !== "ADT" || !ty_holds(t, nat, seen)) {
return "";
}
if (t.k === "Nat") {
return out ? "BigInt" : "nat_host";
}
const key = (out ? "out " : "in ") + Bend.term_key(Bend.term_lower(t));
return memo(FL.marsh, key, () => {
const name = "$0m" + FL.marsh.size;
FL.marsh.set(key, name);
const put = (f: string, a: string, i: string): string => f[0] === "$"
? `q.push(${f}, ${a}, ${i});` : `${a}[${i}] = ${f}(${a}[${i}]);`;
const body: string[] = [];
if (t.k === "Array") {
const f = js_marshal(t.x[0], out);
if (f === "") return "";
body.push(`r = v; v.forEach((x, i) => { ${put(f, "v", "i")} });`);
} else {
const cs = (FL.book.tlds[t.k] as Bend.ADT).c;
const arms = cs.map((c) => {
const fs = ctr_live(c, t.x).flatMap(([, n, B]) => {
const f = js_marshal(B, out);
return f === "" ? [] : [put(f, "r", `"${n}"`)];
});
return [`case "${Bend.name_key(c.k)}": r = ${fs.length ? "{...v}"
: "v"};`, ...fs, "break;"].join(" ");
});
const tk = Bend.name_key(t.k);
const tags = cs.map((c) => Bend.name_key(c.k)).join(", ");
body.push("switch (v.$) {", ...arms,
// TODO(#1105): a tag is the key the loading book gives its constructor,
// so it depends on the root file; make tags the same in every book
`default: throw "bend: ${tk} has no tag " + v?.$ + " (its tags: ${
tags}); a tag names its constructor as the"
+ " loading file sees it, which a later version will make the same"
+ " everywhere (#1105)";`, "}");
}
FL.spins.push({ ...seg_new("", BOX, ["v"]), lines: [`function ${name}(v, q) {`,
"const own = q === undefined;", "if (own) q = [];", "let r;", ...body,
"if (own) while (q.length) {",
"const i = q.pop(), a = q.pop(); a[i] = q.pop()(a[i], q);",
"}", "return r;", "}", ""] });
return name;
});
}

function js_host(k: Name): string {
const { n, live } = fun_of(k);
const ps = live.map((_, i) => "a" + i);
const xs = live.map(([, , A], i) => `${js_marshal(A, false)}(${ps[i]})`);
const ret = tele_fill(FL.book.tlds[k].T, Array(n).fill(DUMMY));
const back = live.map(([, , A], i) =>
`${js_marshal(A, true)}(${ps[i]});`);
return `(${ps.join(", ")}) => { const r = ${js_marshal(ret, true)
}(run_loop(${js_sat(k)}(${xs.join(", ")}))); ${back.join(" ")} return r; }`;
}

// A module (for the .bend loader and -o <out>.mjs) roots and exports each
// def a host can call.
export function js_lib(book: Bend.Book, mod = false): string {
Expand All @@ -3229,7 +3142,7 @@ export function js_lib(book: Bend.Book, mod = false): string {
const effs = effect_srcs(".js", "a foreign def without a .js import: ")
.map((t) => `(() => {\n${t}\n})();\n\n`).join("");
const lib = outs === null ? "" : `export default {\n${outs.map((k) =>
` ${js_key(Bend.name_key(k))}run_lib(${js_host(k)}, ${
` ${js_key(Bend.name_key(k))}run_lib(${js_sat(k)}, ${
fun_of(k).lays.length}),`).join("\n")}\n};\n`;
const jmps = new Map<Name, boolean>();
const jmp = (k: Name): boolean => k === CLO_APPLY || memo(jmps, k, () =>
Expand Down Expand Up @@ -6071,7 +5984,7 @@ function run_loop(r) {

function run_lib(f, n) {
return (...a) => a.length < n ? run_lib((...b) => f(...a, ...b), n - a.length)
: f(...a);
: run_loop(f(...a));
}

// Effect
Expand Down
2 changes: 1 addition & 1 deletion bend2/effs/now.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// ==

function io_now() {
return BigInt(Math.floor(performance.now()));
return Math.floor(performance.now());
}

io_eff(CID(IO.now), io_now);
2 changes: 1 addition & 1 deletion bend2/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// (node --import). A .bend module exports every filled, non-base, non-IO
// def, wrapped so a JS caller passes the live arguments, in one call or
// curried, and gets a plain value back: a constructor is {$: "Name", field:
// value, ...}, a closure is a function, Nat is BigInt, Bool, String and U32
// value, ...}, a closure is a function, Nat is a number, Bool, String and U32
// are native. A page bundles through Bun.build with the loader on, since
// the bun build CLI takes no plugins.

Expand Down
4 changes: 2 additions & 2 deletions demos/app_win_is_bug_2d/web/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Game from "../main.bend";
// Map
// ===

const ROWS = Game.grid(Game.map_h() - 1n).split("\n");
const ROWS = Game.grid(Game.map_h() - 1).split("\n");
const H = ROWS.length;
const W = ROWS[0].length;
const WALLS = new Set();
Expand All @@ -25,7 +25,7 @@ for (let y = 0; y < H; y++) {
// Bend bridge
// ===========
// The conventions: a constructor is {$: "Name", field: value, ...} (live
// fields only), Nat is BigInt, Bool is a plain boolean. Stepping onto the
// fields only), Nat is a number, Bool is a plain boolean. Stepping onto the
// flag's cell is the win: the game sets `won` itself.

const one = (act) => ({ $: "Con", head: { $: act }, tail: { $: "Nil" } });
Expand Down
2 changes: 1 addition & 1 deletion guide/EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ allocate it in the run function and free it in `pack`. In `bend2/effs/`,
The `.js` file is a plain script, run once in a closure of its own, that
registers the effect: `io_eff(CID(Clock.now), clock_now)`. The effect
takes the def's arguments as JS values: a `U32` is a number, a `Nat` a
`BigInt`, a `String` a string, a constructor
number, a `String` a string, a constructor
`{$: CID(Name), field: value}`, a handle its host value (a descriptor).
`CID(Name)` is the constructor's tag, read as on the C side. It returns
the answer the same way: `io_done(v)`, `io_fail(code)`,
Expand Down
4 changes: 2 additions & 2 deletions guide/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,8 @@ underscores. You can add your own effects the same way. Only the event loop runs
them, so proofs, termination and the GPU never touch host code. In the other
direction, a JS file may `import Game from "./game.bend"` (with `bend2/main.ts`
preloaded), or from the `./game.mjs` that `-o game.mjs` writes, and call every
non-IO def, with constructors as `{$: "Name", field: value}` and `Nat` as
`BigInt`. A value crosses without a copy: an `Array`
non-IO def, with constructors as `{$: "Name", field: value}` and `Nat` as a
number. A value crosses without a copy: an `Array`
argument is the caller's own array, updated in place, so copy it first if you
keep it.

Expand Down
6 changes: 3 additions & 3 deletions tests/io/marshal_array_depth.bend
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# io (io_marshal_array_depth): nested Array<Tree> marshalling must use a
# bounded host stack in both directions, preserve every constructor and
# BigInt Nat field, and return the modified deep leaf to Bend.
# io (io_marshal_array_depth): a nested Array<Tree> crosses to the host
# and back at depth 20000, keeping every constructor and Nat field, and
# the host's change to the deep leaf reaches Bend.
import Base

type Tree is Type:
Expand Down
4 changes: 2 additions & 2 deletions tests/io/marshal_array_depth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function exchange(tree) {
const input = 123456789n;
const input = 123456789;
let node = tree;
let depth = 0;
while (node.$ === CID(Node)) {
Expand All @@ -12,7 +12,7 @@ function exchange(tree) {
if (node.$ !== CID(Leaf) || node.n !== input || depth !== 20000) {
throw new Error("invalid Leaf at depth " + depth);
}
node.n = 987654321n;
node.n = 987654321;
return tree;
}

Expand Down
56 changes: 0 additions & 56 deletions tests/io/marshal_nat_bigint.bend

This file was deleted.

52 changes: 52 additions & 0 deletions tests/io/marshal_nat_number.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# io (io_marshal_nat_number): a Nat crosses the effect seam as the
# host's number, unconverted, in every lane: the answers 0 and 5 land
# as zero and a successor, and a negative number, which breaks the
# host's side of the contract, lands raw and matches as a successor;
# the interpreter and compiled JS print the same three tags.
import Base

law zero:
IO(Nat)

def zero():
import "./marshal_nat_number.js"

law five:
IO(Nat)

def five():
import "./marshal_nat_number.js"

law neg:
IO(Nat)

def neg():
import "./marshal_nat_number.js"

law tag_of:
for n: Nat
String

def tag_of(n):
match n:
case 0n:
"zero"
case 1n+p:
"succ"

law main:
IO(U32)

def main():
do IO<U32>:
a : Nat <- zero()
b : Nat <- five()
c : Nat <- neg()
u : Unit <- IO.print(tag_of(a))
v : Unit <- IO.print(tag_of(b))
w : Unit <- IO.print(tag_of(c))
return 0

#|zero
#|succ
#|succ
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
function zero() {
return 0n;
return 0;
}
function five() {
return 5n;
return 5;
}
function neg() {
return -3n;
return -3;
}

io_eff(CID(zero), zero);
Expand Down
10 changes: 4 additions & 6 deletions tests/io/marshal_nat_pair.bend
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# io (io_marshal_nat_pair): a Nat in a pair crosses the effect seam as
# a BigInt at any depth. A pair's second half is a type family, and the
# marshal walk took it for no Nat: it found U32 & Nat's through the
# Sigma's fields, but in U32 & U32 & Nat it had seen Sigma already, so
# that Nat crossed as a number. Both effects answer the BigInt's typeof.
# a number at any depth, the same as a Nat on its own. Both effects
# answer its typeof.
import Base

law top:
Expand All @@ -29,5 +27,5 @@ def main():
u : Unit <- IO.print(a)
IO.print(b)

#|bigint
#|bigint
#|number
#|number
2 changes: 1 addition & 1 deletion tests/io/marshal_native_forms.bend
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# io (io_marshal_native_forms): the native forms cross the effect
# seam canonically, both ways -- a Nat as a BigInt, a Bool as a
# seam canonically, both ways -- a Nat as a number, a Bool as a
# boolean, a Cmp by its tag through the generic ctor lane. The legs
# escalate as io_marshal_char_type does: direct answers, an answer
# inside a Maybe field, a round trip through an argument -- and both
Expand Down
8 changes: 4 additions & 4 deletions tests/io/marshal_native_forms.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
function five() {
return 5n;
return 5;
}
function zero() {
return 0n;
return 0;
}
function wrap() {
return {$: CID(Some), value: 9n};
return {$: CID(Some), value: 9};
}
function dub(n) {
return n * 2n;
return n * 2;
}
function flip(b) {
return !b;
Expand Down
Loading