Skip to content
Merged
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
231 changes: 231 additions & 0 deletions crates/perry-codegen/src/lower_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3359,6 +3359,13 @@ pub(super) fn lower_fetch_native_method(
let promise = blk.call(I64, "js_response_blob", &[(DOUBLE, &recv_handle)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
// Issue #237: response.body — returns ReadableStream over the
// buffered body bytes. Property access lowers as a zero-arg
// method call here, same as response.headers above.
"body" => {
let h = ctx.block().call(DOUBLE, "js_response_body", &[(DOUBLE, &recv_handle)]);
return Ok(Some(h));
}
_ => return Ok(None),
}
}
Expand Down Expand Up @@ -3430,6 +3437,230 @@ pub(super) fn lower_fetch_native_method(
);
return Ok(Some(new_handle));
}
// Issue #237: blob.stream() — returns ReadableStream over the
// blob's bytes. Single-chunk; closes after one read.
"stream" => {
let h = ctx.block().call(DOUBLE, "js_blob_stream", &[(DOUBLE, &recv_handle)]);
return Ok(Some(h));
}
_ => return Ok(None),
}
}

// ─────────────────────────────────────────────────────────────────
// Web Streams API (issue #237)
// The receivers are numeric registry-id handles carried as f64,
// mirroring the Blob/Response handle ABI. Locals are tagged
// (module, class_name) by `register_native_instance` in
// `destructuring.rs`.
// ─────────────────────────────────────────────────────────────────

