Commit Graph
8945 Commits
Author SHA1 Message Date
dotCooCoo 328aa0e340 JavaScript: a RegExp source escapes a line terminator after a backslash, and Date.parse reads its words in place
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.
2026-11-07 20:33:40 -08:00
dotCooCoo 971a0d6626 JavaScript: a backreference takes fuel for each 16 units before it compares them 2026-11-07 20:26:40 -08:00
dotCooCoo fab3425f54 Merge main into feat/js-builtins-text 2026-11-07 20:18:06 -08:00
dotCooCoo 52e94d4f8a JavaScript: compiling a pattern charges the room its lists reserve
- 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.
2026-11-07 20:12:42 -08:00
BFlat a7733acc7e Merge pull request #5984 from BOXELS/fix/5980-nssavepanel-null
#5980: macOS save sheet uses alloc/init instead of +savePanel
2026-11-08 12:00:58 +08:00
dotCooCoo 3d1ce948bc Docs: the JavaScript text built-ins line matches the radix output and matcher memory 2026-11-07 19:45:57 -08:00
dotCooCoo c4aa5cec8a Merge feat/js-interpreter into feat/js-builtins-text 2026-11-07 19:34:34 -08:00
dotCooCoo cea958b782 JavaScript: text built-ins hold their working memory against the heap limit
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.
2026-11-07 19:29:53 -08:00
dotCooCoo c7baa679d9 JavaScript: apply takes fuel for each argument it reads
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.
2026-11-07 19:02:53 -08:00
dotCooCoo 02fffcd00c JavaScript: String, Number, RegExp and Date built-ins
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.
2026-11-07 18:24:27 -08:00
dotCooCoo aa3bcc4c77 JavaScript: each NativeError prototype is an Error object
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.
2026-11-07 17:22:26 -08:00
dotCooCoo 263c7cb926 JavaScript: arguments from apply stay alive while a native function runs
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.
2026-11-07 17:10:48 -08:00
Luis 21249399c6 Release: refresh contributor credits for each release (#5997) (#5998)
About > Contributors still showed an old snapshot, because nothing
regenerated contributors/contributors.json before a release. Opt in to
the shared refresh from craft-common-actions: a read-only credits job
regenerates the snapshot for the release commit, and the shared release
workflow installs it before every source build. The snapshot is not
committed back.
2026-11-08 02:07:42 +01:00
dotCooCoo 0b936bf60e JavaScript: the script a realm runs does not count against the re-entry limit
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.
2026-11-07 15:29:40 -08:00
dotCooCoo 5ebf32e973 JavaScript: shrinking an array takes fuel for each property
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.
2026-11-07 15:24:53 -08:00
dotCooCoo 44c17c4e9a JavaScript: the test262 runner reports a failed assertion's message
The harness's assert.js formats failure messages with the global String, which the interpreter does not provide yet, so the four failing runs reported "ReferenceError: String is not defined". The runner now defines a stand-in that converts with +, and the failures name the assertion (the completion value tests expect undefined where ECMA-262 5.1 section 12.6.3 gives 4).
2026-11-07 15:18:55 -08:00
dotCooCoo cf1162cf95 Merge feat/js-parser into feat/js-interpreter 2026-11-07 15:04:39 -08:00
dotCooCoo 4d8bee277c JavaScript: for-in takes fuel for each property and reserves heap for its names
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.
2026-11-07 14:59:24 -08:00
dotCooCoo 70cfdcb43d JavaScript: kept code and function source text count toward the heap
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.
2026-11-07 14:39:21 -08:00
dotCooCoo dcccbad29c JavaScript: hexadecimal and octal numbers round once to the nearest value
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.
2026-11-07 14:18:23 -08:00
dotCooCoo ea319ebcda JavaScript: identifiers follow the Unicode categories of section 7.6, and label and parameter checks are linear
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.
2026-11-07 14:09:25 -08:00
dotCooCoo 47db0b7a8a JavaScript: declarations check the heap before they are bound
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.
2026-11-07 14:07:07 -08:00
dotCooCoo e1cc8d5de6 JavaScript: names and host error messages count toward the heap when they are made
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.
2026-11-07 13:49:13 -08:00
dotCooCoo dfd0b1c949 JavaScript: a finally block keeps the try block's completion value, and host strings count toward the heap
In script and eval code a finally block that ends normally leaves the completion value of its try or catch block (ECMA-262 5.1 section 12.14), so try { 1; } finally { 2; } completes with 1. A new GetCompletion instruction saves the value when the finally block starts, and SetCompletion sets it back when the block ends normally; a break, continue or throw out of the finally block still replaces it.

A string returned by a host function is charged to the heap, and one that does not fit after a collection ends the run with the heap limit.
2026-11-07 13:32:53 -08:00
dotCooCoo d16ff70fd3 JavaScript: a collection counts the code units of property and binding names
A collection counted each property or binding at a fixed 64 bytes and
never counted the name's own buffer. Once the string that produced a long
name became garbage, the name kept its buffer alive while the estimate
dropped it, so a script could hold megabytes of names past the heap
limit. trace_object and trace_env now add each name's buffer, counting a
buffer that a flat string value or another name shares once.
2026-11-07 13:19:19 -08:00
dotCooCoo f045adba2b JavaScript: a function made by the Function constructor does not bind the name anonymous
The constructor compiles its source as (function anonymous(...) {...}), and the
compiled function bound anonymous to itself as a named function expression does.
That binding shadowed a global variable of the same name. ES5.1 section 15.3.2.1
creates the function with the global environment as its scope and no name binding,
so the constructed function now leaves the name unbound.
2026-11-07 13:09:41 -08:00
dotCooCoo e85cecfa99 JavaScript: for-in over a String object checks the heap before listing its indices
Listing a String object's index names for for-in built one name per UTF-16 unit before any
limit check, so a string within the heap limit could make the loop allocate many times that
limit. The heap budget for all the names is now checked first, after a collection if needed,
and each name takes a unit of fuel. A wrapper made by ToObject stays rooted while the names
are listed.
2026-11-07 13:02:26 -08:00
dotCooCoo 5d61122b77 JavaScript: a strict realm runs scripts as strict mode code, and apply unroots its arguments when one throws
Realm::run compiles the script with the realm's Options::strict instead of always as sloppy code, so a realm made with strict: true rejects with statements and calls functions with an undefined this. Indirect eval and Function keep their own strictness (ES5.1 10.4.2, 15.3.2.1).

Function.prototype.apply drops the temporary GC roots it holds for the argument values when an index getter throws, as it already did when reading succeeds. Before, a caught throw left them rooted until the run ended, so a loop of such calls could reach the heap limit.
2026-11-07 12:54:06 -08:00
dotCooCoo ba9e78ebfd JavaScript: a run clears the last uncaught exception and checks source length before encoding
Realm::run now resets the exception that bind_last_thrown binds, so a run
that completes, reaches a limit or does not parse leaves it undefined. The
script source, the text built by Function and the strings given to eval are
checked against the run's remaining max_source_units before they are encoded
or flattened, so an oversized source is rejected without being copied.
2026-11-07 12:45:02 -08:00
dotCooCoo d026e6ae26 JavaScript: interpreter with a garbage-collected heap and execution limits
vectorcraft-js gains a Realm that runs ECMAScript 5.1 scripts. The parsed
script compiles to stack-machine bytecode, a verifier checks every jump
target, handler, constant index and stack depth before the code runs, and
the interpreter keeps JavaScript calls on its own frame stack. Closures,
this, arguments, with, direct and indirect eval, new Function,
try/catch/finally, labeled break and continue, and for-in in insertion
order follow sections 10 to 14 of ECMA-262 5.1. Variable references are
resolved before the right-hand side of an assignment runs (11.13).

Objects and environments live in an arena with a mark-and-sweep
collector and index handles. Built-ins are limited to Object and
Object.prototype's basic methods, Function with call and apply, eval
and the error constructors.

Every run is bounded by fuel, heap bytes, frames, nested host calls and
an interrupt flag another thread can set, with smaller defaults in the
wasm build. A run that reaches a limit ends with a limit error that a
script's try cannot catch. Source passed to eval and Function counts
against Options::max_source_units.

cargo xtask test262 also runs the suite's ES5 test/language tests that
need only these built-ins and prints their counts apart from the parse
counts. Nothing in the app calls the interpreter yet.
2026-11-07 12:36:48 -08:00
dotCooCoo 6bb3d3dfbe JavaScript: a for-in variable initializer is a SyntaxError in strict mode code
Current test262 (ECMA-262 2017 Annex B.3.6) accepts `for (var x = 1 in o)` only outside strict
mode code. The parser now rejects the initializer in strict code, with the error at `in`.
2026-11-07 10:51:53 -08:00
dotCooCoo 05c1be3548 JavaScript: parser for ES5 scripts, with cargo xtask test262
New crate vectorcraft-js reads ECMAScript 5.1 source into a syntax tree
with source positions: a UTF-16 lexer (line and column tracking, automatic
semicolon insertion, the regexp and division goals), preprocessor lines
read as directive tokens, a recursive-descent parser to an arena tree, and
the ES5 early errors, strict mode included. Nesting deeper than
Options::max_depth (200 by default) gives a SyntaxError with its line and
column rather than overflowing the stack. Nothing runs scripts yet.

cargo xtask test262 downloads test262 at a pinned commit, checks its
SHA-256, unpacks it into the gitignored corpus/ folder and runs the ES5
syntax tests. A differential test in the engine compares the lexer with
scriptcheck on line counts, directives and unterminated literals.
2026-11-07 10:29:51 -08:00
BFlat ff9deee46d Merge pull request #6015 from helenkwok/web/page-api
vectorcraft-web: window.vectorcraft takes its request functions from craft-web-api
2026-11-07 10:32:48 +08:00
BFlat 2b13421da5 Merge pull request #6012 from dotCooCoo/fix/ai-legacy-vertical-type
.ai import: legacy type keeps its writing direction, Tate-chu-yoko runs and upright Roman characters
2026-11-07 10:32:31 +08:00
BFlat c54679d4a7 Merge pull request #5985 from dotCooCoo/fix/text-story-current-thread
Type: story-wide panel changes follow the story's current thread
2026-11-07 10:32:10 +08:00
Helen Kwok f9bf685cbd vectorcraft-web: window.vectorcraft takes its request functions from craft-web-api
request, execute, commands and screenshot move onto the shared crate (storytold/craft-libs#413): a timeout on every call, the screenshot's PNG also in data, and docInspect and uiInspect beside them. A call the page gave up on gives up its request if the app has not started it, so the app skips it. openFile, captureDownloads, files, readFile and inspect stay VectorCraft's own, on the same object.
2026-11-07 09:18:00 +07:00
dotCooCoo fe9d62e05e .ai import: legacy type keeps its writing direction, Tate-chu-yoko runs and upright Roman characters
The legacy reader skipped `TV`, `Tv`, `Ty` and `TY` of section 15.3.4 of
the version 7 file format specification, so type set with `1 TV` opened
horizontal.

- `TV` sets a text object's writing direction. VectorCraft keeps one
  direction for each text object, the one in force at its first
  character, with a warning when a later character has another.
- `Ty` marks a Kumi block as a Tate-chu-yoko run, which is VectorCraft's
  own mapping, and `TY` does the same in horizontal type. A `TY` block in
  vertical type opens unmarked. Two blocks with nothing between them open
  as one block.
- `Tv` sets Standard Vertical Roman Alignment.

All four are modal, and an operand other than 0 or 1 leaves the value as
it was.

Docs: docs/file-format-parity.md.
2026-11-07 09:17:53 +07:00
dotCooCoo 1c2a8c7f7a Type: Justification after Select All covers a frame whose thread broke
After Select All in threaded text, the Justification dialog kept the edited
frame's selected range when that frame was no longer threaded at OK. A thread
broken after the frame moves the rest of the story into it, so the paragraphs
past the old range kept their values. The dialog now drops the range and
changes every paragraph in that frame.
2026-11-07 09:17:48 +07:00
dotCooCoo 24a42c5bb0 Type: Tabs indent markers apply to every paragraph when threaded text reflows
With mixed indents, the Tabs panel sends one text.setFormat per paragraph,
addressed by frame and byte offset. Threaded text reflows after each step,
so an indent that made earlier paragraphs wrap onto more lines moved text
into the next frame, and the later steps landed on the wrong paragraphs or
on none. The steps now run from the story's last paragraph to its first, so
each step's offset comes before every change already made.
2026-11-07 09:17:48 +07:00
dotCooCoo c592b31a1e Type: Tabs indent markers and Justification follow the whole story
After Select All in threaded text, the Tabs panel's indent markers read
the indents of the edited frame alone. When another frame's paragraphs
had a different left indent, a marker drag changed them by the edited
frame's values, or left them alone. The markers now take every
paragraph of every frame in the story, so each paragraph keeps its own
left indent or first-line position.

The Justification dialog kept the frames of the story from the moment
it opened. OK now acts on the frames in the edited frame's thread when
it is pressed, so a frame that left the thread while the dialog was
open is not changed, and a frame that joined it is.
2026-11-07 09:17:48 +07:00
dotCooCoo 40a46e539d Type: story-wide panel changes follow the story's current thread
After Select All in threaded text, the panels read the frames from the
list saved at Select All. A frame that had left the thread still took
Paragraph, Tabs, Justification and Text Styles changes, a frame added
later did not, and when the first saved frame had left, Character,
OpenType and font changes went to that frame alone.

text_story now reads the frames of the edited frame's thread from the
document. Locked and hidden frames stay in the story.
2026-11-07 09:17:46 +07:00
BFlat 845306373b Merge pull request #6017 from dotCooCoo/fix/ai-drawn-look-count
.ai import: drawn looks in pattern and brush art count in the note
2026-11-07 10:15:26 +08:00
BFlat 936f6034da Merge pull request #6014 from mockydevs/fix/ai-import-eid-heading
EPS/.ai editing data: a transparency set inside a clipping group stays after it
2026-11-07 10:15:10 +08:00
BFlat 7e3601d11c Merge pull request #6011 from mockydevs/fix/ai-import-blend-straight-spine
EPS/.ai editing data: blends with no spine written come in live
2026-11-07 10:14:54 +08:00
BFlat 7db6d41ec0 Merge pull request #5992 from mockydevs/fix/ai-symbols-before-art-styles
EPS/.ai editing data: symbols defined before the art styles take them
2026-11-07 10:14:38 +08:00
dotCooCoo 0d91749c74 .ai import: drawn looks in pattern and brush art count in the note
An object read as its drawn look counted in the import's "came in as
their drawn look" note when it was on the page or in a kept symbol, but
not when it was in a pattern's art or a brush's art. A file whose only
such objects were in a pattern or a brush opened without the note.

- A kept pattern's drawn looks count, once for each pattern name: a
  definition that a later one of the same name replaces no longer counts.
- A brush's art counts once a brush that is kept uses it; art no kept
  brush uses adds nothing.
2026-11-07 09:00:23 +07:00
Don Omondi ed00a8870e EPS/.ai editing data: a transparency set inside a clipping group stays after it
Q restores the painting state but not the transparency (Xy): the writer
sets Xy only when the next object's state differs from the last one it
wrote. Restored at Q, type after a clipping group following a Screen glow
drew in Screen and nearly vanished on a light background.
2026-11-07 09:00:20 +07:00
Don Omondi 95ac8d7cc7 EPS/.ai editing data: blends with no spine written come in live
A blend whose spine was never edited has only its keys in the editing
data, no spine part. Its spine is the straight lines between the keys'
centres in the record's order; read as such, the blend comes in live
instead of as its drawn steps.
2026-11-07 09:00:15 +07:00
Don Omondi 2626791d7d EPS/.ai editing data: symbols defined before the art styles take them
Illustrator 24 writes its symbol definitions ahead of %AI9_BeginArtStyles,
so a styled object inside a symbol found no style and came in as its drawn
look. The pre-pass that finds swatch names now also keeps the first
top-level art styles section, which the reader parses before the art.
2026-11-07 08:59:55 +07:00
BFlat d2b82d6b32 Merge pull request #6005 from dotCooCoo/fix/prefs-set-folder-check
Preferences: prefs.set refuses a missing or unusable recovery or plug-ins folder
2026-11-07 09:57:12 +08:00