`#strict on` and `//@strict on` no longer end the run as unsupported. They
turn on the checks the dollar-object help page gives for `$.strict`
(https://extendscript.docsforadobe.dev/extendscript-tools-features/dollar-object/):
an assignment to a read-only property throws a TypeError, and so does a new
property on an object that takes none.
VectorCraft's own rules: a getter-only property counts as read-only; the
objects that take no new properties are the non-extensible ones
(preventExtensions, seal, freeze, and the frozen vc and $ objects); the last
`#strict` line of the script and its includes sets the whole run, and runs
without one are unchanged; eval and Function take the checks of the code
that compiles them; a file $.evalFile runs keeps its own line's checks for
its code and functions, or takes the caller's without one. The rest of ES5
strict mode stays with 'use strict'.
The js crate carries the setting per compiled source
(Realm::set_strict_writes, and a strict_writes callback on
HostItem::Evaluator), and applies it only to property assignments.
alert(message[, title, errorIcon]), confirm(message[, noAsDflt, title])
and prompt(message, preset[, title]) are globals in a script run. In the
app the run waits at the call for the person's answer, spends no
instructions while it waits, and every dialog has Stop, which ends the
run and puts every document back. A run over MCP or the command line
takes its answers in order from script.run's answers, a list of
{kind, value} records; a missing, mismatched or unused answer ends the
run. The report lists each dialog with its answer in dialogs, with
dialogCount and dialogsTruncated.
The interpreter gains a host call that waits at the call for its
result, from the script's own code only; a dialog called in a callback
of a built-in, a getter or a conversion throws in the app.
Docs: docs/mcp.md, docs/target-app-parity.md.
Before a script runs, `script.run` reads its preprocessor lines (`#` or
`//@` at the start of a line, outside strings and comments):
- `#include` and `//@include` put another file's text in place of the
line, looked for beside the including file, then in each
`#includepath` folder, then in each `JSINCLUDE` folder. A later
`#includepath` replaces the list.
- `#script` names the run unless `name` is given; `#target` takes any
value; `#strict off` is taken. `#strict on`, `#targetengine` and
`#engine` are refused as unsupported, and any other `#name` as
unknown.
`$.evalFile(path, timeout?)` runs a file in the same global environment
and returns its last value, `$.fileName` is the file being run and
`$.includePath` the include folders.
A script run from a file reads these files only in its folder and the
folders below it. Paths with a `..` name, links or junctions that lead
out, device names, streams and network paths are refused; a script
given as text reads none. A run reads at most 64 files 16 levels deep,
32 MiB of source in all and 1,024 paths looked up. An error in an
included or evaluated file is reported at that file's line and column,
and the report's new `file` field names it.
Docs: docs/mcp.md (script.run, Script Console), docs/target-app-parity.md.
Scripts write to a new Script Console with $.write(...values) and
$.writeln(...values), each value as String(value) gives it. vc.log
entries, a run's uncaught error as name:line:column: message, and a
start, end and report line for each run go there too. The report line
gives the run's time, host calls and slowest commands.
- The console keeps at most 2,048 lines and 1 MiB of text, dropping the
oldest and counting what it dropped. A line is cut at 4,096 bytes and
a script's name at 256 bytes, with a count of cut lines. Line feeds,
carriage returns and CRLF break lines; other control characters show
as Unicode Control Pictures.
- script.run's result and scriptReport gain runTimeMs, timingAvailable,
hostCallCounts, slowestCommands and the run's own console. A failed
run's report has them too.
- script.console and script.console.clear read and empty the console;
ui.copyScriptConsole {first?, last?} copies lines as the panel shows
them.
- The panel lays out only the rows in view, selects with click and
Shift-click, and has Clear, Copy Selected and Copy All. Output goes on
while it is closed, and Clear works during a run.
Docs: docs/mcp.md, docs/gaps.md, docs/roadmap.md,
docs/target-app-parity.md.
- A new test walks every code point from U+0000 to U+10FFFF, surrogates
left out, and compares the start and part checks with Unicode Standard
Annex #31's general-category definition of ID_Start and ID_Continue,
written out in the test. The test also checks the set sizes against the
totals in Unicode 16.0's DerivedCoreProperties (141,269 and 144,541).
- The Other_ID_Continue test covers every code point of the property,
U+136A to U+1370 and the zero width non-joiner and joiner included.
- The zero width non-joiner and joiner are part of the Other_ID_Continue
list in chars.rs instead of a separate case.
is_id_start now accepts the Other_ID_Start characters (U+1885, U+1886,
U+2118, U+212E, U+309B, U+309C), and is_id_part the Other_ID_Continue
characters (U+00B7, U+0387, U+1369 to U+1371, U+19DA, U+30FB, U+FF65),
as Unicode's ID_Start and ID_Continue properties and the ECMAScript
Names and Keywords clause define them. Letters, marks, digits and
connectors that are Pattern_Syntax are refused, which replaces the
U+2E2F special case. Escaped identifier characters go through the same
check. A supplementary letter right after a number is now the
"identifier starts immediately after numeric literal" error.
Letter categories still come from Unicode 16.0, so characters assigned
in later versions are not identifier characters yet.
Docs: docs/mcp.md, docs/target-app-parity.md.
A document a script opens now counts the art in its graph designs and in
the original of the pattern being edited toward the growth allowance.
`Document::art_roots` and `art_roots_mut` list every tree of a document's
art; `any_node`, `edit_nodes_where` and the script's object count use them.
`script.run` checks the depth of `args` without recursion before it copies
or writes them out, so `args` nested 10,000 levels deep is refused with the
place it went past 64 levels instead of overflowing the stack.
New tests: graph-design art and the growth allowance, `args` nested 10,000
levels deep from a script (a RangeError) and from a caller, `run --cmd
script.run` on the command line with a report and with a thrown error, and
a property test that sends arbitrary source through `script.run`.
File › Scripts › Other Script… picks a .js or .jsx file, shows a warning
that can be turned off in Preferences › General, and runs the script
against the open documents through the `vc` object. The new
`script.run` and `script.stop` commands run and stop a script over MCP.
- A run is one transaction. Each document it changed gets one undo step
named Run Script. An uncaught error, a limit or a stop puts every
document, the paint, the clipboard, menu state and preferences back.
- A command that fails inside a run, or whose result is too large to
reach the script, is undone and throws an error the script can catch.
- Files a command wrote stay on disk and the report names them, also
when the run is stopped from the quit question.
- While a script runs, other commands that change anything fail with a
busy error; MCP reports it as JSON-RPC error -32003.
- In the app the run advances one slice per frame with a Stop button in
the status bar. A run that adds a great many objects asks whether to
continue; Stop and Escape end it.
- Commands that change state outside the documents, such as prefs.*,
plugin.* and file.place.queue, are refused to scripts.
Docs: docs/mcp.md, docs/gaps.md, docs/roadmap.md,
docs/target-app-parity.md.
The text a non-string argument converts to stays rooted while the parse runs, so a
collection still counts it. A property name read from the text is charged to the heap
until its property is defined. Boolean.prototype.toString and valueOf are added, so a
Boolean object writes `true` or `false`. The json2 sample no longer includes U+2028:
the polyfill escapes it and section 15.12.3 does not.
A string value the parser has read is held only by the parser until it is stored. The
property reservation can start a collection, which dropped the string from the heap
total. The value now stays rooted across that reservation, for array elements and for
object members. `JSON.parse` also charges the buffer it flattens a concatenated text
into, through the helper `parseInt` and `parseFloat` use.
`[1, 'x', true].toLocaleString()` gave `[object Number],[object String],[object Boolean]`
because the wrappers' prototypes have no `toString` on this branch and the lookup reached
Object.prototype.toString. Object.prototype.toLocaleString now returns the string form of
the object's primitive value when `toString` is not found on the object or on its chain up
to its standard prototype. A `toString` on the object or that prototype is still called.
Object.prototype.toLocaleString passed the primitive this value to
toString; section 15.2.4.3 calls it on ToObject(this), so a strict
toString now sees the object. The array methods' element write reserved
room for a new element even when it only replaced an own property, so a
heap at its limit stopped reverse or sort writes that add nothing.
Installing the built-ins is not bounded by the heap limit: Math and JSON
no longer reserve heap while they install, so a realm made with a tiny
heap_bytes gets every built-in and runs once the limit is raised. If
installing fails, the realm keeps the reason and every run ends with an
internal error instead of running against missing built-ins.
The json2.js test skips only when the built-in check returns false, and
fails on any other result. The parser's progress line says the app does
not run scripts yet.
A realm made with a low fuel or heap limit lost RegExp and Date, because
installing them compiled a pattern under that limit and the error was
dropped. The built-ins now install before the realm's limits apply.
The early pattern check read an octal class escape starting with 4 to 7
as three digits, while the compiler reads two, so `/[\477-z]/` gave a
SyntaxError for a pattern the compiler accepts. The check now reads
class octal escapes with the compiler's own reader.
docs/target-app-parity.md: the interpreter's entry lists the new
built-ins and the current language-test count.
A constructor pattern with a backslash before a line terminator wrote the raw terminator into source and toString, so the text did not read back as a literal; it now writes the terminator's escape, which matches the same unit (ECMA-262 5.1 section 15.10.2.10).
Date.parse no longer collects every word of a text date into a list before it checks them, and reads month, weekday and zone names without copying the word.
- The compiler charges each working list's added capacity before it
grows (capacity doubles), the code that joins alternatives, and the
flattened code with its stack, instead of an estimate from
instruction and fragment counts.
- The scan for capturing groups and the syntax check before compiling
each take one unit of fuel per unit of the pattern.
- Bytes a dropped HeldBytes released leave the heap's total at the
next limit check, not at the next collection.
- The progress line lists the deletable RegExp.prototype flag
properties among the test262 failures.
The buffer that join, JSON.stringify, the URI functions, escape and
unescape write into takes one unit of fuel for every 16 code units
written, so writing a long run of separators or copying a long string
over and over ends with the fuel limit. Its bytes are added back to the
heap total after each collection until the string is finished, and a
native call that ends early gives them back, so a collection while a
string is being built no longer lets the heap estimate drop below what
is in use.
Array.prototype.join now reads and converts the object's length before
it converts the separator, and still converts the separator when the
length is 0, as ECMA-262 5.1 section 15.4.4.5 steps 2 to 6 give them.
Number objects made by ToObject now carry their primitive value, as
String and Boolean objects already did (ECMA-262 5.1 sections 8.6.2
and 9.9). JSON.stringify reads it for a value, a replacer list entry
and the space argument (section 15.12.3, Str step 4, step 4.b.ii and
step 5) when the method that ToNumber or ToString calls first is not
found before the object's own prototype, so JSON.stringify(Object(3))
gives 3 and JSON.stringify(Object('abc')) gives "abc". A valueOf or
toString on the object or on its prototype still runs.
Native code now holds the Rust memory it works in against the heap limit
until it is done with it: a matcher's capture state and stack, a pattern
being compiled, the matches and pieces of a replacement, and the text
Date.parse reads. A collection counts these bytes as live while they are
held.
- replace reads its replacement pattern once into parts that refer to
ranges of the pattern, so literal text is not copied for each match.
Each piece takes fuel for its units, and the result's length is
checked against the heap as each piece is added.
- The regular expression compiler charges heap through its callback as
it builds, including the ranges of a character class, and each class
atom and range takes fuel. A compiled form counts the capacity of its
lists.
- Clearing the groups inside a quantifier and clearing the matcher's
state before each attempt take fuel.
- Flattening a rope in a String, RegExp or Date method reserves the
flat copy on the heap and takes fuel for it.
- A RegExp object's bytes are reserved together with its compiled form,
so a collection between the two does not drop the compiled form from
the heap count.
Section 15.4.4.13 steps 6 and 7 move every element by the argument count, so with no arguments each element is read and put back in place: getters and setters run, and a read-only element throws a TypeError.
Section 15.2.3.4 leaves the order of the names open. An array's length now comes after its indices and other properties, so Object.getOwnPropertyNames([0, 1, 2]) gives 0,1,2,length, as later editions and test262 expect. length is not enumerable, so Object.keys and for-in are unchanged.
Takes #6178's rooting of every native call's arguments, the fuel per argument apply reads and the Error objects as NativeError prototypes. The bound-function and apply arm that rooted arguments only for those calls is removed, since every native call now roots them; the parsing line in docs/target-app-parity.md takes #6178's wording.
Function.prototype.apply reads up to 65,536 elements from an array-like
object. Each element read now takes a unit of fuel, so a long list ends
the run with the fuel limit as other loops in the interpreter do.
Section 15.4.5.1 steps 3.c and 3.d convert the new length once with
ToUint32 and once with ToNumber. [[Put]] reaches that algorithm through
[[DefineOwnProperty]], so a.length = v now calls v's valueOf twice, the
same as Object.defineProperty(a, 'length', {value: v}).
Tests count the valueOf calls on both paths, check that JSON.stringify
converts Number and String objects with ToNumber and ToString, and check
that toLocaleString calls each primitive element's method on the object
ToObject makes.
parseInt collected every digit into a vector outside the heap count and
then copied the digits again for a base-10 parse, and parseFloat copied
its whole decimal prefix the same way, so one call on a long string could
allocate more than the heap limit. parseInt now counts the digits in
place, reads bases other than 10 straight from the string, and both
functions reserve heap for the base-10 copy before they make it. A
concatenation that the global functions flatten is charged to the heap,
as sort keys are.
A realm now has String, Number, RegExp and Date as ECMAScript 5.1
sections 15.5, 15.7, 15.9 and 15.10 define them, with Annex B's substr,
getYear, setYear and toGMTString.
- String: every ES5 prototype method; split, replace, match and search
take strings and RegExps. Each new string's length is checked against
the heap limit before it is built, and scans take fuel.
- Number: toString(radix), toFixed, toExponential and toPrecision
format from the number's exact decimal value.
- RegExp: a backtracking matcher keeps its choice points and capture
state on the heap, within the heap room left, and takes fuel for every
step, so a catastrophic pattern stops at the fuel limit. Patterns
nested past Options::max_depth give a SyntaxError. Flags g, i and m,
lastIndex, exec, test and toString.
- Date: chrono converts days to calendar dates, shifting by whole
400-year cycles past its years. Realm::set_clock and
Realm::set_time_zone set the clock and local time (UTC by default).
Without a hint, a Date converts to a primitive as a string.
cargo xtask test262 also runs the suite's built-ins tests for the four
and prints their counts.
A host function has no own length, so a script can define one with a
fraction, and the bound function's length kept it. Function.prototype.bind
now converts the target's length to an integer before it subtracts the
bound arguments, as later editions specify.
Object.defineProperty, Object.defineProperties and Object.create added
each new property without checking the heap limit, so one call could
take the heap estimate past the limit before the run stopped. The
property's size is now reserved before it is added, with the object and
the descriptor's values kept reachable while a collection runs.
Array.prototype.join wrote the separator once per index between the
elements it found, so new Array(4294967295).join("") ran four billion
empty writes without taking fuel. Separators are now written in one step
per gap, and an empty separator writes nothing.
Object.prototype.toString gives "[object Error]" for TypeError.prototype
and the other NativeError prototypes, as ECMAScript 5.1 section 15.11.7.7
defines them. Before, only Error.prototype had the Error class.
Array.prototype.sort without a comparison function and the property list
of a JSON.stringify replacer array keep a string for each element. A key
that comes from flattening a concatenation is a new buffer: it is now
charged to the heap, and keys that are not elements are rooted so that a
collection counts them. Keys that share an existing string's buffer are
not charged again.
A native function called through apply could run JavaScript that started a
collection, which freed the arguments apply had read from the array-like
object. The native call now roots its receiver and arguments.
The parsing entry in docs/target-app-parity.md no longer says that nothing
runs scripts.
Array methods that store an element (map, filter, slice, splice, concat,
shift, unshift and sort), the JSON.parse reviver walk and JSON.stringify
reserved heap for a value that only native code held. When the heap was
near its limit, that reservation ran a collection that freed the value
before it was stored, and the script later failed with "object out of
range". The value now stays on the temporary roots until it is stored,
and stringify roots an object from toJSON or a replacer before it writes
the separator and key.
vectorcraft-js now provides the ECMAScript 5.1 built-ins: the Object
property-descriptor functions, create, keys, seal and freeze (15.2),
Function.prototype.bind (15.3.4.5), Array with every ES5 method (15.4),
the error types (15.11), Math (15.8), JSON with revivers, replacers and
indentation (15.12), and the global functions with Annex B's escape and
unescape (15.1, B.2). Prototype methods are writable and configurable.
The array methods work on array-like objects and visit only the indices
that exist, so a sparse array of length 2^32-1 costs as much as its
elements, and they see indices a callback adds. join writes a cycle back
to an array as an empty string; JSON.stringify throws a TypeError on a
cycle. JSON parsing, writing and the reviver walk use no native
recursion. Every loop takes fuel and every result is checked against
the heap limit, so new Array(4294967295).join("x") ends with the heap
limit and a huge sort or stringify ends with a limit.
cargo xtask test262 also runs the suite's ES5 built-in tests and fetches
json2.js at a pinned commit, checked against its SHA-256, to compare its
output with the built-in JSON once Date, String.prototype.replace and
RegExp exist.
Limits::reentry bounds runs inside an operation (getters, setters, conversions, indirect eval). The top-level script took one of those slots, so a limit of 0 refused every script and a limit of 1 left no room for a getter. Only indirect eval now counts when it starts a global run.
Assigning a smaller length to an array visits every property in one operation. It now takes fuel for each one before anything is removed, so a run with a small fuel limit or a set interrupt flag stops there and leaves the array unchanged.
Listing the names for a for-in loop now takes a unit of fuel for each
property it visits and reserves heap for the name list before it is
kept, so a run with small limits stops on an object built under larger
ones.
A collection counts each piece of code a closure or frame keeps: its instructions, tables,
names, nested functions and its source text, each shared buffer once. Code that eval and the
Function constructor compile at run time is charged when it is made, after a heap reservation.
Function.prototype.toString reserves and charges the text it copies.
ToNumber on a hexadecimal string folded its digits into a double one at a
time, so a string past 2^53 could come out one unit off the literal with
the same digits. The lexer kept digits exactly in a u128 but rounded per
digit once the value passed 128 bits. Both now call one helper that keeps
the digits exactly while they fit, records whether any later digit is
set, and rounds once.
is_id_start accepts the letter categories (Lu, Ll, Lt, Lm, Lo, Nl) and
is_id_part adds Mn, Mc, Nd and Pc, read with unicode-general-category.
A superscript digit (No) inside an identifier and a mark such as U+09BE
at its start are now SyntaxErrors, and identifiers with marks outside
the old list, such as the Bengali and Tamil viramas, now parse.
Labels in scope are kept in a map, and duplicate labels in a chain and
duplicate parameters of a strict function are found with a hash set, so
long chains, many labeled statements and long parameter lists are
checked in linear time.
Declaration binding instantiation reserves an upper bound of what a script's,
eval's or function's variables and function declarations add to the heap
before making them, and each declaration and parameter takes a unit of fuel.
A long declaration list now ends with the heap limit without growing the
estimate past it.
Realm::set_limits recomputes when the next collection runs, so a lowered heap
limit applies to the next run instead of after the old threshold.
ToNumber skips the same white space and line terminators as the lexer, so
U+180E is no longer white space in a numeric string.
A new property or binding now adds its name's code units to the heap
estimate when it is inserted, so names declared by eval or Function
source move the estimate toward a collection. Function parameter and
catch bindings no longer count their entries twice.
A host function's error message reserves its size in the heap before
the error object is made, so a message larger than the heap ends the
run with the heap limit.
The Function constructor sums its arguments' lengths and checks the
source limit before flattening or copying any of them, and builds the
source from their code units, so a lone surrogate in an argument is
kept.