if module == "readable_stream" {
let recv_handle = lower_expr(ctx, recv)?;
match method {
"getReader" => {
let h = ctx.block().call(DOUBLE, "js_readable_stream_get_reader", &[(DOUBLE, &recv_handle)]);
return Ok(Some(h));
}
"cancel" => {
let reason = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let blk = ctx.block();
let promise = blk.call(I64, "js_readable_stream_cancel", &[(DOUBLE, &recv_handle), (DOUBLE, &reason)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"tee" => {
let h = ctx.block().call(DOUBLE, "js_readable_stream_tee", &[(DOUBLE, &recv_handle)]);
return Ok(Some(h));
}
"pipeTo" => {
let dest = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let blk = ctx.block();
let promise = blk.call(I64, "js_readable_stream_pipe_to", &[(DOUBLE, &recv_handle), (DOUBLE, &dest)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"pipeThrough" => {
// pipeThrough(transform) — transform has .readable / .writable.
// We need both sub-handles. Lower the transform once, then
// call js_transform_stream_writable / _readable to extract.
let transform = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let writable = ctx.block().call(DOUBLE, "js_transform_stream_writable", &[(DOUBLE, &transform)]);
let readable = ctx.block().call(DOUBLE, "js_transform_stream_readable", &[(DOUBLE, &transform)]);
let new_h = ctx.block().call(
DOUBLE,
"js_readable_stream_pipe_through",
&[(DOUBLE, &recv_handle), (DOUBLE, &writable), (DOUBLE, &readable)],
);
return Ok(Some(new_h));
}
"locked" => {
let v = ctx.block().call(DOUBLE, "js_readable_stream_locked", &[(DOUBLE, &recv_handle)]);
return Ok(Some(v));
}
// ReadableStreamDefaultController on the same handle:
"enqueue" => {
let chunk = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let v = ctx.block().call(DOUBLE, "js_readable_stream_controller_enqueue", &[(DOUBLE, &recv_handle), (DOUBLE, &chunk)]);
return Ok(Some(v));
}
"close" => {
let v = ctx.block().call(DOUBLE, "js_readable_stream_controller_close", &[(DOUBLE, &recv_handle)]);
return Ok(Some(v));
}
"error" => {
let reason = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let v = ctx.block().call(DOUBLE, "js_readable_stream_controller_error", &[(DOUBLE, &recv_handle), (DOUBLE, &reason)]);
return Ok(Some(v));
}
"desiredSize" => {
let v = ctx.block().call(DOUBLE, "js_readable_stream_controller_desired_size", &[(DOUBLE, &recv_handle)]);
return Ok(Some(v));
}
_ => return Ok(None),
}
}

if module == "readable_stream_reader" {
let recv_handle = lower_expr(ctx, recv)?;
match method {
"read" => {
let blk = ctx.block();
let promise = blk.call(I64, "js_reader_read", &[(DOUBLE, &recv_handle)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"releaseLock" => {
let v = ctx.block().call(DOUBLE, "js_reader_release_lock", &[(DOUBLE, &recv_handle)]);
return Ok(Some(v));
}
"cancel" => {
let reason = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let blk = ctx.block();
let promise = blk.call(I64, "js_reader_cancel", &[(DOUBLE, &recv_handle), (DOUBLE, &reason)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"closed" => {
let blk = ctx.block();
let promise = blk.call(I64, "js_reader_closed", &[(DOUBLE, &recv_handle)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
_ => return Ok(None),
}
}

if module == "writable_stream" {
let recv_handle = lower_expr(ctx, recv)?;
match method {
"getWriter" => {
let h = ctx.block().call(DOUBLE, "js_writable_stream_get_writer", &[(DOUBLE, &recv_handle)]);
return Ok(Some(h));
}
"abort" => {
let reason = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let blk = ctx.block();
let promise = blk.call(I64, "js_writable_stream_abort", &[(DOUBLE, &recv_handle), (DOUBLE, &reason)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"close" => {
let blk = ctx.block();
let promise = blk.call(I64, "js_writable_stream_close", &[(DOUBLE, &recv_handle)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"locked" => {
let v = ctx.block().call(DOUBLE, "js_writable_stream_locked", &[(DOUBLE, &recv_handle)]);
return Ok(Some(v));
}
_ => return Ok(None),
}
}

if module == "writable_stream_writer" {
let recv_handle = lower_expr(ctx, recv)?;
match method {
"write" => {
let chunk = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let blk = ctx.block();
let promise = blk.call(I64, "js_writer_write", &[(DOUBLE, &recv_handle), (DOUBLE, &chunk)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"close" => {
let blk = ctx.block();
let promise = blk.call(I64, "js_writer_close", &[(DOUBLE, &recv_handle)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"abort" => {
let reason = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let blk = ctx.block();
let promise = blk.call(I64, "js_writer_abort", &[(DOUBLE, &recv_handle), (DOUBLE, &reason)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"releaseLock" => {
let v = ctx.block().call(DOUBLE, "js_writer_release_lock", &[(DOUBLE, &recv_handle)]);
return Ok(Some(v));
}
"closed" => {
let blk = ctx.block();
let promise = blk.call(I64, "js_writer_closed", &[(DOUBLE, &recv_handle)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"ready" => {
let blk = ctx.block();
let promise = blk.call(I64, "js_writer_ready", &[(DOUBLE, &recv_handle)]);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"desiredSize" => {
let v = ctx.block().call(DOUBLE, "js_writer_desired_size", &[(DOUBLE, &recv_handle)]);
return Ok(Some(v));
}
_ => return Ok(None),
}
}

if module == "transform_stream" {
let recv_handle = lower_expr(ctx, recv)?;
match method {
"readable" => {
let v = ctx.block().call(DOUBLE, "js_transform_stream_readable", &[(DOUBLE, &recv_handle)]);
return Ok(Some(v));
}
"writable" => {
let v = ctx.block().call(DOUBLE, "js_transform_stream_writable", &[(DOUBLE, &recv_handle)]);
return Ok(Some(v));
}
_ => return Ok(None),
}
}
Expand Down
104 changes: 104 additions & 0 deletions crates/perry-codegen/src/lower_call/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,110 @@ pub(super) fn lower_builtin_new(
Ok(Some(handle))
}

// Issue #237: Web Streams API constructors. Source / sink / transform
// objects accept `start` / `pull` / `cancel` / `write` / `close` /
// `abort` / `transform` / `flush` callbacks; missing ones are passed
// as TAG_UNDEFINED so the runtime can no-op cleanly.
"ReadableStream" => {
let mut start = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let mut pull = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let mut cancel = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let mut hwm = double_literal(1.0);
if !args.is_empty() {
if let Some(props) = extract_options_fields(ctx, &args[0]) {
for (k, vexpr) in &props {
match k.as_str() {
"start" => { start = lower_expr(ctx, vexpr)?; }
"pull" => { pull = lower_expr(ctx, vexpr)?; }
"cancel" => { cancel = lower_expr(ctx, vexpr)?; }
_ => { let _ = lower_expr(ctx, vexpr)?; }
}
}
} else {
let _ = lower_expr(ctx, &args[0])?;
}
}
if args.len() >= 2 {
if let Some(qprops) = extract_options_fields(ctx, &args[1]) {
for (k, vexpr) in &qprops {
if k == "highWaterMark" { hwm = lower_expr(ctx, vexpr)?; }
}
}
}
let h = ctx.block().call(
DOUBLE,
"js_readable_stream_new",
&[(DOUBLE, &start), (DOUBLE, &pull), (DOUBLE, &cancel), (DOUBLE, &hwm)],
);
Ok(Some(h))
}

"WritableStream" => {
let mut write = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let mut close = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let mut abort = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let mut hwm = double_literal(1.0);
if !args.is_empty() {
if let Some(props) = extract_options_fields(ctx, &args[0]) {
for (k, vexpr) in &props {
match k.as_str() {
"write" => { write = lower_expr(ctx, vexpr)?; }
"close" => { close = lower_expr(ctx, vexpr)?; }
"abort" => { abort = lower_expr(ctx, vexpr)?; }
_ => { let _ = lower_expr(ctx, vexpr)?; }
}
}
} else {
let _ = lower_expr(ctx, &args[0])?;
}
}
if args.len() >= 2 {
if let Some(qprops) = extract_options_fields(ctx, &args[1]) {
for (k, vexpr) in &qprops {
if k == "highWaterMark" { hwm = lower_expr(ctx, vexpr)?; }
}
}
}
let h = ctx.block().call(
DOUBLE,
"js_writable_stream_new",
&[(DOUBLE, &write), (DOUBLE, &close), (DOUBLE, &abort), (DOUBLE, &hwm)],
);
Ok(Some(h))
}

"TransformStream" => {
let mut transform = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let mut flush = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let mut hwm = double_literal(1.0);
if !args.is_empty() {
if let Some(props) = extract_options_fields(ctx, &args[0]) {
for (k, vexpr) in &props {
match k.as_str() {
"transform" => { transform = lower_expr(ctx, vexpr)?; }
"flush" => { flush = lower_expr(ctx, vexpr)?; }
_ => { let _ = lower_expr(ctx, vexpr)?; }
}
}
} else {
let _ = lower_expr(ctx, &args[0])?;
}
}
if args.len() >= 2 {
if let Some(qprops) = extract_options_fields(ctx, &args[1]) {
for (k, vexpr) in &qprops {
if k == "highWaterMark" { hwm = lower_expr(ctx, vexpr)?; }
}
}
}
let h = ctx.block().call(
DOUBLE,
"js_transform_stream_new",
&[(DOUBLE, &transform), (DOUBLE, &flush), (DOUBLE, &hwm)],
);
Ok(Some(h))
}

"Promise" => {
// `new Promise((resolve, reject) => { ... })` — the runtime's
// `js_promise_new_with_executor` takes the closure, allocates
Expand Down
Loading
Loading