Browse Source

feat(codemode): expand numeric standard library (#35749)

Aiden Cline 1 tháng trước cách đây
mục cha
commit
1fb2837fd3

+ 1 - 1
packages/codemode/README.md

@@ -241,7 +241,7 @@ CodeMode executes a deliberately bounded JavaScript subset. It supports:
 - `if`, conditional expressions, `switch`, `for`, `for...of` (arrays, strings, Maps, Sets, including assignment-form destructuring such as `for ([key, value] of entries)`), `for...in` (own keys of plain objects, index strings of arrays, and namespace/tool names of `tools` references - anything else is an error suggesting `for...of` or `Object.keys`, rather than real JS's surprising behavior of indices for strings and zero iterations for Maps/Sets), `while`, and `do...while`.
 - Arrow functions and function declarations with closures, defaults, rest parameters, and destructuring.
 - Optional chaining, nullish coalescing, templates, spread (arrays, strings, Maps, Sets), and `try`/`catch`.
-- Common array, string, number, `Object`, `Math`, and `JSON` operations. Mutating array methods include `push`/`pop`/`shift`/`unshift`/`splice` (removes in place and returns the removed elements)/`fill`/`copyWithin`; array `keys`/`values`/`entries` return **arrays** (matching the Map/Set convention) and work with `for...of` and spread. String methods include `localeCompare` (locale/options arguments ignored), `normalize`, and the `trimLeft`/`trimRight` aliases. `Object.keys` also accepts arrays (index strings, as in JS) and tool references: `Object.keys(tools)` lists the top-level namespaces, including `$codemode`, and `Object.keys(tools.ns)` lists the names at that node (a callable tool enumerates as `[]`; an unknown path is an `UnknownTool` diagnostic). `Object.values`/`Object.entries` on a tool reference fail with a pointer at `Object.keys(tools)` and `tools.$codemode.search`.
+- Common array, string, number, `Object`, `Math`, and `JSON` operations, including primitive-number `valueOf`, the standard non-finite `Number` constants, and host-backed `Math.random`. Mutating array methods include `push`/`pop`/`shift`/`unshift`/`splice` (removes in place and returns the removed elements)/`fill`/`copyWithin`; array `keys`/`values`/`entries` return **arrays** (matching the Map/Set convention) and work with `for...of` and spread. String methods include `localeCompare` (locale/options arguments ignored), `normalize`, and the `trimLeft`/`trimRight` aliases. `Object.keys` also accepts arrays (index strings, as in JS) and tool references: `Object.keys(tools)` lists the top-level namespaces, including `$codemode`, and `Object.keys(tools.ns)` lists the names at that node (a callable tool enumerates as `[]`; an unknown path is an `UnknownTool` diagnostic). `Object.values`/`Object.entries` on a tool reference fail with a pointer at `Object.keys(tools)` and `tools.$codemode.search`.
 - `Date` - `Date.now()`/`Date.parse()`/`Date.UTC()`, `new Date(...)`, the getter methods, and date arithmetic/comparison via the time value. Dates stringify as ISO (`toString` included, for determinism across host timezones).
 - Regular expressions - `/literals/` and `new RegExp(...)` with `test`/`exec` (stateful `lastIndex` for `g`), plus string `match`/`matchAll`/`replace`/`replaceAll`/`split`/`search` with patterns. Match results are arrays carrying `index` and named `groups` as own properties (`input` is omitted). `replace` and `replaceAll` accept function replacers with captures, offset, input, and named groups; callbacks run sequentially, may await tool calls, and have their results coerced to strings. Invalid patterns, invalid flags, and missing-`g` calls fail with catchable errors that say what was wrong and how to fix it (escaping hints, the exact `/pattern/g` to write). Patterns run on the host engine, so pathological backtracking is bounded only by the execution timeout.
 - `Map` and `Set` - construction from entries/arrays/strings, `get`/`set`/`add`/`has`/`delete`/`clear`/`size`/`forEach`, and `keys`/`values`/`entries` returning **arrays** (not iterators).

+ 1 - 1
packages/codemode/codemode.md

@@ -158,7 +158,7 @@ current omissions to implement, not intentional product boundaries.
 - [ ] Add `Object.is` after runtime method and tool references have stable identity semantics.
 - [ ] Add deterministic modern collection conveniences where they improve orchestration: `Object.groupBy`, Set
       composition methods, and `Array.prototype.toSpliced`.
-- [ ] Decide whether nondeterministic `Math.random` and iterable `Math.sumPrecise` belong in the runtime.
+- [ ] Decide whether iterable `Math.sumPrecise` belongs in the runtime.
 - [ ] Refine diagnostics so user throws, expected tool failures, unexpected host/tool defects, and genuine interpreter
       defects are distinguishable without leaking private causes.
 

+ 2 - 0
packages/codemode/src/stdlib/math.ts

@@ -1,6 +1,7 @@
 export const mathConstants = new Set(["PI", "E", "LN2", "LN10", "LOG2E", "LOG10E", "SQRT2", "SQRT1_2"])
 
 export const mathMethods = new Set([
+  "random",
   "max",
   "min",
   "abs",
@@ -40,6 +41,7 @@ export const mathMethods = new Set([
 
 export const invokeMathMethod = (name: string, args: Array<unknown>, node: AstNode): number => {
   if (!mathMethods.has(name)) throw new InterpreterRuntimeError(`Math.${name} is not available in CodeMode.`, node)
+  if (name === "random") return Math.random()
   const nums = args.map((arg) => {
     if (typeof arg !== "number") throw new InterpreterRuntimeError(`Math.${name} expects number arguments.`, node)
     return arg

+ 14 - 2
packages/codemode/src/stdlib/number.ts

@@ -1,6 +1,15 @@
-export const numberMethods = new Set(["toFixed", "toPrecision", "toExponential", "toString"])
+export const numberMethods = new Set(["toFixed", "toPrecision", "toExponential", "toString", "valueOf"])
 
-export const numberConstants = new Set(["MAX_SAFE_INTEGER", "MIN_SAFE_INTEGER", "MAX_VALUE", "MIN_VALUE", "EPSILON"])
+export const numberConstants = new Set([
+  "MAX_SAFE_INTEGER",
+  "MIN_SAFE_INTEGER",
+  "MAX_VALUE",
+  "MIN_VALUE",
+  "EPSILON",
+  "NaN",
+  "POSITIVE_INFINITY",
+  "NEGATIVE_INFINITY",
+])
 
 export const numberStatics = new Set(["isInteger", "isFinite", "isNaN", "isSafeInteger", "parseInt", "parseFloat"])
 
@@ -32,6 +41,9 @@ export const invokeNumberMethod = (value: number, name: string, args: Array<unkn
       result = value.toString(radix)
       break
     }
+    case "valueOf":
+      result = value
+      break
     default:
       throw new InterpreterRuntimeError(`Number method '${name}' is not available in CodeMode.`, node)
   }

+ 22 - 0
packages/codemode/test/stdlib.test.ts

@@ -19,6 +19,28 @@ const error = async (code: string) => {
   return result.error
 }
 
+describe("Number and Math", () => {
+  test("Math.random returns a number in [0, 1)", async () => {
+    expect(await value(`const n = Math.random(); return typeof n === "number" && n >= 0 && n < 1`)).toBe(true)
+  })
+
+  test("Number exposes native non-finite constants", async () => {
+    expect(
+      await value(
+        `return [Number.isNaN(Number.NaN), Number.POSITIVE_INFINITY === Infinity, Number.NEGATIVE_INFINITY === -Infinity]`,
+      ),
+    ).toEqual([true, true, true])
+  })
+
+  test("Number valueOf returns its primitive receiver", async () => {
+    expect(await value(`return (42).valueOf()`)).toBe(42)
+  })
+
+  test("Number valueOf does not enable boxed numbers", async () => {
+    expect((await error(`return new Number(42)`)).kind).toBe("UnsupportedSyntax")
+  })
+})
+
 describe("Date", () => {
   test("Date.now() returns a number", async () => {
     expect(await value(`return typeof Date.now()`)).toBe("number")