diff --git a/CHANGELOG.md b/CHANGELOG.md index fd019608..19abc1b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ Detailed changelog for Perry. See CLAUDE.md for concise summaries. +## v0.5.980 — feat(lodash): add `sum` / `mean` / `sumBy` / `meanBy` / `tail` aggregators to the manifest + +**Symptom.** Manifest sweep flagged `_.sum([1,2,3,4])` as missing — `import _ from 'lodash'; _.sum(...)` errored at HIR-lower with `\`lodash.sum\` is not implemented in Perry — see \`perry --print-api-manifest\` for the supported surface`. Same shape for `_.mean`, `_.sumBy`, `_.meanBy`, and `_.tail` (tail's runtime + decl already existed but the manifest + lower_call rows were absent, so call sites couldn't reach the native function). + +**Fix.** Added five new lodash bindings following the existing `chunk`/`compact`/`take`/etc. pattern: + +- `crates/perry-stdlib/src/lodash.rs` — new `js_lodash_sum`, `js_lodash_mean`, `js_lodash_sum_by`, `js_lodash_mean_by` runtime exports. All four iterate over `*mut ArrayHeader` via `js_array_get`/`js_array_length` and accumulate via `JSValue::to_number()` (with `undefined` treated as 0 to match lodash's `baseSum`). `mean`/`meanBy` return `NaN` for empty/null arrays (0/0 case). `sumBy`/`meanBy` accept the property-shorthand iteratee form only (a string key); function iteratees return `NaN` to mirror lodash's "unusable iteratee" behaviour — closure-call plumbing across the FFI boundary is its own follow-up. Property lookup uses `js_object_get_field_by_name`. +- `crates/perry-api-manifest/src/entries.rs` — five new `method_sig` rows (`sum`, `mean`, `sumBy`, `meanBy`, `tail`). The first four return `TypeSpec::Number` so HIR knows the return type without inference; `tail` returns `TypeSpec::Any` like the rest of the array-returning helpers. +- `crates/perry-codegen/src/lower_call.rs` — five new `NativeModSig` rows in the lodash block of `NATIVE_MODULE_TABLE`. `sum`/`mean` take `&[NA_PTR]` → `NR_F64`; `sumBy`/`meanBy` take `&[NA_PTR, NA_F64]` → `NR_F64` (iteratee passed as a NaN-boxed `f64` and re-bit-cast to `JSValue` inside the runtime); `tail` takes `&[NA_PTR]` → `NR_PTR`. +- `crates/perry-codegen/src/runtime_decls.rs` — four new `declare_function` rows for the new symbols (`js_lodash_sum`/`_sum_by`/`_mean`/`_mean_by`). `js_lodash_tail` was already declared. + +**Validation.** `test-files/test_lodash_sum.ts` (new) prints `10 / 2.5 / 3 / 1 / 3` and matches `node --experimental-strip-types` byte-for-byte. Tested via the standard manifest-route (no `compilePackages` config) since the `compilePackages: ["lodash"]` path is still blocked on a separate module-init `TypeError: Cannot read properties of undefined (reading 'prototype')` regression that pre-dates this work (reproducible with just `_.head([1,2,3])` after enabling compilePackages — not in scope here). + +**Files.** +- `crates/perry-stdlib/src/lodash.rs` — new aggregator exports + helper `lodash_to_number`. +- `crates/perry-api-manifest/src/entries.rs` — five new manifest rows. +- `crates/perry-codegen/src/lower_call.rs` — five new `NativeModSig` rows. +- `crates/perry-codegen/src/runtime_decls.rs` — four new symbol decls. +- `test-files/test_lodash_sum.ts` — repro/regression fixture. + ## v0.5.979 — feat(globalThis): expose built-in constructors as properties so `globalThis.Array`, `context.Object`, etc. stop returning `undefined` **Symptom.** PR #963 (v0.5.977) closed the `Function('return this')()` recogniser, so the lodash module-init IIFE no longer threw `TypeError: value is not a function`. The next blocker surfaced one frame down, inside `runInContext`: diff --git a/CLAUDE.md b/CLAUDE.md index b05b51a2..f13e59dd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.979 +**Current Version:** 0.5.980 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 7fab71f5..86fcad06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4905,7 +4905,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "base64", @@ -4960,14 +4960,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.979" +version = "0.5.980" dependencies = [ "serde", ] [[package]] name = "perry-codegen" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "log", @@ -4980,7 +4980,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "perry-hir", @@ -4989,7 +4989,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "perry-hir", @@ -4997,7 +4997,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "perry-dispatch", @@ -5007,7 +5007,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "perry-hir", @@ -5016,7 +5016,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "base64", @@ -5029,7 +5029,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "perry-hir", @@ -5037,7 +5037,7 @@ dependencies = [ [[package]] name = "perry-diagnostics" -version = "0.5.979" +version = "0.5.980" dependencies = [ "serde", "serde_json", @@ -5045,7 +5045,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.979" +version = "0.5.980" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5056,7 +5056,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "clap", @@ -5071,7 +5071,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.979" +version = "0.5.980" dependencies = [ "argon2", "perry-ffi", @@ -5079,7 +5079,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "reqwest", @@ -5088,7 +5088,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.979" +version = "0.5.980" dependencies = [ "bcrypt", "perry-ffi", @@ -5096,7 +5096,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "rusqlite", @@ -5104,7 +5104,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "scraper", @@ -5112,14 +5112,14 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-cron" -version = "0.5.979" +version = "0.5.980" dependencies = [ "chrono", "cron", @@ -5128,7 +5128,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.979" +version = "0.5.980" dependencies = [ "chrono", "perry-ffi", @@ -5136,7 +5136,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "rust_decimal", @@ -5144,7 +5144,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "serde_json", @@ -5152,7 +5152,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "rand 0.8.6", @@ -5160,21 +5160,21 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.979" +version = "0.5.980" dependencies = [ "bytes", "http-body-util", @@ -5188,7 +5188,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.979" +version = "0.5.980" dependencies = [ "lazy_static", "perry-ffi", @@ -5199,7 +5199,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.979" +version = "0.5.980" dependencies = [ "lazy_static", "perry-ext-http-server", @@ -5211,7 +5211,7 @@ dependencies = [ [[package]] name = "perry-ext-http-server" -version = "0.5.979" +version = "0.5.980" dependencies = [ "bytes", "http-body-util", @@ -5230,7 +5230,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.979" +version = "0.5.980" dependencies = [ "lazy_static", "perry-ffi", @@ -5240,7 +5240,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.979" +version = "0.5.980" dependencies = [ "base64", "jsonwebtoken", @@ -5251,7 +5251,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.979" +version = "0.5.980" dependencies = [ "lru", "perry-ffi", @@ -5259,7 +5259,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.979" +version = "0.5.980" dependencies = [ "chrono", "perry-ffi", @@ -5267,7 +5267,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.979" +version = "0.5.980" dependencies = [ "bson", "futures-util", @@ -5279,7 +5279,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.979" +version = "0.5.980" dependencies = [ "chrono", "perry-ffi", @@ -5289,7 +5289,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.979" +version = "0.5.980" dependencies = [ "nanoid", "perry-ffi", @@ -5298,7 +5298,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "rustls", @@ -5309,7 +5309,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.979" +version = "0.5.980" dependencies = [ "lettre", "perry-ffi", @@ -5319,7 +5319,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "sqlx", @@ -5328,7 +5328,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.979" +version = "0.5.980" dependencies = [ "governor", "perry-ffi", @@ -5336,7 +5336,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.979" +version = "0.5.980" dependencies = [ "base64", "image", @@ -5345,14 +5345,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.979" +version = "0.5.980" dependencies = [ "lazy_static", "perry-ffi", @@ -5360,7 +5360,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "uuid", @@ -5368,7 +5368,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.979" +version = "0.5.980" dependencies = [ "perry-ffi", "regex", @@ -5378,7 +5378,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.979" +version = "0.5.980" dependencies = [ "futures-util", "lazy_static", @@ -5389,7 +5389,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.979" +version = "0.5.980" dependencies = [ "flate2", "perry-ffi", @@ -5397,7 +5397,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.979" +version = "0.5.980" dependencies = [ "dashmap 6.1.0", "once_cell", @@ -5406,7 +5406,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "perry-api-manifest", @@ -5420,7 +5420,7 @@ dependencies = [ [[package]] name = "perry-jsruntime" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "deno_core", @@ -5440,7 +5440,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "perry-diagnostics", @@ -5452,7 +5452,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "base64", @@ -5476,7 +5476,7 @@ dependencies = [ [[package]] name = "perry-stdlib" -version = "0.5.979" +version = "0.5.980" dependencies = [ "aes 0.8.4", "aes-gcm", @@ -5546,7 +5546,7 @@ dependencies = [ [[package]] name = "perry-transform" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "perry-hir", @@ -5556,7 +5556,7 @@ dependencies = [ [[package]] name = "perry-types" -version = "0.5.979" +version = "0.5.980" dependencies = [ "anyhow", "thiserror 1.0.69", @@ -5564,11 +5564,11 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.979" +version = "0.5.980" [[package]] name = "perry-ui-android" -version = "0.5.979" +version = "0.5.980" dependencies = [ "itoa", "jni", @@ -5583,7 +5583,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.979" +version = "0.5.980" dependencies = [ "rand 0.8.6", "serde", @@ -5593,7 +5593,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.979" +version = "0.5.980" dependencies = [ "cairo-rs", "dirs 5.0.1", @@ -5612,7 +5612,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.979" +version = "0.5.980" dependencies = [ "block2", "libc", @@ -5627,7 +5627,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.979" +version = "0.5.980" dependencies = [ "block2", "libc", @@ -5645,11 +5645,11 @@ version = "0.1.0" [[package]] name = "perry-ui-testkit" -version = "0.5.979" +version = "0.5.980" [[package]] name = "perry-ui-tvos" -version = "0.5.979" +version = "0.5.980" dependencies = [ "block2", "libc", @@ -5664,7 +5664,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.979" +version = "0.5.980" dependencies = [ "block2", "libc", @@ -5679,7 +5679,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.979" +version = "0.5.980" dependencies = [ "block2", "libc", @@ -5692,7 +5692,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.979" +version = "0.5.980" dependencies = [ "libc", "perry-runtime", @@ -5706,7 +5706,7 @@ dependencies = [ [[package]] name = "perry-updater" -version = "0.5.979" +version = "0.5.980" dependencies = [ "base64", "ed25519-dalek", @@ -5720,7 +5720,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.979" +version = "0.5.980" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 714af7fe..d8fa11bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -190,7 +190,7 @@ opt-level = "s" # Optimize for size in stdlib opt-level = 3 [workspace.package] -version = "0.5.979" +version = "0.5.980" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/crates/perry-api-manifest/src/entries.rs b/crates/perry-api-manifest/src/entries.rs index 6eb7b12b..7c116647 100644 --- a/crates/perry-api-manifest/src/entries.rs +++ b/crates/perry-api-manifest/src/entries.rs @@ -995,6 +995,39 @@ pub static API_MANIFEST: &[ApiEntry] = &[ TypeSpec::Any, ), method_sig("lodash", "size", false, None, &[p_any("p0")], TypeSpec::Any), + method_sig( + "lodash", + "sum", + false, + None, + &[p_any("p0")], + TypeSpec::Number, + ), + method_sig( + "lodash", + "mean", + false, + None, + &[p_any("p0")], + TypeSpec::Number, + ), + method_sig( + "lodash", + "sumBy", + false, + None, + &[p_any("p0"), p_any("p1")], + TypeSpec::Number, + ), + method_sig( + "lodash", + "meanBy", + false, + None, + &[p_any("p0"), p_any("p1")], + TypeSpec::Number, + ), + method_sig("lodash", "tail", false, None, &[p_any("p0")], TypeSpec::Any), method_sig("dayjs", "default", false, None, &[], TypeSpec::Any), method_sig("dayjs", "dayjs", false, None, &[], TypeSpec::Any), method("dayjs", "format", true, None), diff --git a/crates/perry-codegen/src/lower_call.rs b/crates/perry-codegen/src/lower_call.rs index fa2e333a..be13616c 100644 --- a/crates/perry-codegen/src/lower_call.rs +++ b/crates/perry-codegen/src/lower_call.rs @@ -8628,6 +8628,51 @@ const NATIVE_MODULE_TABLE: &[NativeModSig] = &[ args: &[NA_F64], ret: NR_F64, }, + NativeModSig { + module: "lodash", + has_receiver: false, + method: "tail", + class_filter: None, + runtime: "js_lodash_tail", + args: &[NA_PTR], + ret: NR_PTR, + }, + NativeModSig { + module: "lodash", + has_receiver: false, + method: "sum", + class_filter: None, + runtime: "js_lodash_sum", + args: &[NA_PTR], + ret: NR_F64, + }, + NativeModSig { + module: "lodash", + has_receiver: false, + method: "mean", + class_filter: None, + runtime: "js_lodash_mean", + args: &[NA_PTR], + ret: NR_F64, + }, + NativeModSig { + module: "lodash", + has_receiver: false, + method: "sumBy", + class_filter: None, + runtime: "js_lodash_sum_by", + args: &[NA_PTR, NA_F64], + ret: NR_F64, + }, + NativeModSig { + module: "lodash", + has_receiver: false, + method: "meanBy", + class_filter: None, + runtime: "js_lodash_mean_by", + args: &[NA_PTR, NA_F64], + ret: NR_F64, + }, // ========== dayjs ========== // Factory: `import dayjs from 'dayjs'; dayjs()` → method:"default". // Named import: `import { dayjs } from 'dayjs'; dayjs()` → method:"dayjs". diff --git a/crates/perry-codegen/src/runtime_decls.rs b/crates/perry-codegen/src/runtime_decls.rs index 6ac5c828..e7f66e73 100644 --- a/crates/perry-codegen/src/runtime_decls.rs +++ b/crates/perry-codegen/src/runtime_decls.rs @@ -2332,6 +2332,8 @@ pub fn declare_stdlib_ffi(module: &mut LlModule) { module.declare_function("js_lodash_last", DOUBLE, &[I64]); module.declare_function("js_lodash_lower_case", I64, &[I64]); module.declare_function("js_lodash_lower_first", I64, &[I64]); + module.declare_function("js_lodash_mean", DOUBLE, &[I64]); + module.declare_function("js_lodash_mean_by", DOUBLE, &[I64, DOUBLE]); module.declare_function("js_lodash_pad", I64, &[I64, DOUBLE]); module.declare_function("js_lodash_pad_end", I64, &[I64, DOUBLE]); module.declare_function("js_lodash_pad_start", I64, &[I64, DOUBLE]); @@ -2344,6 +2346,8 @@ pub fn declare_stdlib_ffi(module: &mut LlModule) { module.declare_function("js_lodash_split", I64, &[I64, I64]); module.declare_function("js_lodash_start_case", I64, &[I64]); module.declare_function("js_lodash_starts_with", DOUBLE, &[I64, I64]); + module.declare_function("js_lodash_sum", DOUBLE, &[I64]); + module.declare_function("js_lodash_sum_by", DOUBLE, &[I64, DOUBLE]); module.declare_function("js_lodash_tail", I64, &[I64]); module.declare_function("js_lodash_take", I64, &[I64, DOUBLE]); module.declare_function("js_lodash_take_right", I64, &[I64, DOUBLE]); diff --git a/crates/perry-stdlib/src/lodash.rs b/crates/perry-stdlib/src/lodash.rs index ea90a107..9e02e1cc 100644 --- a/crates/perry-stdlib/src/lodash.rs +++ b/crates/perry-stdlib/src/lodash.rs @@ -4,8 +4,8 @@ //! Provides array, object, string, and utility operations. use perry_runtime::{ - js_array_alloc, js_array_get, js_array_length, js_array_push, js_string_from_bytes, - ArrayHeader, JSValue, StringHeader, + js_array_alloc, js_array_get, js_array_length, js_array_push, js_object_get_field_by_name, + js_string_from_bytes, ArrayHeader, JSValue, ObjectHeader, StringHeader, }; use std::collections::HashSet; @@ -878,3 +878,115 @@ pub unsafe extern "C" fn js_lodash_size(collection_f: f64) -> f64 { } 0.0 } + +// ============================================================================ +// Aggregators (sum/mean) — issue #793 follow-up +// ============================================================================ + +/// Coerce a JS array element to f64 per lodash's `baseSum`: skip +/// `undefined` (treated as 0 contribution) and `NaN` is propagated by +/// caller summation. lodash uses `current === current` for the NaN check; +/// here we just lean on f64 NaN arithmetic which preserves NaN propagation. +#[inline] +fn lodash_to_number(v: JSValue) -> f64 { + if v.is_undefined() { + 0.0 + } else { + v.to_number() + } +} + +/// _.sum(array) -> number +/// +/// Sum the numeric values of the array. Returns 0 for null/empty. +#[no_mangle] +pub unsafe extern "C" fn js_lodash_sum(arr_ptr: *mut ArrayHeader) -> f64 { + if arr_ptr.is_null() { + return 0.0; + } + let len = js_array_length(arr_ptr); + let mut sum = 0.0; + for i in 0..len { + sum += lodash_to_number(js_array_get(arr_ptr, i)); + } + sum +} + +/// _.mean(array) -> number +/// +/// Arithmetic mean. lodash returns NaN for empty arrays (0/0). +#[no_mangle] +pub unsafe extern "C" fn js_lodash_mean(arr_ptr: *mut ArrayHeader) -> f64 { + if arr_ptr.is_null() { + return f64::NAN; + } + let len = js_array_length(arr_ptr); + if len == 0 { + return f64::NAN; + } + let mut sum = 0.0; + for i in 0..len { + sum += lodash_to_number(js_array_get(arr_ptr, i)); + } + sum / (len as f64) +} + +/// _.sumBy(collection, iteratee) -> number +/// +/// Like `sum`, but iteratee is invoked per element. Today we only +/// support the property-shorthand form (`'name'`) since native function +/// iteratees would require closure-call plumbing. A function iteratee +/// silently returns NaN to mirror lodash's behaviour when the iteratee +/// is unusable. +#[no_mangle] +pub unsafe extern "C" fn js_lodash_sum_by(arr_ptr: *mut ArrayHeader, iteratee_f: f64) -> f64 { + if arr_ptr.is_null() { + return 0.0; + } + let iteratee = JSValue::from_bits(iteratee_f.to_bits()); + // Property-shorthand only: iteratee must be a string key. + if !iteratee.is_string() { + return f64::NAN; + } + let key_ptr = iteratee.as_pointer::(); + let len = js_array_length(arr_ptr); + let mut sum = 0.0; + for i in 0..len { + let elem = js_array_get(arr_ptr, i); + if elem.is_pointer() { + let obj_ptr = elem.as_pointer::(); + let prop = js_object_get_field_by_name(obj_ptr, key_ptr); + sum += lodash_to_number(prop); + } + } + sum +} + +/// _.meanBy(collection, iteratee) -> number +/// +/// Same restriction as `sumBy`: property-shorthand iteratee only. +#[no_mangle] +pub unsafe extern "C" fn js_lodash_mean_by(arr_ptr: *mut ArrayHeader, iteratee_f: f64) -> f64 { + if arr_ptr.is_null() { + return f64::NAN; + } + let len = js_array_length(arr_ptr); + if len == 0 { + return f64::NAN; + } + let iteratee = JSValue::from_bits(iteratee_f.to_bits()); + if !iteratee.is_string() { + return f64::NAN; + } + let key_ptr = iteratee.as_pointer::(); + let mut sum = 0.0; + for i in 0..len { + let elem = js_array_get(arr_ptr, i); + if elem.is_pointer() { + let obj_ptr = elem.as_pointer::(); + let prop = js_object_get_field_by_name(obj_ptr, key_ptr); + sum += lodash_to_number(prop); + } + } + sum / (len as f64) +} diff --git a/test-files/test_lodash_sum.ts b/test-files/test_lodash_sum.ts new file mode 100644 index 00000000..75b6decd --- /dev/null +++ b/test-files/test_lodash_sum.ts @@ -0,0 +1,6 @@ +import _ from 'lodash'; +console.log(_.sum([1, 2, 3, 4])); // 10 +console.log(_.mean([1, 2, 3, 4])); // 2.5 +console.log(_.sumBy([{n:1},{n:2}], 'n')); // 3 +console.log(_.head([1, 2, 3])); // 1 +console.log(_.last([1, 2, 3])); // 3