Commit Graph
19 Commits
Author SHA1 Message Date
dotCooCoo b1c684e912 Scripts: E4X operations count their temporary storage against the heap limit
Assigning to a named child counts its list of the matching children, and
assigning through an empty XMLList counts the chain of targets it climbs,
before either changes the tree. copy(), toXMLString(), normalize(), the
`..` selector and the depth check on insertion hold the stack they keep
for each open level of the tree. Inserting an XMLList, and assigning one
to an item of an XMLList, hold their copy of the list's items.

copy() reads attributes in place, and toXMLString() reads attributes and
namespace declarations in place, instead of copying the lists. copy()
charges the declarations it copies before it copies them. Deleting by
name or assigning an attribute compacts the remaining links in place
instead of building a second list.

toXMLString() holds the set of prefixes a namespace lookup passes, and
picks a new nsN prefix in one pass over the declarations in scope
instead of rescanning them for each candidate.

The parser holds its open elements and their names, the text it gathers,
each tag's attribute values, the sets that check declarations and
attribute names, and its one copy of an element's namespaces, and it
charges the strings of the namespace declarations it makes. The
allowance it holds from the source's length covers the copy a value
takes as it becomes a string.

Comparing XML values, converting them to strings, assigning an XMLList
to an attribute, and reading a name, a qualifier, a namespace URI or
embedded text from a string built by joining strings flatten it with
flat_charged. An attribute name read from a string reserves its copy,
and an element name shares the string.

