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
15 changes: 15 additions & 0 deletions lib/codegen_deno.ml
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,14 @@ let () =
b "ensureDir" (fun a -> Printf.sprintf "__as_ensureDir(%s)" (arg 0 a));
b "readDirNames" (fun a -> Printf.sprintf "__as_readDirNames(%s)" (arg 0 a));
b "statSize" (fun a -> Printf.sprintf "Deno.statSync(%s).size" (arg 0 a));
b "statIsFile" (fun a -> Printf.sprintf "Deno.statSync(%s).isFile" (arg 0 a));
b "statIsDirectory" (fun a -> Printf.sprintf "Deno.statSync(%s).isDirectory" (arg 0 a));
b "bytesLength" (fun a -> Printf.sprintf "(%s).length" (arg 0 a));
b "bytesByteAt" (fun a -> Printf.sprintf "(%s)[%s]" (arg 0 a) (arg 1 a));
b "bytesAsciiSlice" (fun a -> Printf.sprintf "String.fromCharCode(...(%s).slice(%s, %s))" (arg 0 a) (arg 1 a) (arg 2 a));
(* `import.meta.url` — only legal at module top level, which the
Deno-ESM backend's output already is. *)
b "importMetaUrl" (fun _ -> "import.meta.url");
b "pathJoin" (fun a -> Printf.sprintf "__as_pathJoin(%s, %s)" (arg 0 a) (arg 1 a));
b "isNotFound" (fun a -> Printf.sprintf "__as_isNotFound(%s)" (arg 0 a));
(* ---- JSON ---- *)
Expand Down Expand Up @@ -1238,6 +1246,13 @@ and gen_try_stmt ctx body catch finally =

and gen_stmt ctx (stmt : stmt) : string =
match stmt with
| StmtLet { sl_pat = PatWildcard _; sl_value; _ } ->
(* `let _ = X` evaluates X for side effects and drops the value.
Emitting `const _ = X;` produces a JS SyntaxError on the second
occurrence in the same scope ("Identifier '_' has already been
declared"). Drop the binding entirely; the bare expression
statement keeps the semantics. *)
gen_expr ctx sl_value ^ ";"
| StmtLet { sl_pat; sl_value; sl_mut; sl_quantity = _; sl_ty = _ } ->
let kw = if sl_mut then "let" else "const" in
let js = kw ^ " " ^ gen_pattern ctx sl_pat ^ " = "
Expand Down
37 changes: 37 additions & 0 deletions stdlib/Deno.affine
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,39 @@ pub extern fn readDirNames(path: String) -> [String];
/// `Deno.statSync(path).size` in bytes.
pub extern fn statSize(path: String) -> Int;

/// `Deno.statSync(path).isFile` — true if `path` is a regular file.
/// Throws on a missing path (pair with `isNotFound` for the absent case).
pub extern fn statIsFile(path: String) -> Bool;

/// `Deno.statSync(path).isDirectory` — true if `path` is a directory.
/// Throws on a missing path (pair with `isNotFound` for the absent case).
pub extern fn statIsDirectory(path: String) -> Bool;

/// Recursive walk under `root` — every file path beneath it, depth-first.
/// Mirrors `std/fs/walk` for the common case (no glob filter; callers
/// filter by extension). Throws on a missing root via `Deno.readDirSync`.
pub extern fn walkRecursive(root: String) -> [String];

// ── Bytes accessors ────────────────────────────────────────────────
//
// The opaque `Bytes` type (a Uint8Array under the hood) gets minimal
// inspectors so AffineScript can peek at file magic, parse fixed-width
// binary headers, and recover ASCII slices without crossing back through
// `readTextFile`. Bounds-check via `bytesLength` — `bytesByteAt` with
// an out-of-range index returns `undefined → NaN` coerced to 0.

/// `b.length` — number of bytes.
pub extern fn bytesLength(b: Bytes) -> Int;

/// `b[i]` — byte value at index `i` (0..255). Bounds-check via `bytesLength`.
pub extern fn bytesByteAt(b: Bytes, i: Int) -> Int;

/// `String.fromCharCode(...b.slice(start, end))` — decode `[start, end)`
/// as a Latin-1 / ASCII-safe string. Each byte becomes one code point
/// (0..255). Use this for ASCII-only headers (PDF magic, MZ, ELF, etc.);
/// for full UTF-8, prefer `readTextFile` directly.
pub extern fn bytesAsciiSlice(b: Bytes, start: Int, end: Int) -> String;

// ── Path ───────────────────────────────────────────────────────────

/// Single-segment join with a `/` separator (idempotent on a trailing
Expand Down Expand Up @@ -121,6 +149,15 @@ pub extern fn dateNow() -> Int;
/// returns epoch millis as `Int`.
pub extern fn dateNowIso() -> String;

// ── Module identity ────────────────────────────────────────────────

/// `import.meta.url` — the absolute URL of the importing module. The JS
/// idiom for "find my own location" (cf. `__dirname` / `__filename`). At
/// Deno-ESM top level, lowers to the bare `import.meta.url` expression;
/// callers parse it (`new URL(...)`/`fileURLToPath`/string split) for
/// directory-relative behaviour.
pub extern fn importMetaUrl() -> String;

// ── CLI ────────────────────────────────────────────────────────────

/// `Deno.args` — command-line arguments (excludes argv[0]).
Expand Down
2 changes: 1 addition & 1 deletion stdlib/string.affine
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fn starts_with(s: String, prefix: String) -> Bool {
}

/// Check if string ends with the given suffix
fn ends_with(s: String, suffix: String) -> Bool {
pub fn ends_with(s: String, suffix: String) -> Bool {
let slen = len(s);
let sfxlen = len(suffix);
if sfxlen > slen {
Expand Down
63 changes: 63 additions & 0 deletions tests/codegen-deno/deno_scripting_part2.affine
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MPL-2.0
// campaign #239 STEP 3 — second batch of Deno-scripting stdlib gaps
// surfaced during STEP 2 ports (panic-attack#82, session-sentinel#25,
// tropical-resource-typing#15, nafa-app#23).
//
// New externs / fixes exercised:
// - statIsFile / statIsDirectory (gap 3)
// - bytesLength / bytesByteAt /
// bytesAsciiSlice (gap 4)
// - importMetaUrl (gap 5)
// - let _ = X wildcard binding emits
// a bare expression statement (gap 7)
//
// Signatures stay in primitives so the harness can stub the host.

use Deno::{ statIsFile, statIsDirectory, bytesLength, bytesByteAt, bytesAsciiSlice, importMetaUrl, readFileBytes };

pub fn classify_path(p: String) -> Int {
if statIsFile(p) {
1
} else {
if statIsDirectory(p) {
2
} else {
0
}
}
}

pub fn first_byte(p: String) -> Int {
let b = readFileBytes(p);
if bytesLength(b) == 0 {
0
} else {
bytesByteAt(b, 0)
}
}

pub fn header_string(p: String, n: Int) -> String {
let b = readFileBytes(p);
let lim = bytesLength(b);
let end = if n > lim { lim } else { n };
bytesAsciiSlice(b, 0, end)
}

pub fn module_url_has_scheme() -> Bool {
let u = importMetaUrl();
// Any well-formed module URL starts with `file:` or `http`.
len(u) > 4
}

fn side(n: Int) -> Int { n + 1 }

pub fn discard_chain() -> Int {
// Three back-to-back `let _ = side(N)` discards. Before the fix this
// tripped JS `SyntaxError: Identifier '_' has already been declared`
// because the AS pattern lowered to `const _ = ...` thrice. Now they
// lower to bare expression statements.
let _ = side(1);
let _ = side(2);
let _ = side(3);
42
}
Loading
Loading