feat(codemode): expand numeric standard library (#35749)
This commit is contained in:
parent
66a8d830aa
commit
1fb2837fd3
5 changed files with 40 additions and 4 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue