Skip to content

slate 0.0.30 — asset imports, a bytes body, and a builtin that holds its arguments

Choose a tag to compare

@edadma edadma released this 05 Sep 16:01
· 78 commits to dev since this release

Ten things two frameworks asked for, one of which is a defect in the interpreter that had been
there since builtins could take a callback at all.

A builtin holds its arguments across a callback that collects

["a", "b", "c"].map(r -> allocate()) could free the array it was walking. args is a buffer
the collector does not walk, and by the time a builtin runs the call has already taken the callee and
its arguments off the operand stack — so for the length of a native call the only thing holding them
was that buffer. Every builtin that answers without running slate code was fine; the ten or so that
call back into it were not.

What it looked like was never a memory failure, which is why it survived so long:

error: this array was changed while `map` was walking it

about a literal nothing in the program can reach. Binding the array to a val moved the failure onto
the callback"this function takes from 1 to 0 arguments" — the freed slot having been taken by
something else and read back as a function of another arity. Binding both made it pass, and the
JavaScript back end passed throughout.

The rooting is at the dispatch and not in each builtin, which is the point: the hole was in map,
and it was in filter, reduce, sort, find, flatMap, forEach, some and every too. A rule
written out per builtin had already been half-applied, which is how map came to be the one without
it. Found by lath's reconciler.

entries and keys no longer report a proto nobody wrote

entries(One(5)) answered [["a", 5], ["proto", <data One>]], so an object copied out of a data
value was not the value it copied, and has(p, "(class)") was true of a name no program can write.
The printer had hidden both since protos shipped; the six walks over an object had not.

keys, values, entries, has, len and without now read one answer, and it is the printer's:
a proto on a class instance or a data variant is machinery, and a proto a program wrote on a
plain object is a field.

with on a data value refuses a field the type does not have

One(5) with { id: "3" } answered One(5, "3") — a variant carrying a field its own declaration does
not have, printed as though there were a second positional one. Nothing else in the language can build
such a value. A field the type does have still updates, which is what with is for.

Importing a file that is not slate

import styles from "./button.css"
import template from "./welcome.html"

A quoted path naming anything but .sl or .slx is an asset, and one name takes the whole of it as
a string.
The file is read while the program is compiled and travels inside it, so nothing sits
beside the binary at run time and six files importing one stylesheet is one string — and under
slate js the text is written into the emitted program, byte for byte.

The extension decides, never what the writer meant, and each form is refused in the other's:
import { helper } from "./styles.css" says there are no names in it to take, and a bare name asked of
slate source says slate has no default export.

A package's assets are the package's own: a .css shipped beside a .slx is imported by that file,
relatively, and handed on as an ordinary exported value — so the package system had to learn nothing.

Two refusals, both before the program runs: a file that is not there, named beside the file that asked
for it, and a file that is not UTF-8, which is refused rather than mangled — slate has one text
type, and a program that wants bytes wants readBytes.

req.bytes, and a body that is not text

req.body was the only reading of a request body and it is from_utf8(...).unwrap_or("") — so a
PNG posted to a route was the same value as a request that carried no body at all: no header, no
status, no fault. req.bytes is beside it now and is what actually arrived; req.body is unchanged,
so nothing already written moved.

req.address

slate:http told a handler the method, the path and the headers and nothing about who connected,
so a rate limiter, an allow-list and a log line each had only whatever a proxy wrote into a header.
remoteAddress(conn) is the slate:net half; a listener has no other end and answers null.

An IPv4 client of a dual-stack server reads as 127.0.0.1, not ::ffff:127.0.0.1: listen binds
:: so that localhost reaches the server, and an allow-list written against the address a client
dialled has to match.

skip(reason) in the test runner

@test
a_server_answers_what_it_is_asked() =
    if !canListen() then skip("this host has no listener")

    assertEq(ask("/"), "hello")
  skip  tests/api.sl :: a_server_answers_what_it_is_asked   this host has no listener

7 passed, 1 skipped

A third verdict and not a kind of pass, so a suite that quietly stopped running half of itself
cannot report a page of greens. It raises, so nothing after it runs; a catch does not get it, which
is exit's rule; and a run that skipped nothing says exactly what it always said.

without(o, key)

The one object operation there was no way to write. keys, values, entries and has read and
with writes, so a table that had to forget something rebuilt itself out of everything else — which
is what sluice's session store does. A new object, as with's answer is.

insertBefore and removeChild in slate:dom

setChildren writes the whole list and these move one node. A keyed reconciler moving three rows
of a thousand should not make the page do a thousand pieces of work — jsdom's MutationObserver
counted ~1001 records for exactly that. before of null appends, and a node already in the page
moves rather than being copied, so it keeps its focus and its scroll position.

Two things the parser was refusing

then may be indented under the line its if began on, as else and elif already could:

val kind = if len(headers) == 0
    then "empty"
    else "carrying"

And an element may stand where any other value may. return <p>x</p> was a bare return followed
by wreckage, because the table saying which tokens begin an expression had never learned about one —
and the complaint, "this is where the statement should have ended" pointing at the <, sent the
reader to look at the element rather than at the return.