Docs: docs/target-app-parity.md.
2026-11-13 06:54:23 -08:00
dotCooCoo 07c6f11abc Merge main into feat/e4x-xml-xpath 2026-11-11 23:31:02 -08:00
dotCooCoo 7222f43ecb Merge main into feat/e4x-xml-xpath 2026-11-11 22:03:32 -08:00
dotCooCoo 56f9b6181e Merge main into fix/e4x-scope-name-assign 2026-11-11 22:03:32 -08:00
dotCooCoo f526c5310c JS: XML and XMLList xpath evaluates XPath 1.0 expressions
xml.xpath(expression[, variables]) evaluates an XPath 1.0 expression
(https://www.w3.org/TR/1999/REC-xpath-19991116/) with the XML value as
the context node and returns the selected nodes as an XMLList, in
document order and without repeats. On an XMLList the expression runs
on each element in turn, and the results are joined.

- All 13 axes and the 27 core functions. id() selects nothing, since
  E4X trees carry no DTD data.
- Each property of `variables` binds the variable of its name. A
  prefix in a name test resolves through the context element's
  in-scope namespaces; `xml` is always bound.
- An expression that breaks the grammar gives a SyntaxError. An
  unknown function, a wrong argument count, an unbound prefix or
  variable, a result that is not a node-set, and a result that holds
  the root or a namespace node give a TypeError.
- The parser and the evaluator keep their state on their own stacks,
  so expressions of any depth use no native stack; nesting past
  Options::max_depth gives "nesting too deep". Traversal stacks, node
  lists, strings and the values that string functions build count
  against the heap limit, and every node visited and string read takes
  fuel.

Docs: docs/target-app-parity.md (E4X line), crate doc in lib.rs.
2026-11-11 21:01:32 -08:00
dotCooCoo b172e3672d JavaScript: for each evaluates its target before reading the value
A for each loop read the property's value before evaluating its target,
so a target such as (o.p = 2, t).v, t[(o.p = 3, 'w')] or a bare
@[(o.p = 2, 'k')] stored the value from before the target ran. The
target's object and name are now evaluated first and the value is read
after them (ECMA-357 section 12.3).
2026-11-11 20:39:02 -08:00
dotCooCoo db97cc1a8f JavaScript: a bare E4X name inside with or a filter can be assigned
With Options::e4x set, an E4X name standing alone (@a, @ns::a, @[expr],
ns::a, *) is now a valid target for =, compound assignment, ++, --,
delete and for/for each loops, as well as a read. The target is the
nearest XML or XMLList value in scope whose [[HasProperty]] accepts the
name (ECMA-357 section 11.1). The name is evaluated once, and the
target resolves before the right-hand side runs.

When no XML value in scope holds the name, a read or write throws a
ReferenceError and creates no child or attribute. A read no longer
falls back to the first XML value in scope, so a filter such as
list.(@id == '1') throws when an item has no @id and no outer XML
value holds one, instead of treating that item as not matching.
2026-11-11 19:35:59 -08:00
dotCooCoo 781357921e Scripts: E4X checks the XML declaration and takes fuel for every run the parser scans
An XML declaration at the start of XML or XMLList text was skipped up to
the next "?>" without being read. It is now checked against XML 1.0's
grammar (https://www.w3.org/TR/xml/#NT-XMLDecl): a version of "1." and
digits, then an optional encoding name and an optional standalone "yes"
or "no", in that order, with optional white space before "?>". Any other
declaration gives "SyntaxError: invalid XML declaration", and a character
XML does not allow gives "invalid character in XML text". White space
before the declaration is still accepted.

The parser now takes fuel for the length of the declaration and the
white space before it, element and attribute names, white space in open
and closing tags, and the text it searched when an attribute value, CDATA
section, comment or processing instruction is not closed, or when "]]>"
or "<" stops a text run or attribute value. A long run in any of these
used the same fuel as a short one.
2026-11-11 19:35:39 -08:00
dotCooCoo a354503683 JavaScript: XML namespace lookups take fuel for each ancestor they climb
`inScopeNamespaces()`, `namespaceDeclarations()` and `namespace()` walk
from the node up to its root. The walk cloned each ancestor's whole node,
children and attributes included, to read its namespaces, and charged
fuel only for the namespaces it found, so a deep tree with no declared
namespaces cost no fuel. It now takes one unit of fuel per ancestor,
copies only the namespace list and parent link, and checks prefixes
already seen with a set. `namespaceDeclarations()` compares the node's
declarations with the ancestors' through a map instead of a scan.
2026-11-11 14:54:26 -08:00
dotCooCoo 28042c2e92 JavaScript: test that new XML copies a list item's attributes and children
The test for new XML with a one-item list now starts from an item that
has a parent, an attribute and a child. It checks that the copy has no
parent and that changing the copy's attribute and child leaves the
original unchanged, so a copy that drops or shares the children fails.
2026-11-11 09:47:33 -08:00
dotCooCoo 53bd6ecd9a JavaScript: new XML copies a one-item list's node, and insertChildAfter and insertChildBefore take fuel per child scanned
new XML(list) now returns a deep copy of the list's single item, as
ECMA-357 section 13.4.2 specifies for an XMLList argument; before, it
returned the item itself, so changes to the new value changed the list.

insertChildAfter and insertChildBefore take one unit of fuel for each
child they compare with the reference, as childIndex does.

The test for the missing xpath method now checks the XML and XMLList
prototypes; reading .xpath on a value selects child elements and
returned an empty list in every case.
2026-11-11 09:21:00 -08:00
dotCooCoo de95640d93 JavaScript: XML namespace declarations and QName strings count against the heap limit
addNamespace and setNamespace now add a declaration's bytes to the heap total when the element
gains a new prefix, and QName.prototype.toString adds the bytes of the string it returns. Before,
both only checked for room, so a loop of distinct prefixes could grow an element past the heap
limit until a collection recounted it.
2026-11-11 08:09:22 -08:00
dotCooCoo bc6c20d0c9 JavaScript: a limit during an XML path assignment or list copy keeps the tree whole
- A list copy holds each new copy as a root while the list is charged
  for it, so a collection at that point no longer frees the copy. Before,
  `x.setChildren(<c/> + <d/>)` near the heap limit could leave one child
  twice or end with an internal error.
- An assignment to a path such as `x.a.b = v` or `x.a.b[0] = v` removes
  the elements it added for the path when a later step stops at a limit
  or throws.
- Comparing XML values counts the attribute tables and the work stack
  against the heap limit.
2026-11-11 08:00:42 -08:00
dotCooCoo 33f04522f8 JavaScript: an attribute placed among XML children goes in as text
ECMA-357 sends a single attribute given to [[Insert]] through [[Replace]], which makes a text node. A list holding an attribute went into the children as the attribute itself, changed its parent and stayed on its old owner. Each attribute now goes in as a new text node with its value, and it stays on its owner. A list assigned such a value holds the new text nodes, and an attribute does not count against the depth limit.
2026-11-09 07:58:49 -08:00
dotCooCoo 01673695af JavaScript: a computed XML name or attribute run cannot add markup
An embedded expression used as a tag or attribute name must now be one XML
name, and one used as a run of attributes must hold only name="value"
attributes separated by white space. Anything else throws SyntaxError, so a
script string cannot add an attribute, close the tag or add a child.

{name}={value} in a tag now reads as a computed attribute name, as in the
example in ECMA-357 section 11.1.4.
2026-11-09 07:47:29 -08:00
dotCooCoo 80ef5d5f53 JavaScript: a limit while a list or named match replaces XML children leaves the tree intact
Replacing a child with a list, inserting a list, and assigning or
replacing by name now check the depth and take all fuel and heap before
the first change. A limit no longer leaves the old child removed, part
of a list inserted or one of several matches gone.

appendChild and assignment past the end of a list remove their empty
placeholder when the assignment stops. A list that takes an earlier
sibling at an index keeps that node in its slot.
2026-11-09 07:37:24 -08:00
dotCooCoo dbfd3f8a80 JavaScript: XML rejects reserved namespace bindings and names
The XML parser now throws SyntaxError when markup binds the xml or xmlns prefix or either fixed URI against the Namespaces in XML rules. addNamespace, setName, setLocalName and setNamespace throw TypeError for the same bindings and leave the node unchanged.

A new element or attribute cannot be in the xmlns namespace, and an attribute in no namespace cannot be named xmlns. The xmlns URI cannot be the default XML namespace. These assignments throw TypeError.
2026-11-09 07:22:20 -08:00
dotCooCoo 237ba7a483 JavaScript: a limit during an XML insert, delete or put leaves the tree intact
insertChild, prependChild, replace and list assignment take their heap
and fuel before moving a node out of its old parent. Replace past the
last child charges before the push, so a child is never left without
its parent link. Deleting a child or attribute by name, assigning an
attribute or assigning text to an element take the fuel for the whole
step before emptying the node. normalize removes the next text node
before it stores the joined text.
2026-11-09 07:09:12 -08:00
dotCooCoo f85c555014 JavaScript: run E4X XML literals and the XML, XMLList, Namespace and QName objects behind an option
With `Options::e4x` set, vectorcraft-js runs ECMA-357 (2nd edition):
XML and XMLList literals with embedded expressions, the `..`, `.@`,
`.*`, `.()` and `::` selectors, assignment and `delete` on selected
nodes, `for each`, `default xml namespace`, and the four objects with
their methods and settings. The option is off by default, and without
it the syntax and globals do not exist.

Parsing, serializing, comparing and copying trees use no native stack,
and each takes fuel and counts against the heap limit. Inserting a node
into itself or its descendants throws a TypeError before the tree
changes. A literal or XML string nested past `Options::max_depth` gives
a SyntaxError, and a tree built past that depth stops at the heap limit.
`XML.prototype.xpath` is not provided.
2026-11-09 06:50:32 -08:00