Merge branch 'worktree-agent-a2b2b99f84ca798de' into dev-loop

This commit is contained in:
Joseph Ferano 2026-09-21 11:59:19 +07:00
commit 970d5ac38f
110 changed files with 875 additions and 582 deletions

87
FIX.org
View File

@ -5542,3 +5542,90 @@ condition's payload" reached the struct-field refusal that fired first, never
the condition arm it was named for. The struct refusal is gone since the
descriptors landed, so the row is an [accepts] now and a new [rejects_check]
signals a dyn directly to reach the arm that is still there.
* len is a variable name now, 2026-09-21
The author, on why:
#+begin_quote
I think I prefer length over len, because then I'll use len as the variable
name
#+end_quote
So the count is ~length~, and ~len~ is left to programs. One arm in
~lib/check.ml~ and one row in the table beside it; everything that wrote
~(len x)~ across lib, test, examples, vendor, spike, docs, web, emacs,
plan.org and NEXT.md writes ~(length x)~ now.
** What this adds to shadowing, which landed beside it
Shadowing and ~builtin/~ had already taken most of the sting out. A
~(defn len ...)~ was legal, it won at every call site in its file, and
~builtin/len~ reached past it. What was still true is that ~len~ was a
builtin: the defn earned a warning, and anything that wanted to wrap it had
to say ~builtin/~. Now there is no builtin under the name at all — nothing
warns, the qualifier is not needed to reach past anything, and the name is
free in every position, which was never the question for a binding and was
the question for a call.
So the two features do different work and the rename does not repeal any of
the other. ~length~ simply becomes shadowing's worked example in place of
~len~: ~shadow-builtin.flan~, ~builtin-qualified.flan~, the package under
~pkgs/shadowed~ and the ~builtin/~ rows in ~test_flan.ml~ all moved to it, and
they go on testing shadowing rather than quietly becoming tests of a free
name.
** The refusal
A call to a ~len~ that nothing in the program defines is answered where an
unknown function is answered — after every table and after the shadowing
guard, so a program with its own ~len~ never sees it:
#+begin_example
test/programs/len-gone.flan:15:3: there is no len. The number of elements in an
array, a slice, a string, a Vec or a Map is length. Write (length a)
15 | (len a))
| ^^^^^^^
#+end_example
Said rather than guessed at: ~len~ and ~length~ are three edits apart and the
did-you-mean's net is one. The reader's own argument is written back out
through ~spell_arg~, which is ~as-slice~'s spelling lifted out of it and now
shared — a name is its name, an integer its digits, anything with structure
inside it becomes a stand-in.
*Only at one argument.* ~length~ takes exactly one, so ~(len xs 1)~ written
back out as ~(length xs 1)~ would be refused a second time the moment it was
pasted, and a suggestion that does not compile is the whole thing this
spelling exists to prevent. At any other arity the shape ~(length v)~ is
suggested instead. ~as-slice~ can write every argument out because ~slice~
takes one, two or three; the difference is the arity and not the style. Two
drafts of this lane got it wrong in turn — the first quoted the source line
and could print an unbalanced form, the second spelled every argument — and
the arity cases are pinned now, which neither draft was.
~(builtin/len xs)~ is the one rough edge left. It reaches ~not_a_builtin~ and
reads "len is not a builtin, so builtin/len reaches nothing" with no
did-you-mean, for the same three-edit reason. Left as it is rather than
special-cased.
** Certified against b8856be
Rebased onto the diagnostics rewrite, which is where the refusal above has to
read against its neighbours — it takes ~as-slice~'s shape, because that is the
refusal beside it and the two answer the same kind of question. dev-loop moves
hourly; this is the base the green result below was measured on.
** sand.flan, for the author
Two calls are left in the tree, both in the file this lane did not touch
because it is the author's:
: 31 | length (len coll)]
: 55 | (set current-color (% (+ current-color 1) (len colors))))
Line 31 is the one to read twice: the binding is ~length~ and the call is
~len~, and a sequential ~let~ makes ~length (length coll)~ legal — the
initialiser is checked before the name it binds exists.
Until both say ~length~, ~test_acceptance~ and ~test_session~ abort on the
first of them — a fatal exception, not a failing row, which is the
hidden-failure shape a previous lane found. sand.flan also feeds ~@x86~,
~@js~, ~@sanitize~ and ~@valgrind~ through their workspace-file deps, so those
go red too. Everything underneath was verified green against a copy of the
file with those two lines changed.

18
NEXT.md
View File

@ -1386,7 +1386,7 @@ cells already solve. A generic function is a cell whose body is a dispatch table
expensive half of classes is therefore already built and tested.
- **The pool is not one storage option among three.** `migrate-instances` has to *enumerate* live instances. A pool
behind generational `(Handle T)` gives that by construction; a world arena and an owned region do not obviously.
plan.org presents the three as a free choice and they are not. **The pool is built**, and `(len p)` with
plan.org presents the three as a free choice and they are not. **The pool is built**, and `(length p)` with
`(pool-handle p i)` is that enumeration.
- **`Enemy@1` has to stay resolvable** for `migrate` to dispatch on it, so the session retains every layout version's
metadata for as long as any instance holds it. Same rule as "nothing is ever `dlclose`d", and worth stating as one.
@ -1599,7 +1599,7 @@ See [`docs/BUILT.md`](docs/BUILT.md), "`(Handle T)` and the pool, which is what
It was not an incidental precondition. `migrate-instances` has to *enumerate* live instances, and a pool behind a
generational `(Handle T)` gives that by construction while a world arena and an owned region do not. plan.org presents
the three storage strategies as a free choice and they are not: handles are the one that makes migration possible.
`(len p)` plus `(pool-handle p i)` is that enumeration, and it is two entry points rather than an iteration protocol.
`(length p)` plus `(pool-handle p i)` is that enumeration, and it is two entry points rather than an iteration protocol.
Already banked, and it means classes are less work than plan.org implies: **a generic function is an indirection cell**
whose body is a dispatch table, which a reload extends. That is the expensive half of method dispatch, and it is built
@ -1764,7 +1764,7 @@ to avoid rather than inherit. Four, with what to do instead.
**1. One argument-order rule, held everywhere.** Clojure's sequence functions take the collection *last*
(`(map f coll)`) and its collection functions take it *first* (`(assoc m k v)`). The split is deliberate there, and it
is why Clojure needs **two** threading macros instead of one. **Our rule: the thing being operated on comes first.**
That is already what the language does — `(at a i)`, `(len xs)`, `(push v x)`, `(slice v)` — and `into` follows it
That is already what the language does — `(at a i)`, `(length xs)`, `(push v x)`, `(slice v)` — and `into` follows it
with the source first. Hold it; do not ship two of anything to paper over a split.
**2. A membership test says which thing it tests.** Clojure's `contains?` checks *keys*, so `(contains? [1 2 3] 1)` is
@ -1965,8 +1965,8 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
What the spec did not settle and this lane did, beyond those: `resolve` answers `(Option (Ptr T))` and not
`(Option T)` — the spec's own worked example is annotated that way, for the reason written a line above it, that a
pattern binding binds a value and a copy cannot be written back. `(len p)` is the *slot high-water* and `(live p)`
is the live count, in that direction, so a loop bounded by `len` cannot silently skip a live entry. A slot is
pattern binding binds a value and a copy cannot be written back. `(length p)` is the *slot high-water* and `(live p)`
is the live count, in that direction, so a loop bounded by `length` cannot silently skip a live entry. A slot is
recycled by `(release p h)` on the owner and never by `free`, because a handle owns nothing and consuming one copy
would say nothing about the others — so `spec-memory.md`'s two release points are untouched.
@ -2465,8 +2465,8 @@ expander last, on 6's unions.
rather than a literal calling-convention parameter; both are stated as amendments in `docs/BUILT.md`. A user-written
allocator is refused by name with milestone 5 as the reason.
~~2. **`(Vec T)`**~~ **Done**, over the type-erased runtime, with `push`, `reserve`, `at`, `len`, `slice`, `free` and
`clone`, and with move-only enforced by a dead set that unions at an `if` or a `match` join. `at` and `len` were
~~2. **`(Vec T)`**~~ **Done**, over the type-erased runtime, with `push`, `reserve`, `at`, `length`, `slice`, `free` and
`clone`, and with move-only enforced by a dead set that unions at an `if` or a `match` join. `at` and `length` were
extended rather than duplicated. The header is six words in *every* build, not four in release — a layout that
changes with a build flag can disagree silently across the reload boundary — and that is the third amendment.
Ownership is not transitive yet, so a struct field of `Vec` type, a global `Vec` and a `(Vec (Vec T))` are each
@ -2481,7 +2481,7 @@ expander last, on 6's unions.
restart; `exhausted-unhandled.flan` is the same failure with nothing handling it.
~~4. **`(Map K V)`**~~ **Done**, following Odin: open-addressed Robin Hood hashing at a 75% load factor, cache-line
cell packing, and pointer-width integers through the probe loop. `map-new`, `put`, `get`, `has-key?`, and `len`,
cell packing, and pointer-width integers through the probe loop. `map-new`, `put`, `get`, `has-key?`, and `length`,
`reserve`, `clone` and `free` extended rather than duplicated. Two departures from Odin, both deliberate: no
tombstones, because the spec defers removal, which deletes the backward-shift loop entirely; and no capacity tagged
into the data pointer, because this header has room and tagging would make correctness depend on an alignment that
@ -2848,7 +2848,7 @@ What follows is only the part that is still missing.
`macros.flan` and `macro-unless.flan` were not added to it by landing them. `dune build --root . @sanitize` is
clean as it stands; adding the two is a one-line edit in a file this lane did not own.
- **No `&rest` sugar.** A macro takes one parameter, the slice of forms at its call site, and `(len args)` is the
- **No `&rest` sugar.** A macro takes one parameter, the slice of forms at its call site, and `(length args)` is the
arity. That is deliberate — it is where variadics come from — but a `when` written against it reads worse than
`parse.ml`'s version did.

View File

@ -4,8 +4,9 @@
;;;; 3.5
;;;;
;;;; Chosen to be the smallest program that is still a real one. What it needs:
;;;; functions, recursion, structs, (Ptr T) and `addr`, byte slices, `at`/`len`,
;;;; `while`, `set` on locals and on fields, `cond`, `match`, Option, i32/u8/f64,
;;;; functions, recursion, structs, (Ptr T) and `addr`, byte slices,
;;;; `at`/`length`, `while`, `set` on locals and on fields, `cond`, `match`,
;;;; Option, i32/u8/f64,
;;;; and argv. What it deliberately does NOT need: an allocator, Vec, Map, any
;;;; generic function, any macro the user wrote, FFI beyond argv and stdout,
;;;; a window, or a frame loop. It runs headless, so it is the same test on
@ -23,7 +24,7 @@
pos i32]) ; no initialiser means zeroed
(defn peek [c (Ptr Cursor)] u8
(if (< (.pos c) (len (.src c)))
(if (< (.pos c) (length (.src c)))
(at (.src c) (.pos c))
0)) ; 0 doubles as end-of-input
@ -118,7 +119,7 @@
;; argument's concrete type happens at compile time, so there is nothing to
;; dispatch on at run time and no type to name at the call site.
(defn main [args [string]] i32
(if (< (len args) 2)
(if (< (length args) 2)
(do (println "usage: calc-me \"1 + 2 * 3\"") 1)
(match (evaluate (bytes-view (at args 1)))
(Some v) (do (print v) (println "") 0)

View File

@ -15,7 +15,7 @@ of any `Field`. A move nested there is never recorded.
Confirmed by execution, two programs:
- `(println (at rows (eat w)))` then `(free w)` — double free, exit 134.
- Same shape with a move-only global `g`: accepted, `g` freed, `(len g)` reads a freed
- Same shape with a move-only global `g`: accepted, `g` freed, `(length g)` reads a freed
header, exit 0 silent. Defeats the rule test/test_flan.ml:1067-1068 pins.
Fix direction: narrow the flag to the target's own read — restore `ctx.borrow` for the

View File

@ -47,7 +47,8 @@ without touching the generator.)
| `(rand-int-range lo hi)` | an `i64` in `[lo, hi)`; an empty or reversed range answers `lo` |
| `(rand-float-range lo hi)` | an `f64` in `[lo, hi)` |
An index is an `i32` (`len` answers one), so indexing with a draw reads `(at xs (i32 (rand-int-range 0 (i64 (len xs)))))`.
An index is an `i32` (`length` answers one), so indexing with a draw reads
`(at xs (i32 (rand-int-range 0 (i64 (length xs)))))`.
**It is predictable, and that is deliberate.** Getting 64 output bits out of 64 state bits costs reversibility: the
permutation is a bijection, so anyone holding one result can work back to the state and know every number after it.
@ -129,7 +130,7 @@ malloc failure, where the alternative is handing C a null pointer.
and `collision-lines` answers with an `Option`; neither is raylib's own signature. A slice parameter in a `declare-c` is
refused by name, because a slice's length crosses as i64 and the type of the C count parameter beside the pointer is not
recoverable from `[T]` — so that one declares `(Ptr Vector2)` with an explicit `count i32` and the Flan wrapper passes
`(addr (at points 0))` and `(len points)`. Every other refusal — an Option, a union, a fixed array, a map, a returned
`(addr (at points 0))` and `(length points)`. Every other refusal — an Option, a union, a fixed array, a map, a returned
string, a callback, an unknown type, an unrepresentable struct field, two Flan names for one C symbol — is by name with
the reason, and the acceptance table asserts on the reasons.
@ -848,7 +849,7 @@ fact without cutting anything in half. See "The browser is the third target" bel
- `(defconst gravity 0.05)``(defconst gravity f32 0.05)`. An untyped float constant is `f64`, `velocity` is `[f32]`,
and `f64` into `f32` is a narrowing — still written, and still written after implicit widening landed (FIX.org
2026-09-20), because widening is only the conversions that cannot change the number and this one can.
- `(defonce current-color u32)``i32`. It is an index into `colors`, and `(len colors)` is an `i32`.
- `(defonce current-color u32)``i32`. It is an index into `colors`, and `(length colors)` is an `i32`.
- `(defn main [])` is unchanged — the short form, as plan.org says.
Painting is on **hold left mouse button** rather than on space, since the mouse bindings exist now. Space is still what
@ -2445,7 +2446,7 @@ fires.
| `(with-allocator a body...)` | rebinds for a dynamic extent and releases nothing |
| `(vec-new T)` / `(vec-new T a)` / `(vec-new)` | a Vec, against the context or a named allocator |
| `(push v x)` / `(reserve v n)` | `()`, both. **Either may reallocate, and any `[T]` view of this `Vec` is stale afterwards** — see below |
| `(at v i)` / `(len v)` | the array names, extended — not a parallel pair |
| `(at v i)` / `(length v)` | the array names, extended — not a parallel pair |
| `(slice v)` / `(slice v lo)` / `(slice v lo hi)` | a non-owning `[T]` view — the array names again, extended |
| `(clone v)` / `(clone v a)` | the only copy; assignment moves |
| `(free v)` | consumes its argument |
@ -2464,7 +2465,7 @@ one rule: take the view again after the `push`.
One thing it is *not*: a dangle. `(slice (make-vec))` is legal, and the view it answers reads what it says it reads —
the storage a returned `Vec` owns outlives the expression. What does not survive is the header, which was a
temporary, and it was the only handle `free` could have taken, so that block is leaked unless the allocator reclaims
it wholesale at `free-all` or destroy. That is the same leak `(len (mk))` and `(at (mk) 0)` already write, and the
it wholesale at `free-all` or destroy. That is the same leak `(length (mk))` and `(at (mk) 0)` already write, and the
same one `spec-memory.md` names when a global `Vec` is overwritten: manual memory management, and the program's
business. `(slice (mk))` over a fixed **array** is refused, and the difference is worth being exact about — that one
is a view into a frame that is gone, so it answers whatever the frame was reused for. A wrong answer is not a leak.
@ -2531,10 +2532,24 @@ known — which without generics is simply the concrete call site — and passed
- `AddrOf` of any expression, place or not, because the element a `push` copies may be computed. The backend already
spilled a non-place to a temporary for exactly this.
`at` and `len` were already the names for a fixed array and a slice, so a Vec extends them rather than adding a
`at` and `length` were already the names for a fixed array and a slice, so a Vec extends them rather than adding a
parallel pair — the asymmetry `nth` was removed for. The value form `(at v i)` and the place form `(set (at v i) x)` go
through one helper, so they cannot drift apart the way `nth` did.
**The word is `length` and not `len`, so that `len` is a name a program can have.** Shadowing makes a `(defn len ...)`
legal and `builtin/len` reaches past one, so the call position was already recoverable — but at the price of a warning
at the definition and a qualifier at every inner call. With no builtin under the short name there is neither: `len` is
an ordinary identifier in every position, which is what `(let [len (length xs)] ...)` wants. `length` takes over as
the worked example wherever shadowing is demonstrated. A call to a `len` nothing defines is answered where an unknown
function is answered — after every table and after the shadowing guard, so a program with its own `len` never reaches
it — and the sentence is said rather than guessed at, because `len` and `length` are three edits apart and the
did-you-mean's net is one. It writes the reader's own argument back out through `spell_arg`, the same spelling
`as-slice`'s refusal uses: a name is its name, an integer its digits, anything with structure inside it becomes a
stand-in. Only at one argument, though — `length` takes exactly one, so writing three of them back out would produce
a suggestion refused a second time the moment it was pasted; at any other arity the shape `(length v)` is suggested
instead. `as-slice` can spell every argument because `slice` takes one to three. A printed suggestion has to compile,
and the arity is half of what that costs.
A Vec's length and index are `i32`, like every other length here. Widening indices is one change across every
container and not a Vec question.
@ -2556,8 +2571,8 @@ what was built and why. Move-only as a *type* property (what may be copied, what
Reading a move-only local is a move unless the site said it was a borrow. That is the conservative direction: passing
one to a function, binding it, returning it and `free`ing it are all moves and all reach one place, and the handful of
operations that only look at a container (`at`, `len`, `slice`, `push`, `reserve`, `clone`) say so. Only a
*syntactically simple* target counts as a borrow — in `(len (f v))` the call still moves `v`.
operations that only look at a container (`at`, `length`, `slice`, `push`, `reserve`, `clone`) say so. Only a
*syntactically simple* target counts as a borrow — in `(length (f v))` the call still moves `v`.
At an `if` and at a `match`, every arm is checked from the state before the form and the **union** of what they moved
survives the join. A flat set is wrong in both directions: it refuses `(if c (free v) (free v))`, which is legal, and it
@ -2584,7 +2599,7 @@ three shapes are refused where they are declared, each naming `drop`:
duplicate inner headers instead of copying what they own and `free` would drop their buffers.
And a `Vec` does not cross to C: handing a header that owns storage to C hands out an owner. `(slice v)` as
`(Ptr T)` plus `(len v)` is the shape that does cross, and the refusal says so.
`(Ptr T)` plus `(length v)` is the shape that does cross, and the refusal says so.
### `StorageExhausted` went in *with* `Vec`, not after it
@ -2726,15 +2741,15 @@ calls per field, which has no channel to hand on.
| `(get m k)` | `(Option V)` — absence is `None` |
| `(has-key? m k)` | `bool`, copying no value — **an addition; the spec does not name it** |
| `(map-remove m k)` | `(Option V)` — the value that was there, or `None` |
| `(len m)` `(reserve m n)` `(clone m)` `(clone m a)` `(free m)` | extended, not duplicated |
| `(length m)` `(reserve m n)` `(clone m)` `(clone m a)` `(free m)` | extended, not duplicated |
`has-key?` is **not in `spec-memory.md`** and is an addition, flagged because everything else here is the spec's.
`(get m k)` answers the same question, but through an `Option` the caller then has to match, and the common use is a
condition. It copies no value, which is also why it is not just `get` with the result thrown away. The `?` suffix
follows `can-free?`.
`len`, `reserve`, `clone` and `free` were **extended rather than given map-shaped names of their own**, which is what
`at` and `len` already did for `Vec`: one question, one word. `reserve`'s `n` is entries, not slots — the runtime sizes
`length`, `reserve`, `clone` and `free` were **extended rather than given map-shaped names of their own**, which is what
`at` and `length` already did for `Vec`: one question, one word. `reserve`'s `n` is entries, not slots — the runtime sizes
the block so `n` still sits under the load factor, which is the only reading of "room for n" that does not reallocate
on the nth put.
@ -3052,15 +3067,15 @@ reintroduces the silent-wrong-answer mode the handle just removed for anyone who
insert. Chunked never-moving storage is the fix and it costs code; taking the contract is the smaller correct thing,
given `slice` already established it.
### `len` is the slot high-water and `live` is the count, in that direction
### `length` is the slot high-water and `live` is the count, in that direction
`(len p)` is how many slots have ever been handed out. `(live p)` is how many of them are live now. It had to be that
way round: `0..(len p)` are the indices `(pool-handle p i)` accepts, so a loop bounded by `len` visits every live
`(length p)` is how many slots have ever been handed out. `(live p)` is how many of them are live now. It had to be that
way round: `0..(length p)` are the indices `(pool-handle p i)` accepts, so a loop bounded by `length` visits every live
entry. Bounded by the live count instead, it would silently skip entries the moment anything had been released —
which is exactly the quiet wrong answer the whole type exists to remove.
`(pool-handle p i)` answers `(Option (Handle T))`: the handle of slot `i`, or `None` if that slot is dead. That plus
`len` is the whole of iteration. An index outside `0..(len p)` **traps**, exactly as `(at v i)` traps: an index is an
`length` is the whole of iteration. An index outside `0..(length p)` **traps**, exactly as `(at v i)` traps: an index is an
index here, and answering `None` for one would hide a bug rather than a death.
### A slot is released through the pool, and that is not a third release point
@ -3129,7 +3144,7 @@ the tree.
### What classes still need
The enumeration primitive is the piece migration was blocked on, and it exists now. What is left is `defclass` itself
and its runtime shape metadata; `migrate-instances`, which is a walk over `(len p)` and `(pool-handle p i)`; generic
and its runtime shape metadata; `migrate-instances`, which is a walk over `(length p)` and `(pool-handle p i)`; generic
functions and method dispatch, whose expensive half is already built and tested (a generic function is an indirection
cell whose body is a dispatch table, and a reload extends the table); and the rule that `Enemy@1` stays resolvable for
as long as any instance holds it — the same rule as "nothing is ever `dlclose`d".
@ -3455,10 +3470,10 @@ is. So `into` collects, and a reducing macro of the same shape is a separate for
which says *push takes a (Vec T)* and names the real problem. There is no second lowering and no reason to invent
one before something asks.
- **A source that is already a name is used as it is; anything else is bound to a gensym.** Both halves are needed and
neither is cosmetic. Binding is what a source that is a *call* needs — `(len s)` and `(at s i)` have to be the same
neither is cosmetic. Binding is what a source that is a *call* needs — `(length s)` and `(at s i)` have to be the same
`s`, or the call is made once per element. Not binding a name is what everything else needs: a `(Vec T)` is
move-only, so `(let [s v] ...)` would hand the caller's `v` to a binding it cannot see and `v` would be dead after
an `into` that only read it; and a fixed array would be *copied* into the binding, once per `into`. `len` and `at`
an `into` that only read it; and a fixed array would be *copied* into the binding, once per `into`. `length` and `at`
borrow, so used directly the source is only read.
- **An owning temporary as the source leaks**, and this is the wart. `(into (make-a-vec) ...)` binds the result to a
name the caller cannot reach and therefore cannot free. The macro cannot know whether the type owns anything. A call
@ -3586,8 +3601,8 @@ array *literal* of two elements, whose second element is a type name nothing dec
declaration written the other way and either spelling can be the parameter — `array-ctor.flan` asserts exactly that.
It is a parser form and not a builtin call, because the second argument is a *type* and there are no types in the
parser's callers. `Parse` assembles the whole `Tarray (len COUNT, TYPE)` itself, which is why the count takes a
constant's name for free — `len` is the same function `[n T]` goes through — and why a non-type second argument is
parser's callers. `Parse` assembles the whole `Tarray (length COUNT, TYPE)` itself, which is why the count takes a
constant's name for free — `length` is the same function `[n T]` goes through — and why a non-type second argument is
refused by the type reader's own message rather than as an unknown name. The checker resolves it and hands back
`Tast.Zero`, the same node a declaration with no initialiser gets. There is no new backend node and no new type.
@ -4017,7 +4032,7 @@ an ordinary prelude function over a `[EmbedFile]`. A directory embed is tens of
`.rodata`; a compile-time perfect hash would be a build-time map with its own failure modes that nothing has asked for,
and sort-and-bisect is the next step if a program ever embeds thousands of files — it would not change the type. It
takes a **slice** rather than the array, because an array's length is part of its type and there are no generics, so
the call reads `(embed-find (slice assets 0 (len assets)) "brush.png")`. Entries are sorted by name because `readdir`
the call reads `(embed-find (slice assets 0 (length assets)) "brush.png")`. Entries are sorted by name because `readdir`
order is filesystem-dependent and an unsorted embed would make two builds of identical sources emit different `.ll`.
Non-recursive, files only — Odin again.
@ -5939,7 +5954,7 @@ they are not. It stays behind `f.md.checks`.
`lo <= hi` is **not a bounds check**, and the old comment beside it said as much while the
code did the opposite. A slice is `{ptr, i64}` and the `i64` is a count: the emitted
`sub` computes it as `hi - lo`, and the `len` primitive, a re-slice, `flan_write_stdout`
`sub` computes it as `hi - lo`, and the `length` primitive, a re-slice, `flan_write_stdout`
and any C the value is handed to all read it as a non-negative number of elements.
`(slice s 2 1)` does not build an out-of-range slice, it builds a value that is not a
slice — the length word holds -1, which as a count is 18446744073709551615. No build
@ -6176,7 +6191,7 @@ for before — and `@sanitize` is clean over both programs.
`(slice a)` is the whole of `a`, `(slice a n)` is the tail from `n`, and `(slice a n m)` is the half-open range it
has always been. The two short forms exist because a fixed array does not decay to a slice at a call: passing
`[6 2 4 9 1 9 4 5]` to the prelude's `sort`, which takes `[$t]`, used to require `(slice a 0 (len a))` written out
`[6 2 4 9 1 9 4 5]` to the prelude's `sort`, which takes `[$t]`, used to require `(slice a 0 (length a))` written out
at every call site.
```flan
@ -6186,7 +6201,7 @@ at every call site.
**They are one node, not three.** `check.ml` fills the missing arguments in and hands the backends the
three-argument form. The implicit `lo` is the literal 0. The implicit `hi` is the literal length when the target is
a fixed array — the same constant `(len a)` folds to — and the `Len` primitive otherwise, which reads the length
a fixed array — the same constant `(length a)` folds to — and the `Len` primitive otherwise, which reads the length
word a slice or a string is already carrying. So the short spelling costs exactly what the long one costs, the
static refusals apply to it unchanged (a literal `lo` past the end of a fixed array is an error at compile time,
not a trap), and the runtime bounds check is the same check on the same numbers. Neither backend grew an arity

View File

@ -712,7 +712,7 @@ It does not crash, which is the bad part. Run headless:
(let [text "いろはに"
b (bytes text)
sz 0]
(println (rl/get-codepoint-previous (string (slice b 3 (len b))) (addr sz)))
(println (rl/get-codepoint-previous (string (slice b 3 (length b))) (addr sz)))
(println sz))
```
@ -839,7 +839,7 @@ character is — a `string` is bytes and `(bytes s)` / `(string b)` say so in bo
at no cost — and for this job that is exactly the right amount of opinion. There is a
`valid-utf8?` in the prelude and this example never needs it.
**An interior pointer into a string has an idiom already.** `(string (slice b off (len b)))`
**An interior pointer into a string has an idiom already.** `(string (slice b off (length b)))`
is the C's `char *ptr` and compiles to nothing: a `string` and a `[u8]` are the same two
words. Every forward-reading `const char *` entry point is reachable that way.

View File

@ -299,9 +299,9 @@ actually arises — a cycle compared against itself. Two *distinct* cyclic vecs
false, which is a wrong answer to a question nobody has asked yet; the honest fix is a visited set and it
can be added the day somebody needs it.
### `len`, `at`, `set-at`, `push`
### `length`, `at`, `set-at`, `push`
`len` is bytes of a text or elements of a vec. `at` is an element of a vec or a byte of a text as an int
`length` is bytes of a text or elements of a vec. `at` is an element of a vec or a byte of a text as an int
— which is what `(at s i)` on a `(Slice u8)` does in the typed language; codepoints are `utf8`'s job and
stay there. `set-at` and `push` are vec-only, and a `set-at` on a text says *"a text is immutable — build
another one"* rather than quietly copying.
@ -386,7 +386,7 @@ rather than discovered later. Two ways out, both the compiler's and neither this
`flan_dyn_need_f64` — which sees only a tag and could not tell a literal-derived int from a computed
one.
Softening the boundary itself is the option not to take: it would accept `(g (len xs))` as readily as
Softening the boundary itself is the option not to take: it would accept `(g (length xs))` as readily as
`(g 1)`, and those are not the same mistake.
---
@ -470,7 +470,7 @@ No microbenchmarks; these are counts of what the code does.
| `div`/`rem` | the above plus a zero test and an overflow test |
| `lt`/`le`/`gt`/`ge` | two tag tests and a compare; text is `memcmp` |
| `eq` | word compare first; then tags, then bytes or elements — O(size) at worst |
| `len` | tag test and a load |
| `length` | tag test and a load |
| `at` | two tag tests, a bounds compare, a load |
| `set_at` | the same, plus a store; **no write barrier**, because there is no generation to have one for |
| `push` | tag test, a capacity test, amortised O(1); the doubling is a `realloc` |

View File

@ -81,7 +81,7 @@ is the `is_polymorphic_type_assignable` walk rather than a name match.
(defn sort-by! [s [$t] before? (Fn [$t $t] bool)] ()
(let [i 1]
(while (< i (len s))
(while (< i (length s))
(let [j i]
(while (and (> j 0) (before? (at s j) (at s (- j 1))))
(swap! s (- j 1) j)

View File

@ -77,7 +77,7 @@ Four commits, each one a working state:
`flan generate-c`. A hand-written `declare-c` that disagrees with `raylib-5.5.h` therefore
breaks every build in the tree, not just regeneration. That is what makes open question 2
a blocker rather than an inconvenience.
- `(string (slice b off (len b)))` is the idiom for a C `char *` cursor into the middle of a
- `(string (slice b off (length b)))` is the idiom for a C `char *` cursor into the middle of a
string, and it costs nothing. It is correct for every entry point that reads forwards and
wrong for every one that reads backwards.
- `slice-from-ptr` is how a raylib pointer-plus-count becomes something with a length. Both

View File

@ -173,7 +173,7 @@ The expression before point is compiled, run **inside the running program**, and
its value shown as `=> 2` at the end of the line the form is on. Not a copy of
the program, not a simulation — the actual process, with its actual state.
So in a game you can type `(len enemies)` and get the real number.
So in a game you can type `(length enemies)` and get the real number.
**Where it appears.** At the end of the *line*, which is where the watch buffer's
ghost text goes too — the two are the same thing seen twice and they are drawn
@ -405,7 +405,7 @@ everything it had is still there.
get its fields, one per line. It takes the innermost thing point is on rather
than only the one behind point, so the middle of a name works as well as the end
of one — point anywhere in `enemies` takes `enemies`, and on the open paren of
`(len enemies)` takes the whole call.
`(length enemies)` takes the whole call.
**`C-u C-c C-i`** opens the minibuffer instead, pre-filled with whatever was at
point — for inspecting something that is not written in the buffer, or is

View File

@ -689,7 +689,7 @@ form is taken back from there; on an opening delimiter the form that opens
there is taken instead, which is the rule `flan-macroexpand\=' follows for the
same reason.
Innermost, so point on `enemies\=' inside `(len enemies)\=' takes `enemies\=' and
Innermost, so point on `enemies\=' inside `(length enemies)\=' takes `enemies\=' and
not the call which is the right answer for a command that shows you one
value, and the enclosing call is a paren away in either direction.

View File

@ -131,7 +131,7 @@
"loop" "recur" "break" "continue" "match" "set" "return" "fn" "array"
"defer" "some" "none" "try" "signal" "error"
"handler-bind" "handler-case" "restart-case" "invoke-restart"
"zeroed" "uninit" "slice" "at" "len" "addr"
"zeroed" "uninit" "slice" "at" "length" "addr"
"bytes" "cast" "true" "false" "nil" "print" "println")
"Forms with meaning to the checker.

View File

@ -111,7 +111,7 @@
;; of a raylib example a headless case can assert.
(defn collect-unique [codepoints [i32]] ()
(set unique-count 0)
(dotimes [i (len codepoints)]
(dotimes [i (length codepoints)]
(let [cp (at codepoints i)]
(when (and (not (seen? cp unique-count))
(< unique-count max-codepoints))
@ -126,16 +126,16 @@
;; The codepoint starting at `off`, and its size in bytes through `size-out`.
;;
;; `(string (slice b off (len b)))` is the whole of what the C's `ptr` is: the
;; tail of the text from here on. It costs nothing to say — a `string` and a
;; `(string (slice b off (length b)))` is the whole of what the C's `ptr`
;; is: the tail of the text from here on. It costs nothing to say — a `string` and a
;; `[u8]` are the same two words — and the shim NUL-terminates a copy of it
;; for the duration of the call, which is all GetCodepointNext wants, because
;; it only ever reads forwards.
(defn codepoint-at [off i32 size-out (Ptr i32)] i32
(let [b (bytes-view text)]
(if (>= off (len b))
(if (>= off (length b))
0
(rl/get-codepoint-next (string (slice b off (len b))) size-out))))
(rl/get-codepoint-next (string (slice b off (length b))) size-out))))
;; One codepoint forward, clamped at the end.
;;
@ -146,10 +146,10 @@
(defn step-forward [off i32] i32
(let [size 0
b (bytes-view text)]
(if (>= off (len b))
(if (>= off (length b))
off
(do (codepoint-at off (addr size))
(if (>= (+ off size) (len b)) off (+ off size))))))
(if (>= (+ off size) (length b)) off (+ off size))))))
;; One codepoint back, which is GetCodepointPrevious and no longer a stand-in
;; for it.

View File

@ -68,7 +68,7 @@
tint rl/Color] ()
(let [glyphs (rl/font-glyphs font)
recs (rl/font-recs font)
length (i32 (len text))
length (i32 (length text))
scale (/ font-size (f32 (.base-size font)))
line-h (* (f32 (+ (.base-size font) (/ (.base-size font) 2))) scale)
off-x (f32 0.0)

View File

@ -67,7 +67,7 @@
;; One character every ten frames. The clamp is the whole difference from
;; the C — see the header comment.
(let [b (bytes-view message)
n (min (i32 (len b)) (/ frames-counter 10))]
n (min (i32 (length b)) (/ frames-counter 10))]
(rl/draw-text (string (slice b 0 n)) 210 160 20 rl/maroon))
(rl/draw-text "PRESS [ENTER] to RESTART!" 240 260 20 rl/lightgray)

View File

@ -329,15 +329,15 @@ let builtin_names : string list ref = ref []
let builtin_set : (string, unit) Hashtbl.t = Hashtbl.create 128
(* ── builtin/, the reserved qualifier ──────────────────────────────────
[builtin/len] is the builtin [len], whatever else the program has decided
[len] means. It is the way out of the dead end shadowing used to leave: a
[(defn len ...)] takes the bare name over for its whole file, and before
[builtin/length] is the builtin [length], whatever else the program has
decided [length] means. It is the way out of the dead end shadowing used to leave: a
[(defn length ...)] takes the bare name over for its whole file, and before
this there was no remaining spelling for the thing it was wrapping, so the
wrapper was unbounded recursion instead.
The spelling is the package qualifier's, deliberately. A reader who knows
that [rl/draw-fps] is [draw-fps] from the package imported as [rl] already
knows what [builtin/len] is, and needs no second syntax to learn. What
knows what [builtin/length] is, and needs no second syntax to learn. What
makes it work is that [builtin] is reserved rather than resolved: [Load]'s
qualifier comes from the alias in an [import] form and from nowhere else,
so refusing that one alias ([Load.reserved_alias]) is the whole of keeping
@ -364,7 +364,7 @@ let not_a_builtin loc bare =
if bare = "" then
Loc.failk "check/unknown-builtin" loc
"%s needs a name after it — the qualifier reaches a builtin, as \
(%slen v)" builtin_prefix builtin_prefix
(%slength v)" builtin_prefix builtin_prefix
else
match nearest !builtin_names bare with
| Some m ->
@ -378,6 +378,24 @@ let not_a_builtin loc bare =
function is called by the name it was defined under"
bare builtin_prefix bare builtin_prefix
(* One argument of a call, written back out as source. A name is its name and
an integer is its digits; anything with structure inside it a call, a
field, an index becomes the stand-in the caller supplies, because a
suggestion with a hole in it is worse than one that names its blank. Shared
by the refusals that answer a name nothing defines by writing the call the
reader should have written.
This spells one argument and says nothing about how many there are. Whether
the result compiles is the caller's to arrange: a call site that writes out
every argument it was given is only honest where the name it is suggesting
takes that many, so the caller either knows the arity matches or falls back
to a shape of its own. *)
let spell_arg stand_for (a : Ast.expr) =
match a.Ast.e with
| Ast.Var v -> v
| Ast.Int n -> Int64.to_string n
| _ -> stand_for
(* What a [break] or a [continue] may be talking about, innermost first.
[Lloop] is a loop it is lexically inside, carrying its label if it was given
@ -3364,7 +3382,7 @@ and var ctx ?(qualified = false) loc ~want name =
A qualified name that gets past these arms must be refused and not
handed on to the tables below, which is the whole difference between
this and the call path. Falling through would look up [len] in
this and the call path. Falling through would look up [length] in
[env.fns] and answer with the address of the very definition the reader
wrote [builtin/] to get away from the feature inverted, silently. So
the catch-all arm below asks [qualified] before it looks anything up. *)
@ -3411,7 +3429,7 @@ and var ctx ?(qualified = false) loc ~want name =
arms above this one, so it is a builtin that exists only as a call.
There is no value to hand back: a builtin is an arm in the compiler,
not a function in the program, so it has no address for a [Tast.FnAddr]
to carry. Bare [len] in this position says "unknown name"; this says
to carry. Bare [length] in this position says "unknown name"; this says
the true thing instead, which is that the name is real and the position
is wrong. *)
fail loc
@ -6165,7 +6183,7 @@ and vec_at ctx loc (target : Tast.expr) (idx : Ast.expr list) =
it says it reads. What a returned Vec loses is the owner, and losing the
owner is a leak, which this language has already decided is defined
behaviour (spec-memory.md on overwriting a global Vec: "overwrites the
first block and leaks it; there is no drop"). [(len (mk))] and
first block and leaks it; there is no drop"). [(length (mk))] and
[(at (mk) 0)] lose exactly the same owner and are accepted; refusing the
third of those three would be a rule about one spelling rather than about
a hazard, and under a region allocator there is nothing to leak at all. *)
@ -6222,17 +6240,17 @@ and vec_slice ctx ~want loc (target : Tast.expr) elem (bounds : Ast.expr list) =
and named_call ?(qualified = false) ctx ~want loc name args =
let prim p ty args = expect ctx loc ~want (mk loc ty (Tast.Prim (p, args))) in
match name with
(* [builtin/len], before anything else including the shadowing guard below.
(* [builtin/length], before anything else including the shadowing guard below.
The prefix is stripped and the same dispatch runs again with [qualified],
which is the one thing the guard consults: a qualified call has said
which of the two it means, so there is nothing left for shadowing to
decide. Everything after this point sees the bare name, so an arity or a
type refusal on [(builtin/len 1 2)] reads exactly as it does on
[(len 1 2)] which is the point of the spelling, not a loss of detail.
type refusal on [(builtin/length 1 2)] reads exactly as it does on
[(length 1 2)] which is the point of the spelling, not a loss of detail.
Recursion rather than a flag threaded through the arms because there is
only one thing to skip. It cannot loop: the stripped name has no second
[builtin/] on it unless somebody wrote [builtin/builtin/len], which is
[builtin/] on it unless somebody wrote [builtin/builtin/length], which is
stripped once and then refused by name. *)
| _ when not qualified && qualified_builtin name <> None ->
let bare = Option.get (qualified_builtin name) in
@ -6643,7 +6661,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
fail loc
"a pattern cannot destructure %s — a slice's length is not known \
until the program runs, so nothing here can check it has %Ld \
element%s. Use (at s i) and test (len s) yourself"
element%s. Use (at s i) and test (length s) yourself"
(Types.to_string target.Tast.ty) n (plural n)
| other ->
fail loc
@ -6809,7 +6827,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
(* [(vec-new dyn)] is not a [(Vec dyn)]. At milestone 1 the heterogeneous
container is the dyn runtime's own object, and its type is [dyn] like
everything else the runtime hands back which is what lets [push], [at]
and [len] on it go through the dyn operations rather than through a
and [length] on it go through the dyn operations rather than through a
type-erased Vec over eight-byte elements.
The two could be made to coincide later, and the reason not to now is
@ -7596,12 +7614,12 @@ and named_call ?(qualified = false) ctx ~want loc name args =
| _ -> assert false)
(* ── containers ────────────────────────────────────────────────── *)
(* [at] and [len] were already the names for a fixed array and a slice, so a
Vec extends them rather than adding a parallel pair which is the
(* [at] and [length] were already the names for a fixed array and a slice,
so a Vec extends them rather than adding a parallel pair which is the
asymmetry [nth] was removed for. A Vec's length is i32 like every other
length here (index_ty): widening indices is one change across all of them
and not a Vec question. *)
| "len" ->
| "length" ->
arity ctx loc name 1 args;
let target = List.hd args in
let a = check ctx target in
@ -7612,21 +7630,21 @@ and named_call ?(qualified = false) ctx ~want loc name args =
let n = rt loc (Types.Int Types.I64) "flan_vec_len" [ a; here loc ] in
expect ctx loc ~want (mk loc index_ty (Tast.Prim (Tast.Cast index_ty, [ n ])))
(* Extended rather than given a name of its own, for the reason [at] and
[len] were extended over Vec: one question, one word. *)
[length] were extended over Vec: one question, one word. *)
| Types.Map _ ->
let n = rt loc (Types.Int Types.I64) "flan_map_len" [ a; here loc ] in
expect ctx loc ~want (mk loc index_ty (Tast.Prim (Tast.Cast index_ty, [ n ])))
(* A dyn length is an i32 like every other length here, not a dyn holding
one. [len] is what an index loop compares against, and handing back a
boxed number would make [(< i (len xs))] a dyn comparison and a pair of
allocations per iteration. The runtime answers a dyn; it is unboxed at
one. [length] is what an index loop compares against, and handing back a
boxed number would make [(< i (length xs))] a dyn comparison and a
pair of allocations per iteration. The runtime answers a dyn; it is unboxed at
once and narrowed the way the Vec's i64 above is. *)
| Types.Dyn ->
let n = unbox loc (Types.Int Types.I64) (rt loc Types.Dyn "flan_dyn_len" [ a ]) in
expect ctx loc ~want (mk loc index_ty (Tast.Prim (Tast.Cast index_ty, [ n ])))
| other ->
fail loc
"len takes an array, a slice, a string, a Vec or a Map, found %s"
"length takes an array, a slice, a string, a Vec or a Map, found %s"
(Types.to_string other))
| "at" ->
(match args with
@ -7657,7 +7675,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
this line: same node, same checks, same code. Nothing is added at run
time, because neither missing argument needs anything computed
[lo] is 0, and [hi] is the length, which on a fixed array is the
constant [len] already folds to and on a slice or a string is the
constant [length] already folds to and on a slice or a string is the
length word the value is carrying anyway.
The target is read twice when [hi] is the implicit length, so a target
@ -8345,16 +8363,10 @@ and ordinary_call ctx ~want loc name args =
says which. Asked here, after every table, so that a program that
defines an [as-slice] of its own still reaches its own. *)
(* The call is written back out rather than described, and every
argument the reader wrote that can be spelled is spelled. One that
cannot a call, a field, anything with structure becomes the
name it stands for, so the suggestion is always a form that
compiles rather than a form with a hole in it. *)
let spell stand_for (a : Ast.expr) =
match a.Ast.e with
| Ast.Var v -> v
| Ast.Int n -> Int64.to_string n
| _ -> stand_for
in
argument the reader wrote that can be spelled is spelled
([spell_arg]), so the suggestion is always a form that compiles
rather than a form with a hole in it. *)
let spell = spell_arg in
let call =
match args with
| [] -> "(slice v)"
@ -8379,6 +8391,31 @@ and ordinary_call ctx ~want loc name args =
what to write, and the line in [no_such_rand] does. *)
Loc.failk "check/unknown-function" loc "%s"
(Option.get (no_such_rand name))
else if name = "len" then
(* [len] is an ordinary name and the count is [length], so this is the
one sentence a program that reached for the short word needs. It is
said rather than guessed at because the did-you-mean below cannot
reach it: [len] and [length] are three edits apart, and the net is
one. Asked here, after every table and after the shadowing guard at
the head of the dispatch, so a program that defines a [len] of its
own reaches its own this is only ever the answer for a name that
nothing in the program has taken.
The reader's own argument is spelled back only when there is one of
it. [length] takes exactly one, so writing three of them out would
produce a suggestion that is refused for a second reason the moment
it is pasted and a suggestion that does not compile is the bug
this spelling exists to avoid. [as-slice] above can write every
argument out because [slice] takes one, two or three; this cannot,
and the difference is the arity and not the style. *)
let call =
match args with
| [ a ] -> "(length " ^ spell_arg "v" a ^ ")"
| _ -> "(length v)"
in
Loc.failk "check/unknown-function" loc
"there is no len. The number of elements in an array, a slice, a \
string, a Vec or a Map is length. Write %s" call
else
(* The did-you-mean comes first, and for a capitalised head it is asked
of the *type* tables as well: [(Piont 1 2)] with [Point] declared is
@ -8443,7 +8480,8 @@ and ordinary_call ctx ~want loc name args =
carries a slash answers correctly everywhere a call sits inside a
function and wrongly in the one place a call does not: a package's
global initialiser, which is checked with no owner at all. An importer
defining [len] reached inside an imported [(defonce sz i32 (len "abcd"))]
defining [length] reached inside an imported
[(defonce sz i32 (length "abcd"))]
and changed what it computed. The files were never wrong about it.
What it costs is the REPL: an expression evaluated with no file behind it
@ -9292,7 +9330,7 @@ let builtins : (string * string * string) list =
the direction a handler can act on.");
(* containers *)
("len", "len [[n T]|[T]|string|(Vec T)|(Map K V)] i32",
("length", "length [[n T]|[T]|string|(Vec T)|(Map K V)] i32",
"How many elements. One question and one word across an array, a slice, \
a string, a Vec and a Map.");
("at", "at [collection i32 ...] T",
@ -9406,7 +9444,8 @@ let () =
builtin or it did not, and the line is one line and rare.
The second half of the sentence names the way out. Until [builtin/] there
was none: a file that defined [len] had given the builtin [len] up for the
was none: a file that defined [length] had given the builtin [length] up
for the
whole file, and a defn that meant to *wrap* it was unbounded recursion
instead. Saying so here is the cheapest place it can be said the reader
is being told the name was taken over, and the next thing they want to
@ -9498,7 +9537,7 @@ let collect env (decls : Ast.decl list) =
would otherwise be found by LLVM, as [redefinition of function
'@flan.item'], or not at all. A [defn item] and a [defonce item] are two
declarations of one name and are rejected here. *)
(* The qualifier is reserved on this side too. [(defn builtin/len ...)]
(* The qualifier is reserved on this side too. [(defn builtin/length ...)]
reads the reader treats [/] as an ordinary symbol character and would
otherwise land in [env.fns] under a name nothing can ever call, because
[named_call] strips the prefix before any table is consulted. A

View File

@ -51,7 +51,7 @@
shortcut.} [Tast.Bytes] and [Tast.StrOfBytes] are documented there as
non-instructions in both existing backends a [string] {e is} a (ptr, len)
pair over bytes, and the two prims reinterpret rather than convert. Making
[string] a JS string would make [(len s)] count UTF-16 code units where
[string] a JS string would make [(length s)] count UTF-16 code units where
every other backend counts bytes, and the prelude is byte-oriented
throughout ([rune-count], [valid-utf8?], [decode-rune] all take [[u8]]).
So the JS string appears in exactly one place: the argument of
@ -878,7 +878,7 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) =
match x.Tast.ty with
| Types.Array (n, _) -> Printf.sprintf "%Ld" n
| Types.String | Types.Slice _ -> Printf.sprintf "%s.n" (spill f x)
| t -> at loc "(len %s) is not in the JS dialect" (Types.to_string t))
| t -> at loc "(length %s) is not in the JS dialect" (Types.to_string t))
| Tast.At, target :: idx -> index f loc target idx
| Tast.Slice, [ target; lo; hi ] -> (
match evals f [ target; lo; hi ] with

View File

@ -144,8 +144,8 @@ let entries dir suffix =
let qualify alias n = alias ^ "/" ^ n
(* The one alias this module will not hand out. [builtin/len] is the builtin
[len] no matter what a file has defined ([Check]'s [qualified_builtin]),
(* The one alias this module will not hand out. [builtin/length] is the builtin
[length] no matter what a file has defined ([Check]'s [qualified_builtin]),
and that only stays true while nothing else can produce the qualifier
[builtin]. Every qualifier in a finished program comes from [qualify], and
every [qualify] takes its alias from an [import] form, so refusing it here

View File

@ -2,7 +2,7 @@
syntax with the location of the offending form.
Everything not recognised here is a call, which is how a Lisp should work:
[at], [len], [push], [abort] and the rest are ordinary functions resolved
[at], [length], [push], [abort] and the rest are ordinary functions resolved
by the checker, not syntax. *)
open Form
@ -997,8 +997,8 @@ and dvec (p : Form.t) (t : Ast.expr) (items : Form.t list) : Ast.binding list =
match rest with
| None -> []
| Some r ->
(* An ordinary (slice t n (len t)): the tail of the temporary, which is a
local and outlives the body that reads it. Nothing new. *)
(* An ordinary (slice t n (length t)): the tail of the temporary, which
is a local and outlives the body that reads it. Nothing new. *)
let name =
match r.v with
| Sym s -> s
@ -1013,7 +1013,8 @@ and dvec (p : Form.t) (t : Ast.expr) (items : Form.t list) : Ast.binding list =
(Ast.Call (var r.loc "slice",
[ t;
ex r.loc (Ast.Int (Int64.of_int n));
ex r.loc (Ast.Call (var r.loc "len", [ t ])) ])) } ]
ex r.loc
(Ast.Call (var r.loc "length", [ t ])) ])) } ]
in
each 0 elems @ rest_binding

View File

@ -332,7 +332,7 @@ let source = {flan|
(defn reverse [s [$t]] ()
(let [i 0
j (- (len s) 1)]
j (- (length s) 1)]
(while (< i j)
(swap s i j)
(set i (+ i 1))
@ -353,7 +353,7 @@ let source = {flan|
(defn sort [s [$t]] ()
{:where (ordered? $t)}
(let [i 1]
(while (< i (len s))
(while (< i (length s))
(let [j i]
;; `and` short-circuits, which is load-bearing: at j = 0 the left test
;; fails and (at s -1) is never evaluated, so this does not trap.
@ -379,7 +379,7 @@ let source = {flan|
;; thing to want and not only a workaround.
(defn sort-by [s [$t] before? (Fn [$t $t] bool)] ()
(let [i 1]
(while (< i (len s))
(while (< i (length s))
(let [j i]
(while (and (> j 0) (before? (at s j) (at s (- j 1))))
(swap s (- j 1) j)
@ -390,7 +390,7 @@ let source = {flan|
;; language has and a sentinel index is the bug this avoids.
(defn index-of [s [$t] x $t] (Option i32)
{:where (equal? $t)}
(dotimes [i (len s)]
(dotimes [i (length s)]
(when (= (at s i) x)
(return (Some i))))
None)
@ -407,19 +407,19 @@ let source = {flan|
;; different arity, so the different name is honest rather than a workaround.
(defn min-of [s [$t]] (Option $t)
{:where (ordered? $t)}
(if (= (len s) 0)
(if (= (length s) 0)
None
(let [m (at s 0)]
(dotimes [i (len s)]
(dotimes [i (length s)]
(set m (min m (at s i))))
(Some m))))
(defn max-of [s [$t]] (Option $t)
{:where (ordered? $t)}
(if (= (len s) 0)
(if (= (length s) 0)
None
(let [m (at s 0)]
(dotimes [i (len s)]
(dotimes [i (length s)]
(set m (max m (at s i))))
(Some m))))
@ -493,7 +493,7 @@ let source = {flan|
;; here: it is two type variables and a second signature, and nothing has
;; wanted it.
(defn map-in-place [s [$t] f (Fn [$t] $t)] ()
(dotimes [i (len s)]
(dotimes [i (length s)]
(set (at s i) (f (at s i)))))
;; The general fold, of which sum-i32 is the special case with the + written
@ -501,7 +501,7 @@ let source = {flan|
;; as (f acc x) and the order Odin's slice.reduce uses.
(defn reduce [s [$t] init $t f (Fn [$t $t] $t)] $t
(let [acc init]
(dotimes [i (len s)]
(dotimes [i (length s)]
(set acc (f acc (at s i))))
acc))
@ -514,7 +514,7 @@ let source = {flan|
;; the instantiation site, where the element type is concrete.
(defn filter [s [$t] keep? (Fn [$t] bool)] (Vec $t)
(let [v (vec-new t)]
(dotimes [i (len s)]
(dotimes [i (length s)]
(when (keep? (at s i))
(push v (at s i))))
v))
@ -578,7 +578,7 @@ let source = {flan|
;; it.
(defn sum-i32 [s [i32]] i64
(let [t (i64 0)]
(dotimes [i (len s)]
(dotimes [i (length s)]
(set t (+ t (i64 (at s i)))))
t))
@ -591,7 +591,7 @@ let source = {flan|
;; game holds.
(defn sum-f32 [s [f32]] f64
(let [t 0.0]
(dotimes [i (len s)]
(dotimes [i (length s)]
(set t (+ t (f64 (at s i)))))
t))
@ -603,10 +603,10 @@ let source = {flan|
;; result is a bool, an index, or a number.
(defn bytes=? [a [u8] b [u8]] bool
(if (!= (len a) (len b))
(if (!= (length a) (length b))
false
(do
(dotimes [i (len a)]
(dotimes [i (length a)]
(when (!= (at a i) (at b i))
(return false)))
true)))
@ -615,12 +615,12 @@ let source = {flan|
;; built once it is known to be in bounds otherwise a prefix longer than the
;; string would trap rather than answer false.
(defn starts-with? [s [u8] p [u8]] bool
(and (<= (len p) (len s))
(bytes=? (slice s 0 (len p)) p)))
(and (<= (length p) (length s))
(bytes=? (slice s 0 (length p)) p)))
(defn ends-with? [s [u8] p [u8]] bool
(and (<= (len p) (len s))
(bytes=? (slice s (- (len s) (len p)) (len s)) p)))
(and (<= (length p) (length s))
(bytes=? (slice s (- (length s) (length p)) (length s)) p)))
;; The whole slice is an integer, or it is None. bytes->i64 is strtoll, which
;; answers 0 for "" and for "abc" and stops at the first junk byte in "12x"
@ -633,14 +633,14 @@ let source = {flan|
(let [i 0
n (i64 0)
neg false]
(when (= (len s) 0)
(when (= (length s) 0)
(return None))
(when (or (= (at s 0) \-) (= (at s 0) \+))
(set neg (= (at s 0) \-))
(set i 1))
(when (= i (len s))
(when (= i (length s))
(return None)) ; a lone sign is not a number
(while (< i (len s))
(while (< i (length s))
(let [b (at s i)]
(when (or (< b \0) (> b \9))
(return None))
@ -758,7 +758,7 @@ let source = {flan|
;; this file), so a diagnostic would have to be a run-time one, in the one
;; construct whose whole point is that it costs nothing at run time.
(defmacro clamp [& args]
(if (!= (len args) 3)
(if (!= (length args) 3)
`(clamp-takes-a-value-a-low-and-a-high)
`(min ~(at args 2) (max ~(at args 1) ~(at args 0)))))
@ -798,8 +798,8 @@ let source = {flan|
;; sampling would remove it and would consume an unpredictable number of draws,
;; which is the one thing this generator exists not to do.
;;
;; An index is an i32 here (len answers one), so indexing with this reads
;; (at xs (i32 (rand-int-range 0 (i64 (len xs))))).
;; An index is an i32 here (length answers one), so indexing with this reads
;; (at xs (i32 (rand-int-range 0 (i64 (length xs))))).
(defn rand-int-range [lo i64 hi i64] i64
(if (<= hi lo)
lo
@ -1220,12 +1220,12 @@ let source = {flan|
;; Naive, O(n·m), and that is the deliberate choice: BoyerMoore wants a skip
;; table, which is an array sized by the needle, which is an allocation.
(defn index-of-bytes [s [u8] p [u8]] (Option i32)
(when (> (len p) (len s))
(when (> (length p) (length s))
(return None))
(let [last (- (len s) (len p))
(let [last (- (length s) (length p))
i 0]
(while (<= i last)
(when (bytes=? (slice s i (+ i (len p))) p)
(when (bytes=? (slice s i (+ i (length p))) p)
(return (Some i)))
(set i (+ i 1))))
None)
@ -1240,7 +1240,7 @@ let source = {flan|
;; lo would pass hi and (slice s lo hi) would be a reversed range, which traps.
(defn trim [s [u8]] [u8]
(let [lo 0
hi (len s)]
hi (length s)]
(while (and (< lo hi) (space? (at s lo)))
(set lo (+ lo 1)))
(while (and (< lo hi) (space? (at s (- hi 1))))
@ -1269,33 +1269,33 @@ let source = {flan|
(defn parse-f64 [s [u8]] (Option f64)
(let [i 0
digits 0]
(when (or (= (len s) 0) (> (len s) 511))
(when (or (= (length s) 0) (> (length s) 511))
(return None))
(when (or (= (at s 0) \-) (= (at s 0) \+))
(set i 1))
(while (and (< i (len s)) (digit? (at s i)))
(while (and (< i (length s)) (digit? (at s i)))
(set i (+ i 1))
(set digits (+ digits 1)))
(when (and (< i (len s)) (= (at s i) \.))
(when (and (< i (length s)) (= (at s i) \.))
(set i (+ i 1))
(while (and (< i (len s)) (digit? (at s i)))
(while (and (< i (length s)) (digit? (at s i)))
(set i (+ i 1))
(set digits (+ digits 1))))
(when (= digits 0)
(return None)) ; "." and "+" and "e5" are not numbers
(when (and (< i (len s)) (or (= (at s i) \e) (= (at s i) \E)))
(when (and (< i (length s)) (or (= (at s i) \e) (= (at s i) \E)))
(set i (+ i 1))
(when (and (< i (len s)) (or (= (at s i) \-) (= (at s i) \+)))
(when (and (< i (length s)) (or (= (at s i) \-) (= (at s i) \+)))
(set i (+ i 1)))
(let [e 0]
(while (and (< i (len s)) (digit? (at s i)))
(while (and (< i (length s)) (digit? (at s i)))
(set i (+ i 1))
(set e (+ e 1)))
(when (= e 0)
(return None)))) ; a lone exponent marker
;; Trailing junk is the case strtod is silent about, so the position has
;; to land exactly on the end.
(if (= i (len s)) (Some (bytes->f64 s)) None)))
(if (= i (length s)) (Some (bytes->f64 s)) None)))
;; UTF-8
;;
@ -1349,7 +1349,7 @@ let source = {flan|
(!= (bit-and b 0xc0) 0x80))
(defn decode-rune [s [u8]] Rune
(when (= (len s) 0)
(when (= (length s) 0)
(return (Rune {.code 0 .width 0 .ok false})))
(let [b0 (at s 0)]
(when (< b0 0x80)
@ -1375,7 +1375,7 @@ let source = {flan|
(return (Rune {.code 0 .width 1 .ok false})))
;; A sequence cut off by the end of the slice. Width 1, so a caller
;; scanning a buffer boundary makes progress instead of stalling.
(when (> size (len s))
(when (> size (length s))
(return (Rune {.code 0 .width 1 .ok false})))
(let [b1 (at s 1)]
(when (or (< b1 lo) (> b1 hi))
@ -1405,9 +1405,9 @@ let source = {flan|
;; the bytes there are malformed, which is stricter than Odin's rune_at that
;; one hands back RUNE_ERROR and the caller carries on with a wrong character.
(defn rune-at [s [u8] i i32] (Option i32)
(if (or (< i 0) (>= i (len s)))
(if (or (< i 0) (>= i (length s)))
None
(let [r (decode-rune (slice s i (len s)))]
(let [r (decode-rune (slice s i (length s)))]
(if (.ok r) (Some (.code r)) None))))
;; Counted through decode-rune rather than through a second walk of its own.
@ -1420,16 +1420,16 @@ let source = {flan|
(defn rune-count [s [u8]] i32
(let [i 0
n 0]
(while (< i (len s))
(let [r (decode-rune (slice s i (len s)))]
(while (< i (length s))
(let [r (decode-rune (slice s i (length s)))]
(set i (+ i (.width r)))
(set n (+ n 1))))
n))
(defn valid-utf8? [s [u8]] bool
(let [i 0]
(while (< i (len s))
(let [r (decode-rune (slice s i (len s)))]
(while (< i (length s))
(let [r (decode-rune (slice s i (length s)))]
(when (not (.ok r))
(return false))
(set i (+ i (.width r)))))
@ -1467,7 +1467,7 @@ let source = {flan|
(match (rune-size code)
None None
(Some w)
(if (> w (len dst))
(if (> w (length dst))
None
(do
(cond
@ -1516,12 +1516,13 @@ let source = {flan|
(match (index-of (.rest it) (.sep it))
(Some i)
(let [field (slice (.rest it) 0 i)]
(set (.rest it) (slice (.rest it) (+ i 1) (len (.rest it))))
(set (.rest it) (slice (.rest it) (+ i 1) (length (.rest it))))
(Some field))
None
(let [field (.rest it)]
(set (.more it) false)
(set (.rest it) (slice (.rest it) (len (.rest it)) (len (.rest it))))
(set (.rest it)
(slice (.rest it) (length (.rest it)) (length (.rest it))))
(Some field))))
;; ASCII case
@ -1565,10 +1566,10 @@ let source = {flan|
;; half of to_lower and needs no storage at all: comparing two lowered copies
;; is what a caller wanted, and this is that answer without either copy.
(defn bytes-ci=? [a [u8] b [u8]] bool
(if (!= (len a) (len b))
(if (!= (length a) (length b))
false
(do
(dotimes [i (len a)]
(dotimes [i (length a)]
(when (!= (lower-ascii (at a i)) (lower-ascii (at b i)))
(return false)))
true)))
@ -1592,13 +1593,13 @@ let source = {flan|
;;
;; A prefix sorts before what extends it "ab" before "abc" which falls out
;; of running to the shorter length and then comparing lengths, and is the case
;; a loop written to (len a) alone reads off the end for.
;; a loop written to (length a) alone reads off the end for.
(defn bytes<? [a [u8] b [u8]] bool
(let [n (min (len a) (len b))]
(let [n (min (length a) (length b))]
(dotimes [i n]
(when (!= (at a i) (at b i))
(return (< (at a i) (at b i)))))
(< (len a) (len b))))
(< (length a) (length b))))
;; sort-by with the comparison written in, over the same in-place contract:
;; the *slices* move, never the bytes they point at, so this sorts a [[u8]] of
@ -1652,7 +1653,7 @@ let source = {flan|
;; style: a Vec parameter *moves*, so (append b s) taking one by value would
;; consume the caller's builder on the first call and refuse the second.
(defn append [b (Ptr (Vec u8)) s [u8]] ()
(dotimes [i (len s)]
(dotimes [i (length s)]
(push (deref b) (at s i))))
;; The two number appends, and they are the reason this shape is worth having
@ -1677,7 +1678,7 @@ let source = {flan|
;; that reads like a mistake at the call site.
(defn concat [parts [[u8]]] (Vec u8)
(let [b (vec-new u8)]
(dotimes [i (len parts)]
(dotimes [i (length parts)]
(append (addr b) (at parts i)))
b))
@ -1687,7 +1688,7 @@ let source = {flan|
;; exactly that input, because there is no tail to chop.
(defn join [parts [[u8]] sep [u8]] (Vec u8)
(let [b (vec-new u8)]
(dotimes [i (len parts)]
(dotimes [i (length parts)]
(when (> i 0)
(append (addr b) sep))
(append (addr b) (at parts i)))
@ -1706,13 +1707,13 @@ let source = {flan|
;; its own.
(defn to-lower [s [u8]] (Vec u8)
(let [b (vec-new u8)]
(dotimes [i (len s)]
(dotimes [i (length s)]
(push b (lower-ascii (at s i))))
b))
(defn to-upper [s [u8]] (Vec u8)
(let [b (vec-new u8)]
(dotimes [i (len s)]
(dotimes [i (length s)]
(push b (upper-ascii (at s i))))
b))
@ -1732,19 +1733,19 @@ let source = {flan|
(defn replace-bytes [s [u8] from [u8] to [u8]] (Vec u8)
(let [b (vec-new u8)
i 0]
(if (= (len from) 0)
(if (= (length from) 0)
(append (addr b) s)
(while (< i (len s))
(match (index-of-bytes (slice s i (len s)) from)
(while (< i (length s))
(match (index-of-bytes (slice s i (length s)) from)
(Some k)
(do
(append (addr b) (slice s i (+ i k)))
(append (addr b) to)
(set i (+ i k (len from))))
(set i (+ i k (length from))))
None
(do
(append (addr b) (slice s i (len s)))
(set i (len s))))))
(append (addr b) (slice s i (length s)))
(set i (length s))))))
b))
;; A (Vec [u8]) cannot be written at a let, and this one-line function is where
@ -1865,7 +1866,7 @@ let source = {flan|
;; scale by the carry above, so it never needs more, and
;; without the padding 1.005 at three places prints "1.5".
(let [d (i64->bytes fr)]
(dotimes [i (- p (len d))]
(dotimes [i (- p (length d))]
(push b \0))
(append (addr b) d))))))))
b))
@ -1938,9 +1939,9 @@ let source = {flan|
;;
;; It takes a slice rather than the array (embed-dir) answers, because an array
;; length is part of its type and there are no generics: write
;; (embed-find (slice assets 0 (len assets)) "brush.png").
;; (embed-find (slice assets 0 (length assets)) "brush.png").
(defn embed-find [files [EmbedFile] name string] (Option [u8])
(dotimes [i (len files)]
(dotimes [i (length files)]
(when (bytes=? (bytes-view (.name (at files i))) (bytes-view name))
(return (Some (.data (at files i))))))
None)
@ -2091,15 +2092,15 @@ let source = {flan|
(defn form-cons [x Form rest [Form]] [Form]
(let [v (vec-new Form)]
(push v x)
(dotimes [i (len rest)]
(dotimes [i (length rest)]
(push v (at rest i)))
(slice v)))
(defn form-append [a [Form] b [Form]] [Form]
(let [v (vec-new Form)]
(dotimes [i (len a)]
(dotimes [i (length a)]
(push v (at a i)))
(dotimes [i (len b)]
(dotimes [i (length b)]
(push v (at b i)))
(slice v)))
@ -2108,7 +2109,7 @@ let source = {flan|
(defn form-rest [xs [Form] from i32] [Form]
(let [v (vec-new Form)
i from]
(while (< i (len xs))
(while (< i (length xs))
(push v (at xs i))
(set i (+ i 1)))
(slice v)))
@ -2142,7 +2143,7 @@ let source = {flan|
(push v 126) ; ~
(push v 103) ; g
(let [d (i64->bytes gensym-n)]
(dotimes [i (len d)]
(dotimes [i (length d)]
(push v (at d i))))
(Form.Sym {.s (string (slice v))})))
@ -2173,7 +2174,7 @@ let source = {flan|
;; keeping. A test is still required, because there is nothing to negate
;; without one.
(defmacro unless [& args]
(if (< (len args) 1)
(if (< (length args) 1)
`(unless-takes-a-test)
`(if (not ~(at args 0)) (do ~@(form-rest args 1)))))
@ -2230,22 +2231,22 @@ let source = {flan|
;; rl/with-drawing and rl/with-mode-2d already take the same trade on their
;; arguments. Write the index out first if it does anything.
(defmacro inc [& args]
(if (!= (len args) 1)
(if (!= (length args) 1)
`(inc-takes-one-number)
`(+ ~(at args 0) 1)))
(defmacro dec [& args]
(if (!= (len args) 1)
(if (!= (length args) 1)
`(dec-takes-one-number)
`(- ~(at args 0) 1)))
(defmacro ++ [& args]
(if (!= (len args) 1)
(if (!= (length args) 1)
`(++-takes-one-place)
`(set ~(at args 0) (+ ~(at args 0) 1))))
(defmacro -- [& args]
(if (!= (len args) 1)
(if (!= (length args) 1)
`(---takes-one-place)
`(set ~(at args 0) (- ~(at args 0) 1))))
@ -2306,12 +2307,12 @@ let source = {flan|
;; place and the wrong sentence. The bad transform is passed along so that at
;; least it is named.
(defn into-wrap [ts [Form] dst Form x Form] Form
(loop [k (len ts) body `(push ~dst ~x)]
(loop [k (length ts) body `(push ~dst ~x)]
(if (= k 0)
body
(let [t (at ts (- k 1))
items (form-items t)]
(if (!= (len items) 2)
(if (!= (length items) 2)
`(into-transform-is-map-or-filter-of-one-function ~t)
(let [head (at items 0)
f (at items 1)]
@ -2340,7 +2341,7 @@ let source = {flan|
;; body that was not written rather than a body of one form.
(defn form-empty-list? [f Form] bool
(match f
(Form.List xs) (= (len xs) 0)
(Form.List xs) (= (length xs) 0)
_ false))
(defn form-is-sym? [f Form] bool
@ -2354,7 +2355,7 @@ let source = {flan|
;; A source that is already a name is used as it is, and a source that is
;; anything else is bound to one. Both halves matter.
;;
;; Binding it is what a source that is a *call* needs: (len s) and (at s i)
;; Binding it is what a source that is a *call* needs: (length s) and (at s i)
;; have to be the same s, and without the binding the call would be made twice
;; per element.
;;
@ -2362,11 +2363,11 @@ let source = {flan|
;; (let [s v] ...) would hand v's ownership to the macro's own binding and the
;; caller would find v dead after an (into v ...) that only read it and a
;; fixed array would be *copied* into the binding, once per into. Neither is
;; what was written. len and at borrow, so used directly the source is only
;; what was written. length and at borrow, so used directly the source is only
;; read. A source that is a call and produces a Vec is still consumed, which is
;; right: nobody else is holding it.
(defmacro into [& args]
(if (< (len args) 2)
(if (< (length args) 2)
`(into-takes-a-source-a-destination-and-transforms)
(let [from (at args 0)
named? (form-is-sym? from)
@ -2376,7 +2377,7 @@ let source = {flan|
x (gensym)
i (gensym)]
`(let [~dst ~(at args 1) ~@bind]
(dotimes [~i (len ~src)]
(dotimes [~i (length ~src)]
(let [~x (at ~src ~i)]
~(into-wrap (form-rest args 2) dst x)))
~dst))))

View File

@ -226,7 +226,7 @@ let rec cty env ~needed ~loc ~what (t : Ast.texpr) : string =
fail loc
"%s is a slice, and nothing here says what type the C count parameter \
is declare (Ptr T) with an explicit count, and pass \
(addr (at s 0)) and (len s) from Flan"
(addr (at s 0)) and (length s) from Flan"
what
| Ast.Tarray _ ->
fail loc
@ -244,7 +244,7 @@ let rec cty env ~needed ~loc ~what (t : Ast.texpr) : string =
| Ast.Tapp ("Vec", _) ->
fail loc
"%s is a Vec, which owns its storage. Pass (slice v) as (Ptr T) and \
(len v)"
(length v)"
what
| Ast.Tfn _ ->
fail loc "%s is a function type, and a C callback is not implemented" what

View File

@ -3013,7 +3013,7 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) dst =
let l = lvalue f a in
load_int f.b ~dst:rax ~mm:(lmem f (shift l 8) ~scratch:r11) ~size:8
~signed:true
| ty -> unsupported "len of %s" (Types.to_string ty));
| ty -> unsupported "length of %s" (Types.to_string ty));
store_loc f ~reg:rax dst t
| Tast.At, a :: is when is <> [] ->
let l = elements f (lvalue f a) a.Tast.ty is in

View File

@ -104,7 +104,7 @@ world.
names its types: ~(let [enemies (map-new string Enemy)] ...)~. ~get~
returns ~(Option V)~; ~put~ is the ~()~-returning upsert. See
spec-memory.md for the deferred move-aware operations.
- Operations: ~get~, ~put~, ~remove~, ~push~, ~pop~, ~at~, ~len~, ~update~.
- Operations: ~get~, ~put~, ~remove~, ~push~, ~pop~, ~at~, ~length~, ~update~.
Copying is explicit: ~(clone m)~, and owning containers move rather than copy on
assignment. No ~!~ convention — nothing is immutable, so it
would carry no information. No ~assoc~; it only existed as the copy-returning form.
@ -436,7 +436,7 @@ wasm32 target cheap, because a primitive is the only thing implemented twice.
| ~argv~ | ~[string]~, borrowed, never freed |
| ~write-stdout~ | takes ~[u8]~; the ONE output primitive |
| ~exit~ | ~i32~ status |
| ~len~ ~at~ ~slice~ | on fixed arrays and slices |
| ~length~ ~at~ ~slice~ | on fixed arrays and slices |
| ~bytes~ | ~string~ → ~[u8]~, a view, no copy |
| ~bytes->f64~ ~bytes->i64~ | and the inverses, for printing |
| ~addr~ | address of a place |
@ -805,7 +805,7 @@ building the whole live environment at once.
throughput number; with no interpreter that criterion is gone and the narrow
host ABI took its place on the critical path (see Compilation). Packages,
structs, ~(Ptr T)~ + ~addr~, byte slices,
~at~/~len~, ~while~, ~set~ on the fixed place list, ~cond~, ~match~, ~Option~
~at~/~length~, ~while~, ~set~ on the fixed place list, ~cond~, ~match~, ~Option~
+ ~some~, ~i32~/~u8~/~f64~, recursion, argv, stdout. No allocator, no ~Vec~,
no generics, no user macros, no FFI, no window. Headless, so the acceptance
test is a table of expression/result pairs.

View File

@ -1489,7 +1489,7 @@ int64_t flan_dyn_need_i64(flan_dyn v) {
* down in the doc's boundary section so that closing it is a decision somebody
* makes rather than a surprise somebody meets. Softening the check here is the
* option not to take: this function sees a tag and nothing else, so it could
* not tell (g 1) from (g (len xs)). */
* not tell (g 1) from (g (length xs)). */
double flan_dyn_need_f64(flan_dyn v) {
if (flan_dyn_tag(v) != FLAN_DYN_TAG_FLOAT)
trap1(NULL, 0, TYPE_TRAP, "f64", "a float was wanted", v);
@ -2074,10 +2074,10 @@ flan_dyn flan_dyn_len(flan_dyn v) {
}
if (is_vec(v)) {
flan_obj *o = dyn_obj(v);
if (o->kind == OBJ_VIEW) return flan_dyn_from_i64(view_len("len", o));
if (o->kind == OBJ_VIEW) return flan_dyn_from_i64(view_len("length", o));
return flan_dyn_from_i64(o->len);
}
trap1(NULL, 0, TYPE_TRAP, "len", "only a text, a vec or a map has one", v);
trap1(NULL, 0, TYPE_TRAP, "length", "only a text, a vec or a map has one", v);
}
/* The index has to be an int, and that is a separate sentence from the

View File

@ -110,7 +110,7 @@ region" for the rule that stands in its place and for what it costs.
- A view of a `Vec` a call returned is legal and does not dangle — the storage
outlives the expression — but the header was the only handle `free` could
have taken, so that block is leaked unless its allocator reclaims it
wholesale. Same leak as `(len (mk))`, same rule as overwriting a global
wholesale. Same leak as `(length (mk))`, same rule as overwriting a global
`Vec`: manual memory management, and the program's business.
- **The first implementation follows Zig/Odin's explicit model, not Rust's
borrow checker.** Dev builds carry a generation word on `Vec` and trap on use

View File

@ -38,7 +38,7 @@ let generic_src n =
\ (set (at xs j) tmp)))\n\n\
(defn gsort [s [$t] before? (Fn [$t $t] bool)] ()\n\
\ (let [i 1]\n\
\ (while (< i (len s))\n\
\ (while (< i (length s))\n\
\ (let [j i]\n\
\ (while (and (> j 0) (before? (at s j) (at s (- j 1))))\n\
\ (gswap s (- j 1) j)\n\
@ -69,7 +69,7 @@ let mono_src n =
\ (set (at xs j) tmp)))\n\n\
(defn msort-%s [s [%s] before? (Fn [%s %s] bool)] ()\n\
\ (let [i 1]\n\
\ (while (< i (len s))\n\
\ (while (< i (length s))\n\
\ (let [j i]\n\
\ (while (and (> j 0) (before? (at s j) (at s (- j 1))))\n\
\ (mswap-%s s (- j 1) j)\n\

View File

@ -4,24 +4,24 @@
(defn keep [s [$t] keep? (Fn [$t] bool)] (Vec $t)
(let [v (vec-new t)]
(dotimes [i (len s)]
(dotimes [i (length s)]
(when (keep? (at s i))
(push v (at s i))))
v))
(defn apply! [s [$t] f (Fn [$t] $t)] ()
(dotimes [i (len s)]
(dotimes [i (length s)]
(set (at s i) (f (at s i)))))
(defn fold [s [$t] init $t f (Fn [$t $t] $t)] t
(let [acc init]
(dotimes [i (len s)]
(dotimes [i (length s)]
(set acc (f acc (at s i))))
acc))
(defn flip! [s [$t]] ()
(let [i 0
j (- (len s) 1)]
j (- (length s) 1)]
(while (< i j)
(let [tmp (at s i)]
(set (at s i) (at s j))
@ -45,7 +45,7 @@
(println (fold xs 0 (fn [a b] (+ a b))))
(println (fold ys (f32 0) (fn [a b] (+ a b))))
(let [evens (keep xs (fn [x] (= (% x 20) 0)))]
(println (len evens))
(println (length evens))
(free evens))
(println (at xs 0))
(println (at ys 0))))

View File

@ -9,7 +9,7 @@
(defn sort-by! [s [$t] before? (Fn [$t $t] bool)] ()
(let [i 1]
(while (< i (len s))
(while (< i (length s))
(let [j i]
(while (and (> j 0) (before? (at s j) (at s (- j 1))))
(swap! s (- j 1) j)

View File

@ -14,12 +14,12 @@
;;;; A backend that quietly builds the slice exits 0 while the other exits 134
;;;; and the survey says DIFFER.
;;;;
;;;; The two ends come from (len args), which is 1 for a program run with no
;;;; The two ends come from (length args), which is 1 for a program run with no
;;;; arguments and is not a number either optimiser can see, so the branch
;;;; cannot be folded away and the checker has no literal to object to.
(defn main [args [string]] i32
(let [s (bytes-view "hello")
hi (i32 (len args))
hi (i32 (length args))
lo (+ hi 1)]
(print (slice s lo hi))
(println ""))

View File

@ -45,11 +45,11 @@
;; main's own root outlived every collection build triggered.
(let [keep "kept"
xs (build 40000)]
(print (len xs))
(print (length xs))
(print "\n")
(print (at xs 0))
(print "\n")
(print (at xs (- (len xs) 1)))
(print (at xs (- (length xs) 1)))
(print "\n")
(print keep)
(print "\n")))

View File

@ -14,7 +14,7 @@
(+ (+ (+ a b) (+ c d)) (+ (+ e f) (+ g h))))
(defn taglen [s [u8]] i64
(i64 (len s)))
(i64 (length s)))
(defn main [] i32
(let [v (V3 {.x 1.0 .y 2.0 .z 3.0})

View File

@ -20,7 +20,7 @@
;; n is a parameter, so the checker has no literal to look at.
(restart-case
(let [s (slice-from-ptr (addr (at a 0)) n)]
(len s))
(length s))
(give-up [] -1)))
(defn show [name string n i64] ()

View File

@ -70,9 +70,9 @@
;; inferred from the argument types; there is no explicit instantiation.
(defn largest [xs [$t] gt (Fn [$t $t] bool)] (Option $t)
{:where (copyable? $t)}
(if (> (len xs) 0)
(if (> (length xs) 0)
(let [best (at xs 0)]
(dotimes [i (len xs)]
(dotimes [i (length xs)]
(when (gt (at xs i) best) (set best (at xs i))))
(Some best))
None))
@ -82,9 +82,9 @@
;; and each instantiation is checked against the ones the signature declares.
(defn smallest [xs [$t]] (Option $t)
{:where (ordered? $t)}
(if (> (len xs) 0)
(if (> (length xs) 0)
(let [m (at xs 0)]
(dotimes [i (len xs)] (set m (min m (at xs i))))
(dotimes [i (length xs)] (set m (min m (at xs i))))
(Some m))
None))
@ -106,7 +106,7 @@
;; exists, this is a `dotimes` accumulating into a local.
(defn centroid [es [Enemy]] Vec2
(/ (reduce (fn [acc e] (+ acc (.pos e))) [0 0] es)
(f32 (len es))))
(f32 (length es))))
;; ── Handles, not pointers, for anything cross-referenced ──────────────
;; Pattern bindings bind VALUES, so matching a struct out of a pool would give
@ -201,7 +201,7 @@
(push errors c) ; value struct: copies out of
(invoke-restart 'skip-form))] ; the signalling frame
(let [ast (parse-all (parser src))]
(if (zero? (len errors))
(if (zero? (length errors))
(Ok ast)
(Err (Errors errors))))))) ; errors moves into the Err

View File

@ -539,6 +539,19 @@ static void refuse_view(const char *what) {
} else if (strcmp(what, "flatpush") == 0) {
v = flan_dyn_view_flat(buf, 2, FLAN_VIEW_I64);
flan_dyn_push(v, flan_dyn_from_i64(9));
} else if (strcmp(what, "stalelen") == 0) {
/* A Vec view whose allocator has moved on, asked for its length. The
* operator name is what this mode is for: the sentence is built from the
* string the entry point hands down, and [flan_dyn_len] has two of them
* its own trap and this one which is how one of them came to be a
* word the language no longer has. The prefix of flan_allocator is
* restated here for the same reason [hand_vec] restates flan_vec's. */
static struct { void *proc; void *data; uint32_t caps; uint64_t epoch; }
alloc = { NULL, NULL, 0, 7 };
static hand_vec hv;
hv.ptr = buf; hv.len = 2; hv.cap = 2; hv.alloc = &alloc; hv.epoch = 3;
v = flan_dyn_view_vec(&hv, FLAN_VIEW_I64);
(void)flan_dyn_len(v);
} else {
printf("no such refusal: %s\n", what);
exit(2);

View File

@ -20,7 +20,7 @@
ticks)
(defn main [args [string]] i32
(if (< (len args) 2)
(if (< (length args) 2)
(do (println "usage: agent-queue <socket>") 2)
(do
(if (< (agent/start (at args 1)) 0)

View File

@ -20,7 +20,7 @@
ticks)
(defn main [args [string]] i32
(if (< (len args) 2)
(if (< (length args) 2)
(do (println "usage: agent <socket>") 2)
(do
(if (< (agent/start (at args 1)) 0)

View File

@ -8,13 +8,13 @@
;;;; that the concrete ones are right.
(defn show-f32 [s [f32]] ()
(dotimes [i (len s)]
(dotimes [i (length s)]
(print (at s i))
(print " "))
(println ""))
(defn show-fields [s [[u8]]] ()
(dotimes [i (len s)]
(dotimes [i (length s)]
(print (string (at s i)))
(print " "))
(println ""))
@ -83,7 +83,7 @@
;; bytes<? is bytewise and unsigned, and explicitly not alphabetical: "Zebra"
;; comes before "apple" because 'Z' is 90. A prefix comes before what extends
;; it, which is the case a loop running only to (len a) reads off the end
;; it, which is the case a loop running only to (length a) reads off the end
;; for, and 0x80 above 0x00 is the case a signed byte gets backwards.
(print (bytes<? (bytes-view "a") (bytes-view "b"))) (print " ") ; true
(print (bytes<? (bytes-view "b") (bytes-view "a"))) (print " ") ; false

View File

@ -25,7 +25,7 @@
(defn main [args [string]] i32
(set frame (arena-new 4096))
(let [which (if (> (len args) 1) (i32 (bytes->i64 (bytes-view (at args 1)))) 0)]
(let [which (if (> (length args) 1) (i32 (bytes->i64 (bytes-view (at args 1)))) 0)]
(cond
(= which 1)
;; The refusal. The context here is the heap, which can free one
@ -33,7 +33,7 @@
;; the slots and strand every inner Vec — so the construction dies
;; rather than the free three hundred lines later.
(let [bad (vec-new Value)]
(println (len bad)))
(println (length bad)))
(= which 2)
;; Use after free-all, which is a different mechanism and worth
@ -50,9 +50,9 @@
(push outer (Value.List {.items inner})))
(match (at outer 0)
(List items)
(do (println (len items))
(do (println (length items))
(free-all frame)
(println (len items)))
(println (length items)))
_ (println 0)))
(= which 3)
@ -66,7 +66,7 @@
(match v
(List items)
(do (push items (Value.Int {.n (i64 1)}))
(println (len items)))
(println (length items)))
_ (println 0)))
:else
@ -83,8 +83,8 @@
(push row 1)
(push row 2)
(push rows row))
(println (len rows))
(println (len (at rows 0)))))
(println (length rows))
(println (length (at rows 0)))))
(free-all frame)
;; And the same container against the heap dies — asserted from the
;; other side in run 1 above; here the point is only that the region
@ -92,7 +92,7 @@
(with-allocator frame
(let [vs (vec-new Value)]
(push vs (Value.Int {.n (i64 41)}))
(println (len vs))))
(println (length vs))))
(free-all frame)
;; And the zeroed field of run 3, this time in the region: the growth
;; guard has to pass here as surely as it has to fail there, or every
@ -102,7 +102,7 @@
(match v
(List items)
(do (push items (Value.Int {.n (i64 1)}))
(println (len items)))
(println (length items)))
_ (println 0))))
(free-all frame))))
(arena-destroy frame)

View File

@ -52,7 +52,7 @@
(Int n) n
(List items)
(let [t (i64 0)]
(dotimes [i (len items)]
(dotimes [i (length items)]
(set t (+ t (total (at items i)))))
t)
(Table entries)
@ -66,9 +66,9 @@
(push outer (number-list (+ i 2))))
(push outer (a-table))
(push outer Value.Nil)
(println (len outer))
(println (length outer))
(let [t (i64 0)]
(dotimes [i (len outer)]
(dotimes [i (length outer)]
(set t (+ t (total (at outer i)))))
t)))

View File

@ -87,7 +87,7 @@
(defn slice-frame [s [u8] lo i32 hi i32] ()
(restart-case
(do (show "slice" (i64 (len (slice s lo hi))))
(do (show "slice" (i64 (length (slice s lo hi))))
(set frames (+ frames 1)))
(continue [] (set skipped (+ skipped 1)))))
@ -166,11 +166,11 @@
(show "low" low)
(show "length" length)
(restart-case
(do (show "vec-slice" (i64 (len (slice v 0 2))))
(do (show "vec-slice" (i64 (length (slice v 0 2))))
(set frames (+ frames 1)))
(continue [] (set skipped (+ skipped 1))))
(restart-case
(do (show "vec-slice" (i64 (len (slice v 0 9))))
(do (show "vec-slice" (i64 (length (slice v 0 9))))
(set frames (+ frames 1)))
(continue [] (set skipped (+ skipped 1))))
(show "high" high))

View File

@ -39,7 +39,7 @@
;; number is not absurd. Signed, deliberately: the comparisons the other
;; two checks use are unsigned, and a negative i32 sign-extended to i64
;; is a huge unsigned value that sails straight through them.
(= n -2) (print (len (slice-from-ptr (addr (at arr 0)) n)))
(= n -2) (print (length (slice-from-ptr (addr (at arr 0)) n)))
:else (println "?"))
0))

View File

@ -2,14 +2,16 @@
;;;;
;;;; Shadowing a builtin is legal and program-wide within the file that does
;;;; it (shadow-builtin.flan is that rule on its own). What it used to cost
;;;; was the builtin itself: a (defn len ...) had given up the builtin len for
;;;; was the builtin itself: a (defn length ...) had given up the builtin
;;;; length for
;;;; the whole file, so a definition that meant to *wrap* the builtin was
;;;; unbounded recursion instead, and stack-overflowed at run time with
;;;; nothing for the compiler to object to.
;;;;
;;;; builtin/len is the way out, and it is the package qualifier's own
;;;; builtin/length is the way out, and it is the package qualifier's own
;;;; spelling: rl/draw-fps is draw-fps from the package imported as rl, and
;;;; builtin/len is len from the compiler. builtin is a reserved qualifier —
;;;; builtin/length is length from the compiler. builtin is a reserved
;;;; qualifier —
;;;; an import may not take it as an alias — so the two can never be confused
;;;; about which one a name means.
;;;;
@ -19,10 +21,10 @@
;;;; qualifier is legal whether or not anything is shadowed; a spelling that
;;;; only compiled while some other declaration existed would be a spelling
;;;; nobody could write down in advance.
;;;; - 5: this file's own len, which is a real wrapper — it is "1 + the
;;;; - 5: this file's own length, which is a real wrapper — it is "1 + the
;;;; builtin length", and the inner call reaches the builtin rather than
;;;; itself. This is the program the earlier lane could not write.
;;;; - 4: builtin/len in the same file, beside the bare name that means the
;;;; - 4: builtin/length in the same file, beside the bare name that means the
;;;; definition. Both spellings, one program, different answers.
;;;; - 99: the shadowed operator, unchanged. An operator is a builtin like any
;;;; other and shadows like any other.
@ -31,16 +33,17 @@
;;;; begin with a digit or a sign, so an operator needs no exception here.
;;; The wrapper, written the way the dead end said could not be written: the
;;; inner builtin/len is the compiler's len, and the outer name is this one.
;;; inner builtin/length is the compiler's length, and the outer name is this
;;; one.
;;; Both additions are qualified too, since + is shadowed below and this
;;; function wants the arithmetic and not the 99.
(defn len [s string] i32 (builtin/+ 1 (builtin/len s)))
(defn length [s string] i32 (builtin/+ 1 (builtin/length s)))
(defn + [a i32 b i32] i32 99)
(defn main [] ()
(println (builtin/max 3 9))
(println (len "abcd"))
(println (builtin/len "abcd"))
(println (length "abcd"))
(println (builtin/length "abcd"))
(println (+ 1 2))
(println (builtin/+ 1 2)))

View File

@ -24,7 +24,7 @@
;; 3. The view still costs nothing and reads the string's own storage.
(let [v (bytes-view "abc")]
(println (len v)) ; 3
(println (length v)) ; 3
(println (at v 2))) ; 99
;; 4. (bytes s a) names the allocator, like (vec-new T a) and (clone v a):

View File

@ -46,7 +46,7 @@
;; The statics, untouched: zero, zero, zero, and an empty Vec that is a real
;; empty Vec rather than a placeholder.
(print current-color) (print " ") (print (at grid 1 2)) (print " ")
(print (.x origin)) (print " ") (print (len bytes)) (println "")
(print (.x origin)) (print " ") (print (length bytes)) (println "")
;; The dyn globals, as their initialisers left them.
(print score) (print " ") (print label) (print " ")

View File

@ -72,13 +72,14 @@
(print b) (print " ")
(print c) (println ""))
;; & rest is the tail as a slice, which is an ordinary (slice xs n (len xs))
;; & rest is the tail as a slice, which is an ordinary
;; (slice xs n (length xs))
;; over a local — nothing new, and nothing that outlives the array.
(let [xs [1 2 3 4 5]
[head & tail] xs]
(print "rest ")
(print head) (print " ")
(print (len tail)) (print " ")
(print (length tail)) (print " ")
(print (at tail 0)) (print " ")
(print (at tail 3)) (println ""))
@ -88,7 +89,7 @@
[p q & rest] xs]
(print "empty-tail ")
(print (+ p q)) (print " ")
(print (len rest)) (println ""))
(print (length rest)) (println ""))
;; Patterns nest through each other: a struct inside an array.
(let [ps [(Point {.x 1 .y 2}) (Point {.x 3 .y 4})]
@ -97,13 +98,13 @@
;; A tail of something wider than a machine word. The corpus slices arrays of
;; i32, u8 and f32 and nothing else, so this is the one place the desugared
;; (slice xs n (len xs)) has to get a struct's stride right rather than a
;; (slice xs n (length xs)) has to get a struct's stride right rather than a
;; scalar's.
(let [ps [(Point {.x 1 .y 2}) (Point {.x 3 .y 4}) (Point {.x 5 .y 6})]
[first & others] ps]
(print "struct-tail ")
(print (.x first)) (print " ")
(print (len others)) (print " ")
(print (length others)) (print " ")
(print (.y (at others 0))) (print " ")
(print (.x (at others 1))) (println ""))

View File

@ -4,7 +4,8 @@
;;;; class's own name, positional over the slots; the slots are ordinary map
;;;; keys, so get and put are how one is read and written and nothing new was
;;;; needed for either. What the class adds is the tag, which lives in the
;;;; object's header and not in the entries: (len p) is the slot count, no key
;;;; object's header and not in the entries: (length p) is the slot count, no
;;;; key
;;;; a program can write collides with it, and it shows up in exactly three
;;;; places -- class-of, equality, and the printed form #point{ :x 1 :y 2}.
;;;;
@ -57,7 +58,7 @@
c (circle 2)]
;; The instance is a map, and prints as one with its tag in front.
(println p)
(println (len p))
(println (length p))
(println (get p :x))
(put p :x 10)
(println (get p :x))
@ -101,7 +102,7 @@
;; An instance is an ordinary dyn value: it goes in a vec, keys a map,
;; and is collected like anything else.
(let [v [p c]]
(println (len v))
(println (length v))
(println (class-of (at v 1))))
;; The renamed parameters, in the generic's own order: the first element
@ -131,7 +132,7 @@
(let [total (point 0 0)]
(dotimes [i 50000]
(let [q (point i "forty-seven bytes of text to fatten each row")]
(put total :x (+ (get total :x) (len q)))))
(put total :x (+ (get total :x) (length q)))))
(println (get total :x))
(println (class-of total)))
0)

View File

@ -16,7 +16,7 @@
;; The literal, and what it prints as.
(let [m {:a 1 :b "two" :xs [1 2 3] :inner {:c 2.5}}]
(println m)
(println (len m))
(println (length m))
(println (get m :a))
(println (get m :b))
(println (get m :xs))
@ -35,7 +35,7 @@
;; put replaces an equal key's value in place; the length holds still.
(put m :a 99)
(println (get m :a))
(println (len m))
(println (length m))
;; Keywords: identity equality, printing, and the runtime constructor —
;; (keyword "a") has to be the same word as the literal :a.
@ -48,7 +48,7 @@
;; Keys are whole values compared structurally: a text and a keyword are
;; two keys, and a vec of numbers can key a map.
(put m "a" "text key")
(println (len m))
(println (length m))
(println (get m "a"))
(put m [1 2] "vec key")
(println (get m [1 2]))
@ -65,6 +65,6 @@
(set config {:total 0})
(dotimes [i 200000]
(let [row {:i 1 :s "forty-seven bytes of text to fatten each row" :v [1 2 3]}]
(put config :total (+ (get config :total) (len row)))))
(put config :total (+ (get config :total) (length row)))))
(println (get config :total))
0)

View File

@ -77,7 +77,7 @@
;;; An aggregate parameter, which arrives in its slot before the roots are
;;; pushed. Zeroing its dyn words over the top of the argument would be a
;;; silent miscompile, so the count this returns is the check for it.
(defn row-len [r Row] i64 (i64 (len (.rows r))))
(defn row-len [r Row] i64 (i64 (length (.rows r))))
;;; The payload crosses as a pointer to a value in this frame, and the handler
;;; below allocates before it reads it.
@ -127,7 +127,7 @@
(print stalls) (print "\n")
(print echoed) (print "\n")
(print (.name (.tag registry))) (print "\n")
(print (len (.rows registry))) (print "\n")
(print (length (.rows registry))) (print "\n")
(print (at (.rows registry) 0)) (print "\n")
;; The two that mangle alike, read after forty thousand rows of churn
;; collected over them many times.

View File

@ -2,7 +2,7 @@
;;;;
;;;; (vec-new dyn) is not a (Vec dyn) — it is the dyn runtime's own vector, and
;;;; its type is dyn like everything else the runtime hands back. That is what
;;;; lets push, at and len on it be the dyn operations rather than a
;;;; lets push, at and length on it be the dyn operations rather than a
;;;; type-erased Vec over eight-byte elements, and it is why no allocator is
;;;; named: the storage is the collector's to walk.
@ -12,13 +12,13 @@
(push xs 2.5)
(push xs "three")
(push xs true)
(print (len xs))
(print (length xs))
(print "\n")
(print xs)
(print "\n")
;; Read back out one at a time, to show that at answers a dyn and that the
;; four of them are still four different things.
(dotimes [i (len xs)]
(dotimes [i (length xs)]
(print (at xs i))
(print " "))
(print "\n")))

View File

@ -79,7 +79,7 @@
;; same header the view points at — so the typed side, asked
;; afterwards, already agrees with the push it never made itself.
(push dv 40)
(print (len v))
(print (length v))
(print "\n")
(print (at v 3))
(print "\n"))
@ -130,7 +130,7 @@
(print dr)
(print "\n")
(push dr 333)
(print (len (at rows 0)))
(print (length (at rows 0)))
(print "\n")
(print (at (at rows 0) 2))
(print "\n"))

View File

@ -35,7 +35,7 @@
;; 54 pairs, and the same three memberships and one miss. A derivation
;; that flattened the pairs into 108 integers would have a different count
;; and would answer no to every one of these.
(println (len (.selected-cells t)))
(println (length (.selected-cells t)))
(println (has-key? (.selected-cells t) [3 4]))
(println (has-key? (.selected-cells t) [0 0]))
(println (has-key? (.selected-cells t) [4 11]))
@ -47,11 +47,11 @@
(println (.hp t))
(println (.speed t))
(println (if (.boss? t) "yes" "no"))
(println (len (.drops t)))
(println (length (.drops t)))
;; 3 + 1 + 4 + 1 + 5. A vector read that stopped at the first element would
;; still have a plausible length from a zeroed Vec, so the sum is the claim.
(let [total (i64 0)]
(dotimes [i (len (.drops t))]
(dotimes [i (length (.drops t))]
(set total (+ total (at (.drops t) i))))
(println total))
;; The nested structs, by the names the paths give them: Tuning-hitbox and

View File

@ -42,7 +42,7 @@
(let [doc (edn/read tileset)
cells (get doc :selected-cells)]
(println (get doc :texture-path))
(println (len cells))
(println (length cells))
;; Two cells that are in the file and one that is not. A reader that
;; flattened the pairs into 108 integers would still have the right count
;; of *something*, and would miss all of these; [3 4] answering yes where
@ -55,7 +55,7 @@
;; The size of a set after the dedup, which is the whole of what the dedup can
;; be asked for.
(defn set-size [src string] ()
(println (len (edn/read (bytes-view src)))))
(println (length (edn/read (bytes-view src)))))
;; Malformed input, told apart from the document `nil` by the cursor — the
;; return value alone cannot say it, and this is the spelling that can.
@ -73,7 +73,7 @@
(append (addr buf) (bytes-view "{:name \"level-1\" :xs [1 2]}"))
(let [src (slice buf)
v (edn/read src)]
(dotimes [i (len src)]
(dotimes [i (length src)]
(set (at src i) \x))
(println (get v :name)))))

View File

@ -19,7 +19,7 @@
(defn main [] i32
;; The default answer is a [u8]: bytes, because that is what an asset is.
(let [a (embed "assets/a.txt")]
(println (len a)) ; 13
(println (length a)) ; 13
(print (string a))) ; hello from a
;; `string` is the second spelling, not a different meaning for the same
@ -32,7 +32,7 @@
;; escape hex-escapes everything outside printable ASCII, so a PNG makes the
;; round trip through the .ll unchanged.
(let [raw (embed "assets/raw.bin")]
(println (len raw)) ; 4
(println (length raw)) ; 4
(println (at raw 0)) ; 0
(println (at raw 2)) ; 255
(println (at raw 3))) ; 254
@ -40,7 +40,7 @@
;; A directory embed is a fixed array of EmbedFile, sorted by name — sorted
;; because readdir order is filesystem-dependent and an unsorted embed would
;; make two builds of identical sources emit different .ll.
(println (len assets)) ; 3
(println (length assets)) ; 3
(println (.name (at assets 0))) ; a.txt
(println (.name (at assets 1))) ; b.bin
(println (.name (at assets 2))) ; raw.bin
@ -48,7 +48,7 @@
;; The name-to-bytes lookup is a linear scan in the prelude. It takes a slice
;; rather than the array, because an array's length is part of its type and
;; there are no generics.
(let [all (slice assets 0 (len assets))]
(let [all (slice assets 0 (length assets))]
(match (embed-find all "b.bin")
(Some b) (println (string b)) ; BBB
None (println "missing"))

View File

@ -53,7 +53,7 @@
;; push that failed is re-attempted. No push is lost: a failed push
;; appends nothing and the retry appends exactly once.
(dotimes [i 64] (push v (* i 2)))
(println (len v)) ; 64
(println (length v)) ; 64
(println (at v 0)) ; 0
(println (at v 63)) ; 126
(free v)))
@ -76,11 +76,11 @@
(invoke-restart 'retry))]
(let [v (vec-new i32 tight)]
(reserve v 256)
(println (len v)) ; 0
(println (length v)) ; 0
(dotimes [i 8] (push v i))
(set-alloc-budget tight 4128)
(let [w (clone v)]
(println (len w)) ; 8
(println (length w)) ; 8
(println (at w 7)) ; 7
(free w))
(free v)))
@ -106,7 +106,7 @@
(set-alloc-budget tight 4096)
(invoke-restart 'retry))]
(let [b (bytes "INSERTIONSORT" tight)]
(println (len b)) ; 13 — the whole string, not a prefix
(println (length b)) ; 13 — the whole string, not a prefix
(println (at b 0)) ; 73 — \I
(println (at b 12)) ; 84 — \T, the last byte
;; Writable, which is the point of the copy, and the literal is untouched.

View File

@ -11,12 +11,12 @@
;; The shape map/filter/reduce want: the function arrives as a parameter, is
;; called, and is never stored.
(defn each [xs [i32] f (Fn [i32] i32)] ()
(dotimes [i (len xs)]
(dotimes [i (length xs)]
(set (at xs i) (f (at xs i)))))
(defn fold [xs [i32] f (Fn [i32] i32)] i32
(let [t 0]
(dotimes [i (len xs)]
(dotimes [i (length xs)]
(set t (+ t (f (at xs i)))))
t))
@ -24,7 +24,7 @@
;; told the order rather than having it written in. Insertion sort, because the
;; point here is the parameter and not the algorithm.
(defn insertion-by [xs [i32] before? (Fn [i32 i32] bool)] ()
(dotimes [i (len xs)]
(dotimes [i (length xs)]
(let [j i]
(while (and (> j 0) (before? (at xs j) (at xs (- j 1))))
(swap xs j (- j 1))

View File

@ -20,7 +20,7 @@
(defn sorted [xs [$t]] (Vec $t)
{:where (ordered? $t)}
(let [v (vec-new $t)]
(dotimes [i (len xs)] (push v (at xs i)))
(dotimes [i (length xs)] (push v (at xs i)))
(sort (slice v))
v))
@ -29,7 +29,7 @@
(defn sorted-in [xs [$t] al Allocator] (Vec $t)
{:where (ordered? $t)}
(let [v (vec-new $t al)]
(dotimes [i (len xs)] (push v (at xs i)))
(dotimes [i (length xs)] (push v (at xs i)))
(sort (slice v))
v))
@ -38,9 +38,9 @@
(defn distinct-count [ks [$k]] i32
{:where (hashable? $k)}
(let [m (map-new $k i32)]
(dotimes [i (len ks)]
(dotimes [i (length ks)]
(put m (at ks i) 1))
(let [n (len m)] (free m) n)))
(let [n (length m)] (free m) n)))
;; The zeroed fixed array. Its length is still a compile-time constant; only
;; the element type is the variable, and that is answered per copy.
@ -74,7 +74,7 @@
(println (at (slice a) 0)) ; 1
(println (at (slice a) 3)) ; 9
(println (at (slice b) 0)) ; 0.5
(println (len (slice b))) ; 3
(println (length (slice b))) ; 3
(free a)
(free b))
@ -105,7 +105,7 @@
(invoke-restart 'retry))]
(let [ns [9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4]
d (sorted-in (slice ns 0 16) tight)]
(println (len (slice d))) ; 16
(println (length (slice d))) ; 16
(println (at (slice d) 0)) ; 0
(free d)))
(println (> failures 0)) ; true

View File

@ -23,12 +23,12 @@
;; The variable is bound *inside* a type constructor, which is a structural
;; walk rather than a name match.
(defn first-or [s [$t] d $t] $t
(if (= (len s) 0) d (at s 0)))
(if (= (length s) 0) d (at s 0)))
;; A generic calling a generic at its own variable: the copy of [swap] is
;; generated when [rotate] is instantiated and not before.
(defn rotate [s [$t]] ()
(dotimes [i (- (len s) 1)]
(dotimes [i (- (length s) 1)]
(swap s i (+ i 1))))
;; numeric? admits + - * / %.
@ -41,7 +41,7 @@
(defn count-of [s [$t] x $t] i32
{:where (equal? $t)}
(let [n 0]
(dotimes [i (len s)]
(dotimes [i (length s)]
(when (= (at s i) x)
(set n (+ n 1))))
n))
@ -230,8 +230,8 @@
empty (vec-new u8)]
(push full 65)
(push full 66)
(println (len (or-else (Some full) empty))) ; 2, full's header
(println (len (or-else (none-vec) empty))) ; 0, empty's
(println (length (or-else (Some full) empty))) ; 2, full's header
(println (length (or-else (none-vec) empty))) ; 0, empty's
(free full)
(free empty))
@ -246,7 +246,7 @@
(let [a (arena-new 4096)
keep (filter (slice ns 0 4) (fn [x] (> x 5)))
one (one-of 4.5)]
(println (len (slice keep)))
(println (length (slice keep)))
(println (at (slice one) 0))
(free one)
(free keep)

View File

@ -73,7 +73,7 @@
;; program's when main runs.
(push names 11)
(push names 22)
(println (len (slice names)))
(println (length (slice names)))
(println (at names 1))
(free names)
;; And the arena, used the way sand.flan means to use it.

View File

@ -169,7 +169,7 @@
(let [v (vec-new i32)]
(push v 1)
(error (Missing {.id n}))
(i32 (len v))))
(i32 (length v))))
[(Missing [c] (+ 20 (.id c)))]))
(defn main [] i32
@ -226,6 +226,6 @@
(arena-destroy frame)
(let [h (vec-new i32)]
(push h 9)
(print (len h)) (println "")
(print (length h)) (println "")
(free h))
0)

View File

@ -26,7 +26,7 @@
;; filter allocates and the caller frees.
(let [v (filter s odd?)]
(print (len v)) (print " ") (print (at v 0)) (println "")
(print (length v)) (print " ") (print (at v 0)) (println "")
(free v))
;; A comparator sort, both directions off the same slice.
@ -43,7 +43,7 @@
(print (at t 0)) (print " ") (print (at t 2)) (println "")
(print (reduce t 0.0 (fn [a b] (+ a b)))) (println "")
(let [w (filter t big?)]
(print (len w)) (println "")
(print (length w)) (println "")
(free w))
(sort-by t (fn [a b] (> a b)))
(print (at t 0)) (print " ") (print (at t 3)) (println ""))

View File

@ -1,6 +1,7 @@
;;;; The reserved qualifier, from the import's side. Refused, never built.
;;;;
;;;; builtin/len means the compiler's len and must go on meaning it, which is
;;;; builtin/length means the compiler's length and must go on meaning it,
;;;; which is
;;;; only true while nothing else can produce the qualifier "builtin". Every
;;;; qualifier in a finished program comes from an import's alias, so refusing
;;;; that one alias is the whole of the reservation — there is no other door.

View File

@ -64,7 +64,7 @@
(defn total [xs [$t]] i32
{:where (integer? $t)}
(let [acc 0]
(dotimes [i (len xs)] (set acc (+ acc (i32 (at xs i)))))
(dotimes [i (length xs)] (set acc (+ acc (i32 (at xs i)))))
acc))
;; The same rule widening. i64 → f64 rounds above 2^53 and i32 → f64 does
@ -74,8 +74,8 @@
(defn mean [xs [$t]] f64
{:where (integer? $t)}
(let [sum 0.0]
(dotimes [i (len xs)] (set sum (+ sum (f64 (at xs i)))))
(/ sum (f64 (len xs)))))
(dotimes [i (length xs)] (set sum (+ sum (f64 (at xs i)))))
(/ sum (f64 (length xs)))))
;; numeric? is the weaker bound and narrowing is legal under it too, because
;; it is legal at every type it admits: (i32 x) on a written f64 truncates

View File

@ -33,13 +33,13 @@
;; see, so an owning temporary here would be a leak nobody can reach.
(defn source [xs [i32]] [i32]
(set builds (+ builds 1))
(slice xs 0 (len xs)))
(slice xs 0 (length xs)))
(defn wide [x i32] f32 (f32 x))
(defn bigf? [x f32] bool (> x 2.5))
(defn show [v [i32]] ()
(dotimes [i (len v)] (print (at v i)) (print " "))
(dotimes [i (length v)] (print (at v i)) (print " "))
(println ""))
(defn main [] i32
@ -80,7 +80,7 @@
;; each stage reads the stage before it.
(let [xs [1 2 3 4]
v (into xs (vec-new f32) (map wide) (filter bigf?))]
(dotimes [i (len v)] (print (at v i)) (print " "))
(dotimes [i (length v)] (print (at v i)) (print " "))
(println "") ; 3 4
(free v))

View File

@ -23,11 +23,11 @@
(println (.port cfg))
(println (.scale cfg))
(println (if (.debug cfg) "yes" "no"))
(println (len (.layers cfg)))
(println (length (.layers cfg)))
;; 3 + 1 + 4 + 1 + 5. A length alone would pass on a Vec that was allocated
;; and never filled, so the sum is the claim.
(let [total (i64 0)]
(dotimes [i (len (.layers cfg))]
(dotimes [i (length (.layers cfg))]
(set total (+ total (at (.layers cfg) i))))
(println total))
;; The nested structs, by the names the paths give them: Config-window and

View File

@ -205,7 +205,7 @@
(match v
(Array items)
(let [n 0]
(dotimes [i (len items)]
(dotimes [i (length items)]
(set n (+ n (count-leaves (at items i)))))
n)
;; map-next fills an out-parameter with a copy of the value's bytes, which
@ -227,7 +227,7 @@
(Int n) n
(Array items)
(let [t (i64 0)]
(dotimes [i (len items)]
(dotimes [i (length items)]
(set t (+ t (sum-ints (at items i)))))
t)
(Object entries)
@ -419,11 +419,11 @@
;; of twelve; and the eight one-character escapes are eight bytes.
(println (text-at v "name"))
(println (text-at v "note"))
(println (len (text-at v "note")))
(println (len (text-at v "esc")))
(println (length (text-at v "note")))
(println (length (text-at v "esc")))
;; And now the source buffer is destroyed under the live document. An
;; implementation that aliased it prints question marks from here on.
(dotimes [i (len buf)]
(dotimes [i (length buf)]
(set (at buf i) \?))
(println (text-at v "name"))
(println (text-at v "nothing"))

View File

@ -0,0 +1,37 @@
;;;; `len` as an ordinary name, which is the whole reason the count is spelled
;;;; `length`.
;;;;
;;;; Shadowing already made a `(defn len ...)` legal, and `builtin/len` already
;;;; made the builtin reachable past one. What is left, and what this file is,
;;;; is that `len` is not a builtin at all: nothing warns, nothing needs the
;;;; qualifier, and the name is free in every position a name can be in. A
;;;; global called `len` is the one case that cannot also be here — this is a
;;;; Lisp-1 and a defn and a defonce may not share a name — so it is pinned in
;;;; test_flan instead. `len-gone.flan` is the other half: the call to the name
;;;; nothing defines.
;;;;
;;;; Four lines. In order: the local (3), the parameter doubled (6), the defn
;;;; reached by its bare name (2), and the local again over a Vec (3).
(defn twice-len [len i32] i32
(* len 2))
;;; A defn takes the name too, and a call to it is this program's. Nothing is
;;; shadowed here — if `len` were still a builtin this definition would carry
;;; a warning, and shadow-builtin.flan is where that sentence is pinned.
(defn len [s [i32]] i32
(length s))
(defn main [] ()
(let [xs [10 20 30]]
;; The shape the long word exists for: the count in a binding called len.
(let [len (length xs)]
(println len)
(println (twice-len len)))
;; And the bare name as a call, where no local has taken it.
(println (len (slice xs 0 2))))
(let [v (vec-new i32)]
(push v 1) (push v 2) (push v 3)
(let [len (length v)]
(println len))
(free v)))

View File

@ -0,0 +1,15 @@
;;;; The count is `length`, and `len` is left to programs — it is the obvious
;;;; name for a local holding one, and a builtin sitting on it would take that
;;;; away. So a call to a `len` this program has not defined is a call to a
;;;; name nothing defines, and the refusal has to say the word that does work
;;;; and write the call out with it. `len-bound.flan` is the other half: the
;;;; name binds like any other.
;;;;
;;;; The sentence is said rather than guessed at. `len` and `length` are three
;;;; edits apart and the did-you-mean's net is one, so nothing else in the
;;;; compiler would reach it.
(defonce a [4 i32])
(defn main [] i32
(len a))

View File

@ -48,7 +48,7 @@
;; [& args]. Every macro in the tree was migrated to this line, so it is the
;; one that has to keep working unchanged.
(defmacro all-of [& args]
(if (= (len args) 0)
(if (= (length args) 0)
`true
`(if ~(at args 0) (all-of ~@(form-rest args 1)) false)))

View File

@ -7,7 +7,7 @@
;;;;
;;;; Every macro below is written [& args] and picks its arguments apart by
;;;; hand, which is what a macro looked like before parameter lists: & binds
;;;; the whole call as a slice of forms, and (len args) is how many were
;;;; the whole call as a slice of forms, and (length args) is how many were
;;;; written. macro-params.flan is the other spelling of the same grammar —
;;;; named parameters, [ ] patterns, & only for the tail — and the two files
;;;; are kept apart on purpose, so that each is a whole program in one style.
@ -58,7 +58,7 @@
;; conditional macro in every Lisp is. It gets smaller each time and stops at
;; the empty case, so the expander's fuel never comes into it.
(defmacro all-of [& args]
(if (= (len args) 0)
(if (= (length args) 0)
`true
`(if ~(at args 0) (all-of ~@(form-rest args 1)) false)))

View File

@ -41,7 +41,7 @@
;; puts nothing, and the retry puts exactly once — so no entry is lost
;; and none is doubled.
(dotimes [i 300] (put m i (* (i64 i) 7)))
(println (len m)) ; 300
(println (length m)) ; 300
(let [bad 0]
(dotimes [i 300]
(match (get m i)
@ -66,11 +66,11 @@
(invoke-restart 'retry))]
(let [m (map-new string i32 tight)]
(reserve m 200)
(println (len m)) ; 0
(println (length m)) ; 0
(put m "a" 1)
(put m "b" 2)
(let [w (clone m)]
(println (len w)) ; 2
(println (length w)) ; 2
(match (get w "b")
(Some v) (println v) ; 2
None (println "missing"))

View File

@ -76,7 +76,7 @@
xs 0
ys 0]
(while (map-next m (addr cur) (addr k) (addr v))
(set chars (+ chars (len k)))
(set chars (+ chars (length k)))
(set xs (+ xs (.x v)))
(set ys (+ ys (.y v))))
(print chars) (print " ") (print xs) (print " ") (print ys) (println ""))
@ -97,7 +97,7 @@
(while (map-next m (addr cur) (addr k) (addr v))
(set n (+ n 1))
(when (= v (* k 2)) (set doubled (+ doubled 1))))
(print n) (print " ") (print doubled) (print " ") (print (len m)) (println ""))
(print n) (print " ") (print doubled) (print " ") (print (length m)) (println ""))
(free m)))
(defn main [] i32

View File

@ -22,10 +22,10 @@
(match (map-remove m 1)
(Some v) (do (print v) (println "")) ; 100
None (println "missing"))
(print (len m)) (println "") ; 1
(print (length m)) (println "") ; 1
(print (has-key? m 1)) (println "") ; false
(match (map-remove m 1) (Some v) (do (print v) (println "")) None (println "gone"))
(println (len m)) ; gone, then 1
(println (length m)) ; gone, then 1
(free m))
;; (2) Removing every entry empties the map, and it is usable afterwards:
@ -33,10 +33,10 @@
(let [m (map-new i32 i32)]
(dotimes [i 64] (put m i i))
(dotimes [i 64] (map-remove m i))
(print (len m)) (println "") ; 0
(print (length m)) (println "") ; 0
(put m 7 77)
(match (get m 7) (Some v) (do (print v) (println "")) None (println "?")) ; 77
(print (len m)) (println "") ; 1
(print (length m)) (println "") ; 1
(free m))
;; (3) The survivors, which is the row that matters. 2000 entries is eight
@ -53,7 +53,7 @@
(Some v) (if (= v (* (i64 i) 3)) (set taken (+ taken 1)))
None (set taken taken))))
(print taken) (println "")) ; 1000
(print (len m)) (println "") ; 1000
(print (length m)) (println "") ; 1000
(let [lost 0 ghosts 0]
(dotimes [i 2000]
(match (get m i)
@ -65,7 +65,7 @@
;; Re-inserting what was taken out puts the length back, through no grow:
;; the block still has the room the removals freed up.
(dotimes [i 2000] (if (= 0 (% i 2)) (put m i (* (i64 i) 3))))
(print (len m)) (println "") ; 2000
(print (length m)) (println "") ; 2000
(free m))
;; (4) A struct key and a string key, so the emitted hash/equality pair is
@ -77,7 +77,7 @@
None (println "?"))
(print (has-key? g (Cell {.x 7 .y 9}))) (println "") ; false
(print (has-key? g (Cell {.x 7 .y 10}))) (println "") ; true
(print (len g)) (println "") ; 399
(print (length g)) (println "") ; 399
(free g))
(let [s (map-new string i32)]
@ -87,7 +87,7 @@
(Some v) (do (print v) (println "")) ; 1
None (println "?"))
(print (has-key? s "beta")) (println "") ; true
(print (len s)) (println "") ; 1
(print (length s)) (println "") ; 1
(free s))
;; (5) Iteration after removals answers exactly the survivors. The cursor is
@ -102,7 +102,7 @@
(if (not (= k v)) (set sum (+ sum 1))))
(print seen) (println "") ; 66
(print sum) (println "")) ; 0
(print (len m)) (println "") ; 66
(print (length m)) (println "") ; 66
(free m))
;; (6) An arena, which refuses can-free. Removal asks the allocator for
@ -114,7 +114,7 @@
(let [t (map-new i32 i32)]
(dotimes [i 300] (put t i (* i 2)))
(dotimes [i 300] (if (= 0 (% i 2)) (map-remove t i)))
(print (len t)) (println "") ; 150
(print (length t)) (println "") ; 150
(match (get t 299) (Some v) (do (print v) (println "")) None (println "?")) ; 598
(print (has-key? t 298)) (println ""))) ; false
(free-all ar))

View File

@ -23,7 +23,7 @@
;; Takes the map, which is a move: this owns it now, and frees it.
(defn consume-grid [m (Map Cell2 i32)] i32
(let [n (len m)]
(let [n (length m)]
(free m)
n))
@ -36,7 +36,7 @@
(let [m (map-new i32 i64)]
(dotimes [i 2000]
(put m i (* (i64 i) 3)))
(print (len m)) (println "") ; 2000
(print (length m)) (println "") ; 2000
(let [bad 0]
(dotimes [i 2000]
(match (get m i)
@ -53,7 +53,7 @@
(dotimes [i 40]
(dotimes [j 40]
(put g (Cell {.x i .y j}) (+ (* i 100) j))))
(print (len g)) (println "") ; 1600
(print (length g)) (println "") ; 1600
(match (get g (Cell {.x 7 .y 9}))
(Some v) (do (print v) (println "")) ; 709
None (println "missing"))
@ -68,7 +68,7 @@
(put n (Named {.tag "alpha" .n 1}) 10)
(put n (Named {.tag "alpha" .n 2}) 20)
(put n (Named {.tag "beta" .n 1}) 30)
(print (len n)) (println "") ; 3
(print (length n)) (println "") ; 3
(match (get n (Named {.tag "alpha" .n 2}))
(Some v) (do (print v) (println "")) ; 20
None (println "missing"))
@ -79,7 +79,7 @@
(let [s (map-new Suit i32)]
(put s :hearts 1)
(put s :clubs 3)
(print (len s)) (println "") ; 2
(print (length s)) (println "") ; 2
(match (get s :clubs)
(Some v) (do (print v) (println "")) ; 3
None (println "missing"))
@ -97,7 +97,7 @@
(put b 1 999)
(match (get a 1) (Some v) (do (print v) (println "")) None (println "?")) ; 100
(match (get b 1) (Some v) (do (print v) (println "")) None (println "?")) ; 999
(print (len b)) (println "") ; 2
(print (length b)) (println "") ; 2
(free b))
(free a))
@ -108,7 +108,7 @@
(put u "k" 1)
(put u "k" 2)
(put u "k" 3)
(print (len u)) (println "") ; 1
(print (length u)) (println "") ; 1
(match (get u "k") (Some v) (do (print v) (println "")) None (println "?")) ; 3
(free u))
@ -119,11 +119,11 @@
(with-allocator ar
(let [t (map-new i32 i32)]
(dotimes [i 500] (put t i (* i 2)))
(print (len t)) (println "") ; 500
(print (length t)) (println "") ; 500
(match (get t 499) (Some v) (do (print v) (println "")) None (println "?")))) ; 998
(free-all ar))
(let [g (make-grid)]
(print (len g)) (println "") ; 2
(print (length g)) (println "") ; 2
(print (consume-grid g)) (println "")) ; 2
0)

View File

@ -15,7 +15,7 @@
;;;; call site two files away.
(defn last-of [s [$t]] $t
(at s (- (len s) 1)))
(at s (- (length s) 1)))
;;; Calls last-of at its own variable: the copy of last-of is generated when
;;; this is instantiated, and this is instantiated from the program.
@ -28,6 +28,6 @@
(defn largest [s [$t]] $t
{:where (ordered? $t)}
(let [m (at s 0)]
(dotimes [i (len s)]
(dotimes [i (length s)]
(set m (max m (at s i))))
m))

View File

@ -11,8 +11,8 @@
;;; The same question asked where there is no enclosing function to be
;;; qualified: a global's initialiser, which runs at startup and is checked
;;; with no owner at all. The importer below defines a len of its own; this
;;; with no owner at all. The importer below defines a length of its own; this
;;; one is the builtin's and this global is 4.
(defonce size i32 (len "abcd"))
(defonce size i32 (length "abcd"))
(defn stored-size [] i32 size)

View File

@ -26,7 +26,7 @@
(Leaf n) n
(Branch kids)
(let [s (i64 0)]
(dotimes [i (len kids)]
(dotimes [i (length kids)]
(set s (+ s (total (at kids i)))))
s)
Empty (i64 0)))

View File

@ -37,7 +37,7 @@
(defn nothing [] () )
(defn find-it [s [i32] k i32] (Option i32)
(dotimes [i (len s)]
(dotimes [i (length s)]
(when (= (at s i) k) (return (Some i))))
None)

View File

@ -89,7 +89,7 @@
(if (= i 3)
acc
(do (push acc i) (recur acc (+ i 1)))))]
(dotimes [i (len v)] (print (at v i)))
(dotimes [i (length v)] (print (at v i)))
(println "") ; 012
(free v))

View File

@ -54,7 +54,7 @@
;; else is live by here — sections 1 and 2 both released — so the live
;; count *is* the copy's block, and free-all takes it back to zero.
(let [b (bytes "copy" frame)]
(println (len b)) ; 4 — the string's length
(println (length b)) ; 4 — the string's length
(println (reg-count 1)) ; dev: 1 — the copy's block
(free-all frame)
(println (reg-count 1))) ; 0 either way

View File

@ -21,7 +21,7 @@
(defn pick [xs [$t]] $t
{:where (ordered? $t)}
(let [m (at xs 0)]
(dotimes [i (len xs)]
(dotimes [i (length xs)]
(set m (min m (at xs i))))
m))

View File

@ -103,7 +103,7 @@
(defn main [args [string]] i32
;; One argument selects a trap; none runs the table's case.
(if (> (len args) 1)
(if (> (length args) 1)
(let [k (i32 (bytes->i64 (bytes-view (at args 1))))]
(cond
(= k 1) (print (mismatched 90))

View File

@ -13,9 +13,9 @@
;;;; calls the builtin get on a dyn map. The shadow does not follow it
;;;; there: a package's calls mean what they meant when it was written.
;;;; - 99: an operator is a builtin like any other and shadows like one.
;;;; - 999: this file's own len, which is what len means in this file.
;;;; - 999: this file's own length, which is what length means in this file.
;;;; - 4 again, and it is the one that needed the work: the package's global
;;;; initialiser (defonce size i32 (len "abcd")) is checked with no
;;;; initialiser (defonce size i32 (length "abcd")) is checked with no
;;;; enclosing function at all, so there is no qualified name on it to say
;;;; it belongs to a package. The file it was written in says so instead.
@ -33,13 +33,13 @@
;;; And a builtin the imported package uses in a *global initialiser*, which
;;; is the one place there is no enclosing function to carry a package's
;;; qualified name. The package's (defonce size i32 (len "abcd")) is 4; this
;;; qualified name. The package's (defonce size i32 (length "abcd")) is 4; this
;;; definition answers 999 and is reached only here.
(defn len [s string] i32 999)
(defn length [s string] i32 999)
(defn main [] ()
(println (get (P {.x 7})))
(println (shadowed/field {:a 1 :b 4}))
(println (+ 1 2))
(println (len "abcd"))
(println (length "abcd"))
(println (shadowed/stored-size)))

View File

@ -5,7 +5,7 @@
;;;;
;;;; What is asserted, line by line:
;;;;
;;;; - the length is the one the caller stated, and (len s) answers it;
;;;; - the length is the one the caller stated, and (length s) answers it;
;;;; - the elements read through are the same storage, not a copy — the last
;;;; two lines write through the slice and read the array back, which is
;;;; the whole ptr+len claim;
@ -23,7 +23,7 @@
(defn total [s [i32]] i32
(let [acc 0]
(dotimes [i (len s)]
(dotimes [i (length s)]
(set acc (+ acc (at s i))))
acc))
@ -34,7 +34,7 @@
;; The whole array, as the caller promises it: five elements behind the
;; address of the first.
(let [s (slice-from-ptr (addr (at a 0)) 5)]
(println (len s)) ; 5
(println (length s)) ; 5
(println (at s 0)) ; 10
(println (at s 4)) ; 50
(println (total s)) ; 150
@ -46,12 +46,12 @@
;; complain with — and the length the caller gave is the length indexing and
;; the bounds check both use.
(let [s (slice-from-ptr (addr (at a 1)) 2)]
(println (len s)) ; 2
(println (length s)) ; 2
(println (total s))) ; 50
;; Zero is a length like any other. An empty slice is not a null pointer and
;; is not an error.
(println (len (slice-from-ptr (addr (at a 0)) 0))) ; 0
(println (length (slice-from-ptr (addr (at a 0)) 0))) ; 0
;; It is a view, not a copy: a write through the slice is a write to the
;; array, and this is what would fail if the form ever grew a memcpy.

View File

@ -16,7 +16,7 @@
(defonce zs [8 i32])
(defn show [s [i32]] ()
(dotimes [i (len s)]
(dotimes [i (length s)]
(when (> i 0) (print " "))
(print (at s i)))
(println ""))
@ -32,56 +32,57 @@
(defn main [] i32
(load-xs)
(show (slice xs 0 (len xs))) ; 5 -3 5 0 12 -3 7
(show (slice xs 0 (length xs))) ; 5 -3 5 0 12 -3 7
;; Reading the whole slice, before anything reorders it.
(print (sum-i32 (slice xs 0 (len xs)))) (println "") ; 23
(print (match (min-of (slice xs 0 (len xs))) (Some v) v None 99))
(print (sum-i32 (slice xs 0 (length xs)))) (println "") ; 23
(print (match (min-of (slice xs 0 (length xs))) (Some v) v None 99))
(println "") ; -3
(print (match (max-of (slice xs 0 (len xs))) (Some v) v None 99))
(print (match (max-of (slice xs 0 (length xs))) (Some v) v None 99))
(println "") ; 12
;; First index, not the last: 5 appears at 0 and at 2.
(print (match (index-of (slice xs 0 (len xs)) 5) (Some v) v None -1))
(print (match (index-of (slice xs 0 (length xs)) 5) (Some v) v None -1))
(println "") ; 0
(print (match (index-of (slice xs 0 (len xs)) 4) (Some v) v None -1))
(print (match (index-of (slice xs 0 (length xs)) 4) (Some v) v None -1))
(println "") ; -1
;; An empty slice has no least element, and None is the answer.
(print (match (min-of (slice xs 3 3)) (Some v) v None 99))
(println "") ; 99
;; Reverse of an odd-length slice: the middle element stays put.
(reverse (slice xs 0 (len xs)))
(show (slice xs 0 (len xs))) ; 7 -3 12 0 5 -3 5
(reverse (slice xs 0 (length xs)))
(show (slice xs 0 (length xs))) ; 7 -3 12 0 5 -3 5
;; And of a two-element one, the smallest case that can actually move.
(reverse (slice xs 0 2))
(show (slice xs 0 (len xs))) ; -3 7 12 0 5 -3 5
(show (slice xs 0 (length xs))) ; -3 7 12 0 5 -3 5
(load-xs)
(sort (slice xs 0 (len xs)))
(show (slice xs 0 (len xs))) ; -3 -3 0 5 5 7 12
(sort (slice xs 0 (length xs)))
(show (slice xs 0 (length xs))) ; -3 -3 0 5 5 7 12
;; Reverse-sorted: the case a comparison that never fires would pass.
(set (at ys 0) 5) (set (at ys 1) 4) (set (at ys 2) 3)
(set (at ys 3) 2) (set (at ys 4) 1)
(sort (slice ys 0 (len ys)))
(show (slice ys 0 (len ys))) ; 1 2 3 4 5
(sort (slice ys 0 (length ys)))
(show (slice ys 0 (length ys))) ; 1 2 3 4 5
;; A subslice, with the elements on both sides left alone.
(set (at zs 0) 100) (set (at zs 1) 9) (set (at zs 2) -1)
(set (at zs 3) 9) (set (at zs 4) 4) (set (at zs 5) 0)
(set (at zs 6) 200) (set (at zs 7) 300)
(sort (slice zs 1 6))
(show (slice zs 0 (len zs))) ; 100 -1 0 4 9 9 200 300
(show (slice zs 0 (length zs))) ; 100 -1 0 4 9 9 200 300
;; Degenerate lengths must do nothing rather than run off an end.
(sort (slice zs 0 0))
(reverse (slice zs 0 0))
(sort (slice zs 2 3))
(reverse (slice zs 2 3))
(show (slice zs 0 (len zs))) ; 100 -1 0 4 9 9 200 300
(show (slice zs 0 (length zs))) ; 100 -1 0 4 9 9 200 300
;; ── The short arities ──────────────────────────────────────────────
;; (slice a) and (slice a n) are (slice a 0 (len a)) and (slice a n (len a))
;; (slice a) and (slice a n) are (slice a 0 (length a)) and
;; (slice a n (length a))
;; written out in the checker, so each line here is read against the
;; spelling above it that it stands for. A fixed array does not decay to a
;; slice at a call, so passing one to a function over [$t] is what these

View File

@ -28,13 +28,13 @@
(defn main [] i32
;; ── The happy path ────────────────────────────────────────────────
(let [v (slurp "programs/assets/a.txt")]
(println (len v)) ; 13
(println (length v)) ; 13
(print (string (slice v))) ; hello from a
(free v))
;; Byte-exact, the same as an embed: nothing here decodes anything.
(let [v (slurp "programs/assets/raw.bin")]
(println (len v)) ; 4
(println (length v)) ; 4
(println (at v 0)) ; 0
(println (at v 2)) ; 255
(free v))
@ -51,7 +51,7 @@
(set last-path (.path c))
(invoke-restart 'use-value "programs/assets/b.bin"))]
(let [v (slurp "programs/assets/does-not-exist")]
(println (len v)) ; 3
(println (length v)) ; 3
(println (string (slice v))) ; BBB
(free v)))
(println seen) ; 1
@ -64,7 +64,7 @@
;; ── barf, and reading back what it wrote ──────────────────────────
(barf "slurp-out.txt" (bytes-view "round trip\n"))
(let [v (slurp "slurp-out.txt")]
(println (len v)) ; 11
(println (length v)) ; 11
(print (string (slice v))) ; round trip
(free v))

View File

@ -24,7 +24,7 @@
(print "[")
(print s)
(print "] ")
(print (len (bytes-view s)))
(print (length (bytes-view s)))
(println ""))
(defn main [] i32
@ -46,7 +46,7 @@
;; Round trip: (bytes-view (string b)) is b, and both directions are the identity.
(let [b (i64->bytes 1234567)]
(print (len (bytes-view (string b))))
(print (length (bytes-view (string b))))
(println ""))
;; Across the declare-c boundary. The first is a sub-view — five bytes out of

View File

@ -37,7 +37,7 @@
(free c)))
(let [parts [(bytes-view "unused")]]
(let [c (concat (slice parts 0 0))]
(println (len c)) ; 0
(println (length c)) ; 0
(free c)))
;; join: n parts, n-1 separators. The one-part case is the one that must not
@ -51,7 +51,7 @@
(show (addr j)) ; a
(free j))
(let [j (join (slice parts 0 0) (bytes-view ", "))]
(println (len j)) ; 0
(println (length j)) ; 0
(free j))
;; An empty separator is concat.
(let [j (join (slice parts 0 3) (bytes-view ""))]
@ -63,7 +63,7 @@
(show (addr r)) ; ababab
(free r))
(let [r (repeat-bytes (bytes-view "ab") 0)]
(println (len r)) ; 0
(println (length r)) ; 0
(free r))
;; The allocating case pair. The input is a string literal, which lives in
@ -103,26 +103,26 @@
;; present, which is where Odin's own iterator and its allocating split
;; disagree with each other.
(let [f (split (bytes-view "a,b,c") \,)]
(println (len f)) ; 3
(println (length f)) ; 3
(println (string (at f 0))) ; a
(println (string (at f 2))) ; c
(free f))
(let [f (split (bytes-view "a,b,") \,)]
(println (len f)) ; 3
(println (len (at f 2))) ; 0
(println (length f)) ; 3
(println (length (at f 2))) ; 0
(free f))
(let [f (split (bytes-view ",a") \,)]
(println (len f)) ; 2
(println (len (at f 0))) ; 0
(println (length f)) ; 2
(println (length (at f 0))) ; 0
(free f))
;; No separator at all is one field, and the empty input is one empty field.
(let [f (split (bytes-view "abc") \,)]
(println (len f)) ; 1
(println (length f)) ; 1
(println (string (at f 0))) ; abc
(free f))
(let [f (split (bytes-view "") \,)]
(println (len f)) ; 1
(println (len (at f 0))) ; 0
(println (length f)) ; 1
(println (length (at f 0))) ; 0
(free f))
;; The fields are slices of the input and nothing was copied: this one

View File

@ -66,5 +66,5 @@
;; The dyn map literal is untouched by any of this: keyword keys, and a
;; .field-keyed brace was never part of that spelling.
(let [m {:a 1 :b 2}]
(println (len m))
(println (length m))
(println (get m :b))))

View File

@ -71,6 +71,6 @@
;; PATH is set for every process that gets as far as running this, and the
;; only portable thing about its contents is that there are some.
(match (getenv "PATH")
(Some v) (println (> (len v) 0))
(Some v) (println (> (length v) 0))
None (println "no PATH"))
0)

View File

@ -130,7 +130,7 @@
(print (rune-count (bytes-view ""))) (print " ")
(print (rune-count (bytes-view "abc"))) (print " ")
(print (rune-count (bytes-view "héllo 日本"))) (print " ")
(print (len (bytes-view "héllo 日本"))) (print " ")
(print (length (bytes-view "héllo 日本"))) (print " ")
(print (rune-count (slice bad-tail 0 3)))
(println "")

View File

@ -15,7 +15,7 @@
;; Reading it here is a borrow. So is reading it in [total] below, which is the
;; case the old rule could not express: two functions holding the same global
;; at once is fine exactly because neither of them can free it.
(defn loaded? [] bool (> (len the-data) 0))
(defn loaded? [] bool (> (length the-data) 0))
(defn load [] ()
(when (not (loaded?))
@ -24,7 +24,7 @@
(defn total [] i64
(let [s (i64 0)]
(dotimes [i (len the-data)] (set s (+ s (i64 (at the-data i)))))
(dotimes [i (length the-data)] (set s (+ s (i64 (at the-data i)))))
s))
;; Mutating in place, through the global rather than through a copy of it. The
@ -39,7 +39,7 @@
(load)
(bump)
(put counts 1 (total))
(println (len the-data))
(println (length the-data))
(println (total))
(match (get counts 1) (Some v) (println v) None (println "?")))
@ -48,11 +48,11 @@
;; The second run. Nothing re-initialises the global between them, so the
;; length keeps climbing and the loaded data is the same block it was.
(entry)
(println (len (slice the-data)))
(println (length (slice the-data)))
;; A copy is the one thing something else may own, and freeing that copy
;; leaves the global untouched.
(let [c (clone the-data)]
(println (len c))
(println (length c))
(free c))
(println (len the-data))
(println (length the-data))
0)

View File

@ -2,7 +2,7 @@
;;;; source binding is dead afterwards. That rule is what makes a double free
;;;; unrepresentable, which is why `free` needs no analysis of its own.
(defn take [v (Vec i32)] i32
(let [n (len v)]
(let [n (length v)]
(free v)
n))
@ -11,5 +11,5 @@
(push v 1)
(println (take v))
;; v went with the call. Being refused here is the whole test.
(println (len v))
(println (length v))
0))

View File

@ -11,7 +11,7 @@
;;; Ownership transfers on the call. The caller's binding is dead after this,
;;; which is what the refusal cases in test_acceptance assert.
(defn consume [v (Vec i32)] i32
(let [n (len v)]
(let [n (length v)]
(free v)
n))
@ -25,16 +25,16 @@
(defn sum [xs [i32]] i32
(let [total 0]
(dotimes [i (len xs)] (set total (+ total (at xs i))))
(dotimes [i (length xs)] (set total (+ total (at xs i))))
total))
(defn main [] i32
(let [v (vec-new i32)]
(println (len v)) ; 0
(println (length v)) ; 0
(push v 10)
(push v 20)
(push v 30)
(println (len v)) ; 3
(println (length v)) ; 3
(println (at v 0)) ; 10
(println (at v 2)) ; 30
;; A Vec element is a place, and the same bounds and epoch check stands
@ -44,15 +44,15 @@
;; slice over a Vec is a non-owning view: it copies ptr+len and never the
;; elements, and it carries no allocator, so nothing can be freed through
;; one. [at] and [len] over it are the array operations, unchanged, and
;; one. [at] and [length] over it are the array operations, unchanged, and
;; the three arities are the ones every other target has -- the tail form
;; included, which the Vec had no spelling for while it had a name of its
;; own. The view is of storage v owns: a push here would move it, and
;; nothing would say so.
(println (sum (slice v))) ; 139
(println (len (slice v 1 3))) ; 2
(println (length (slice v 1 3))) ; 2
(println (at (slice v 1 3) 0)) ; 99
(println (len (slice v 1))) ; 2
(println (length (slice v 1))) ; 2
(println (at (slice v 1) 1)) ; 30
;; clone is the only copy: assignment moves. The copy is independent, and
@ -66,9 +66,9 @@
;; reserve does not change the length, only the capacity, so a reserve
;; that succeeds is invisible except that the pushes after it do not grow.
(reserve v 64)
(println (len v)) ; 3
(println (length v)) ; 3
(push v 40)
(println (len v)) ; 4
(println (length v)) ; 4
;; The structural printer reaches both new types. Neither is followed: a
;; Vec's elements are printed through (slice v), which says at the call
@ -84,7 +84,7 @@
(let [ps (vec-new Point)]
(push ps (Point {.x 1 .y 2}))
(push ps (Point {.x 3 .y 4}))
(println (len ps)) ; 2
(println (length ps)) ; 2
(println (.y (at ps 1))) ; 4
(free ps))

View File

@ -411,6 +411,15 @@ let () =
chain_out;
outputs ~x86:true "chained comparisons, --x86" "programs/chain.flan"
chain_out;
(* The count is [length] so that [len] is left to programs, and this is
the claim that it really is one: a local holding a count, a
parameter, and a defn the program calls by its bare name, all of
them called len, all of them running. Shadowing and [builtin/] had
already made a defn named after a builtin legal; what this adds is
that there is no builtin here to be shadowed, so nothing warns and
the qualifier is not needed to reach past anything. *)
outputs "len is an ordinary name" "programs/len-bound.flan"
"3\n6\n2\n3\n";
(* A global of move-only type, which this compiler used to refuse outright.
What the numbers assert is the half of the rule no checker test can: the
global is loaded once and *stays* loaded across a second entry, which is
@ -1032,26 +1041,26 @@ let () =
refuses anything that is not Addressing_Constant). This is the refusal
that keeps the result genuinely free at run time. *)
refuses_src "an embedded path that is computed"
"(defn main [] i32 (let [p \"x\"] (len (embed p))))"
"(defn main [] i32 (let [p \"x\"] (length (embed p))))"
"must be a literal string";
(* A file that is not there is a compile error naming it, not an empty
embed: an asset silently missing is the class of quiet wrongness this
whole feature exists to remove. *)
refuses_src "an embedded file that does not exist"
"(defn main [] i32 (len (embed \"no-such-asset.bin\")))"
"(defn main [] i32 (length (embed \"no-such-asset.bin\")))"
"cannot embed";
(* A directory where a file was meant. On Linux open_in_bin on a directory
succeeds and the *read* is where EISDIR arrives, so this was an uncaught
exception out of the checker until the whole read was guarded - the one
way a user could make the compiler crash rather than refuse. *)
refuses_src "embed given a directory"
"(defn main [] i32 (len (embed \"programs/assets\")))"
"(defn main [] i32 (length (embed \"programs/assets\")))"
"it is a directory";
(* One extra argument, and `string` is the only thing it can be. Two
spellings, not one form that changes type with its context. *)
refuses_src "embed asked for a type it cannot read a file as"
"(defn main [] i32 (len (embed \"no-such-asset.bin\" i32)))"
"(defn main [] i32 (length (embed \"no-such-asset.bin\" i32)))"
"embed's second argument is string";
(* Allocators, spec-memory.md. The tier on its own, with no container
@ -2741,12 +2750,12 @@ let () =
the other reading: 7 is the program's own one-argument (get p), which
the builtin get has no arity for at all; 4 is the builtin get called
inside the imported package on a dyn map; 99 is an operator shadowed
like any other name; 999 is this program's len.
like any other name; 999 is this program's length.
The last 4 is the one that was a bug. It is the package's global
initialiser, (defonce size i32 (len "abcd")), which is the one place a
initialiser, (defonce size i32 (length "abcd")), which is the one place a
call sits inside no function and so carries no package-qualified name
the importer's len reached into it and made it 999. The shadow is
the importer's length reached into it and made it 999. The shadow is
decided by the file the definition was written in, and a file is
something a global initialiser has.
@ -2759,8 +2768,9 @@ let () =
(* And the way back out of it. builtin/name is the builtin whatever the
file has decided the bare name means, so the two spellings sit side by
side in one program and answer differently: 9 is builtin/max with
nothing named max in the program at all, 5 is a len that is a real
wrapper 1 + the builtin length 4 is builtin/len beside it, 99 is
nothing named max in the program at all, 5 is a length that is a
real wrapper 1 + the builtin length 4 is builtin/length beside
it, 99 is
the shadowed operator and 3 is builtin/+.
The 5 is the line that could not be written before this. The same defn
@ -3055,6 +3065,15 @@ let () =
and this row is what says so. *)
refuses "nth is not a name" "programs/nth-gone.flan"
"unknown function nth";
(* [len] is not a removal the way [nth] is — it is a name a program may
have, and the count is [length]. A program that reached for the
short word and defined nothing under it gets the word that works,
with its own call written back out: the arguments are spelled from
the forms the reader wrote, so what is printed compiles. *)
refuses "len is not a builtin" "programs/len-gone.flan"
"there is no len";
refuses "and the refusal writes the call out" "programs/len-gone.flan"
"Write (length a)";
(* Still the one thing in the allocator tier that does not work, and the
reason changed when function values landed: it *has* a defn's name in
value position now. What it does not have is a way to be called the
@ -3750,7 +3769,7 @@ level "1"
Unit as a value is refused rather than dividing a cache line by zero,
and it is named because it is the natural spelling of a set. *)
(* Removal is a map's operation and says so, rather than reaching for a
[len] that a Vec would also answer. *)
[length] that a Vec would also answer. *)
refuses_src "map-remove wants a map"
"(defn main [] i32 (let [v (vec-new i32)] (map-remove v 1) (free v)) 0)"
"map-remove takes a (Map K V)";
@ -4698,7 +4717,7 @@ level "1"
end)
ds
in
(* The vec file reports nine: a vec-new, four boxed pushes, a len, an at
(* The vec file reports nine: a vec-new, four boxed pushes, a length, an at
and the dotimes bound. The floor is under that rather than equal to it
so an added line does not fail the test, and well over one so that a
pass which named the first site and stopped would. *)
@ -5350,7 +5369,7 @@ level "1"
first macro hands back a Form the compiler misreads. *)
let form_src =
"(defn shape [f Form] i32\n\
\ (match f (Int _n) 1 (Str _s) 2 (List xs) (i32 (len xs)) _ 0))\n\
\ (match f (Int _n) 1 (Str _s) 2 (List xs) (i32 (length xs)) _ 0))\n\
(defn main [] i32 (shape (Form.Int {.i 1})))\n"
in
layout_case "DWARF offsets agree with LLVM: Form" form_src

View File

@ -6626,7 +6626,7 @@ let () =
holds "a kept slot keeps its value"
"(if (= (get (at instances 0) :x) 3) 1 0)";
holds "the migrated instance has the new slot count"
"(if (= (len (at instances 0)) 3) 1 0)";
"(if (= (length (at instances 0)) 3) 1 0)";
(* Dispatch after migration. The generic reaches its method by the
instance's shape tag, and a migration rebuilds the instance's
entries so this is the line that says the tag came through it.
@ -6638,7 +6638,7 @@ let () =
special: it migrates when it is asked, not when the class
changed. *)
holds "an untouched instance migrates on its own first touch"
"(if (= (len (at instances 1)) 3) 1 0)"
"(if (= (length (at instances 1)) 3) 1 0)"
end;
(* ── A slot lost, and a second generation ──
@ -6651,7 +6651,7 @@ let () =
holds "a lost slot reads as absent"
"(if (= (get (at instances 0) :y) nil) 1 0)";
holds "a lost slot is gone from the count"
"(if (= (len (at instances 0)) 2) 1 0)";
"(if (= (length (at instances 0)) 2) 1 0)";
holds "the slots either side of it are untouched"
"(if (= (get (at instances 0) :x) 3) 1 0)"
end;
@ -6665,7 +6665,7 @@ let () =
if status r <> "ok" then fail "a third redefinition: %s" (said r)
else begin
holds "the third definition's slot count"
"(if (= (len (at instances 1)) 3) 1 0)";
"(if (= (length (at instances 1)) 3) 1 0)";
holds "the third definition's new slot is nil"
"(if (= (get (at instances 1) :w) nil) 1 0)";
holds "and the value from before the first edit is still there"
@ -6771,7 +6771,7 @@ let () =
holds "a kept slot keeps its value through the LLVM backend"
"(if (= (get (at instances 0) :x) 3) 1 0)";
holds "the slot count through the LLVM backend"
"(if (= (len (at instances 0)) 3) 1 0)";
"(if (= (length (at instances 0)) 3) 1 0)";
holds "a generic still dispatches through the LLVM backend"
"(if (= (area (at instances 0)) 12) 1 0)"
end

View File

@ -215,7 +215,7 @@ let () =
("le", "dyn <=: text and nil");
("gt", "dyn >: vec and vec");
("ge", "dyn >=: bool and bool");
("len", "dyn len: int");
("len", "dyn length: int");
("at", "dyn at: int and int");
("atindex", "an index must be an int");
("atrange", "index 9 is out of bounds for text of length 2");
@ -249,7 +249,14 @@ let () =
("wrongwrite", "this view's elements are int");
("wrongbool", "this view's elements are bool");
("wrongfloat", "this view's elements are float");
("flatpush", "this view is a slice or an array and cannot grow") ]
("flatpush", "this view is a slice or an array and cannot grow");
(* And the operator's own name in that sentence. [flan_dyn_len] hands
a string down twice once to its type trap and once to the view
check and the two are easy to move apart, which is exactly what
happened when the builtin was renamed. Both say [length] because
both are naming the same Flan word. *)
("stalelen", "dyn length: this view's container's allocator was \
released") ]
in
List.iter
(fun (mode, phrase) ->

View File

@ -879,7 +879,7 @@ let probe_env = lazy (snd (Check.program_with_env []))
Checked as an expression, the way a session checks one sent from the editor.
It used to be the type of [(defconst probe <src>)], which stopped working on
2026-09-20: a defconst's initialiser has to be a compile-time constant now
and most of the probes below are calls (cast ...), (len ...), a
and most of the probes below are calls (cast ...), (length ...), a
comparison. A defonce would not do either, since only the defconst form
takes no type. This asks [check] the question the wrapper was only ever a
way of asking. *)
@ -1044,7 +1044,7 @@ let () =
infers "array-fill of nothing" "(array-fill [0] 1)" "[0 i32]";
infers "bytes of a string" "(bytes \"hi\")" "[u8]";
infers "bytes-view of a string" "(bytes-view \"hi\")" "[u8]";
infers "len is i32" "(len (bytes \"hi\"))" "i32";
infers "length is i32" "(length (bytes \"hi\"))" "i32";
infers "slice of a slice" "(slice (bytes \"hi\") 0 1)" "[u8]";
infers "slice of the whole" "(slice (bytes \"hi\"))" "[u8]";
infers "slice from n" "(slice (bytes \"hi\") 1)" "[u8]";
@ -1738,15 +1738,15 @@ let () =
symbol are a dyn map, in binding position and as a call argument alike
the argument spelling used to be swallowed by the struct-literal rule. *)
accepts "a map literal in a binding"
"(defn main [] i32 (let [m {:a 1 :b \"two\"}] (len m)))";
"(defn main [] i32 (let [m {:a 1 :b \"two\"}] (length m)))";
accepts "a map literal as an argument"
"(defn take [d dyn] i32 1)\n(defn main [] i32 (take {:a 1}))";
accepts "the empty braces are an empty map"
"(defn main [] i32 (let [m {}] (len m)))";
"(defn main [] i32 (let [m {}] (length m)))";
accepts "map literals nest, and brackets inside are dyn vecs"
"(defn main [] i32 (let [m {:xs [1 2] :inner {:c 2.5}}] (len m)))";
"(defn main [] i32 (let [m {:xs [1 2] :inner {:c 2.5}}] (length m)))";
rejects_check "a map literal with an odd number of forms"
"(defn main [] i32 (let [m {:a 1 :b}] (len m)))"
"(defn main [] i32 (let [m {:a 1 :b}] (length m)))"
~needle:"odd number of forms";
(* The struct spelling is untouched on both of its sides: bare braces
opening on a .field are still a struct field list and not a map, and
@ -1954,13 +1954,13 @@ let () =
~needle:"numeric?";
(* The map operations ride the words the typed map already owns: get, put,
len, has-key? one question, one word, on both sides. has-key? on a
length, has-key? one question, one word, on both sides. has-key? on a
typed map still checks against its K. *)
accepts "get, put, len and has-key? over a dyn map"
accepts "get, put, length and has-key? over a dyn map"
"(defn main [] i32\n\
\ (let [m {:a 1}]\n\
\ (put m :b 2)\n\
\ (if (has-key? m :b) (len m) 0)))";
\ (if (has-key? m :b) (length m) 0)))";
accepts "has-key? still serves the typed map"
"(defn main [] i32\n\
\ (let [m (map-new string i32 (heap-allocator))]\n\
@ -2048,7 +2048,8 @@ let () =
(* The short arities are the three-argument form written out, so they are
held to the same standard: the implicit hi on a fixed array is the same
literal (len a) folds to, and a lo past it is refused here and not later.
literal (length a) folds to, and a lo past it is refused here and not
later.
A 1-argument slice cannot fail either check 0 and the length are both
in range by construction so what is pinned about it is that it is
accepted on each of the three things slice takes. *)
@ -2096,7 +2097,7 @@ let () =
(* A Vec a call returned is *accepted*, where an array a call returned is
not. The array dangles; this does not the storage outlives the
expression and what it loses is the owner, which is a leak, which is
defined behaviour here. (len (mk)) and (at (mk) 0) lose the same owner
defined behaviour here. (length (mk)) and (at (mk) 0) lose the same owner
and compile, so refusing only the third would be a rule about a spelling
rather than about a hazard. *)
accepts "slice of a returned Vec"
@ -2179,7 +2180,7 @@ let () =
(* And a string is still not a [u8]: slicing one does not smuggle a byte
slice out of it. *)
rejects_check "a string slice is not a byte slice"
"(defn g [b [u8]] i32 (len b)) (defn f [s string] i32 (g (slice s)))"
"(defn g [b [u8]] i32 (length b)) (defn f [s string] i32 (g (slice s)))"
~needle:"expected [u8], found string";
(* (slice-from-ptr p n). The one form in the language whose central claim the
@ -2189,12 +2190,12 @@ let () =
accepts "a pointer plus a length is a slice"
"(defn f [p (Ptr i32) n i32] i32 (at (slice-from-ptr p n) 0))";
accepts "zero is a length"
"(defn f [p (Ptr i32)] i32 (len (slice-from-ptr p 0)))";
"(defn f [p (Ptr i32)] i32 (length (slice-from-ptr p 0)))";
rejects_check "slice-from-ptr of something that is not a pointer"
"(defn f [s [i32]] i32 (len (slice-from-ptr s 3)))"
"(defn f [s [i32]] i32 (length (slice-from-ptr s 3)))"
~needle:"takes a (Ptr T)";
rejects_check "slice-from-ptr with a negative literal length"
"(defn f [p (Ptr i32)] i32 (len (slice-from-ptr p -1)))"
"(defn f [p (Ptr i32)] i32 (length (slice-from-ptr p -1)))"
~needle:"is negative";
(* The storage stays C's. A slice carries no allocator, so free refuses one
by the rule it already had this pins that the new form did not become
@ -2285,7 +2286,7 @@ let () =
accepts "a keyword-keyed literal is still a dyn map at a dyn want"
"(defn f [] dyn {:a 1 :b [2 3]})";
accepts "the empty braces are still an empty dyn map"
"(defn main [] i32 (let [m {}] (len m)))";
"(defn main [] i32 (let [m {}] (length m)))";
(* ── (Cell 1 2), positional ────────────────────────────────────── *)
accepts "positional struct construction"
@ -2386,6 +2387,48 @@ let () =
rejects_check "unknown function" "(defn f [] i32 (nope 1))"
~needle:"unknown function";
(* ── len is a name, and length is the builtin ───────────────────────
[len] is short enough to want as a variable, so the builtin takes the
long word and the short one is left to programs. Shadowing had already
made a defn named after a builtin legal; what is new is that there is no
builtin here to shadow, so nothing warns and [builtin/] is not needed to
reach past anything. Four claims: the call is refused, the refusal says
what to write instead, the name binds in every position, and a defn under
it earns no shadowing warning. *)
rejects_check "len is not a builtin"
"(defn f [s [i32]] i32 (len s))" ~needle:"there is no len";
rejects_check "and the refusal writes the call out"
"(defn f [s [i32]] i32 (len s))" ~needle:"Write (length s)";
(* What it suggests has to compile, and [length] takes exactly one argument.
So the reader's own argument is written back only when there is one of
it: at any other arity the shape is suggested instead, because a call
with three arguments in it would be refused a second time the moment it
was pasted. Three arities, because the failure was silent at all of
them. *)
rejects_check "a len at the wrong arity is not written back out"
"(defn f [s [i32]] i32 (len s 1))" ~needle:"Write (length v)";
rejects_check "and neither is a len with no arguments"
"(defn f [] i32 (len))" ~needle:"Write (length v)";
rejects_check "and neither is one with a great many"
"(defn f [s [i32]] i32 (len s s s s s))" ~needle:"Write (length v)";
(* An argument with structure inside it is the stand-in too, at the one
arity that does spell: a form written back half-quoted would not
compile. *)
rejects_check "an argument that is a call is stood in for"
"(defn f [s [i32]] i32 (len (slice s 0 1)))" ~needle:"Write (length v)";
accepts "len is an ordinary binding"
"(defn f [s [i32]] i32 (let [len (length s)] (+ len 1)))";
accepts "and an ordinary parameter"
"(defn f [len i32] i32 (+ len 1))";
accepts "and an ordinary global"
"(defonce len i32 0)\n(defn f [] i32 (do (set len 3) len))";
accepts "and a function a program defines and calls"
"(defn len [s [i32]] i32 (length s))\n\
(defn f [s [i32]] i32 (len s))";
check "and a defn called len shadows nothing, so it is not warned about"
(Check.shadowed_builtins
(program "(defn len [s [i32]] i32 (length s))") = []);
(* ── Did-you-mean, and the dot habit ───────────────────────────────
[near_miss] was written, tested and wired to the type tables alone, so a
mistyped *value* got the bare refusal. The candidate list at a value
@ -2895,7 +2938,7 @@ let () =
which is also why a malformed one stays a type error rather than turning
into a call to something named Vec. *)
defvar_reading "a parenthesised type stays a zeroed static"
"(defonce v (Vec i32)) (defn f [] i32 (len v))"
"(defonce v (Vec i32)) (defn f [] i32 (length v))"
"v" ~ty:"(Vec i32)" ~zeroed:true;
rejects_check "a malformed parenthesised type stays a type error"
"(defonce v (Vec i32 i32)) (defn f [] ())"
@ -3183,7 +3226,7 @@ let () =
accepts "a global Vec is borrowed, mutated and cloned"
"(defonce g (Vec u8)) \
(defn f [] () (set g (vec-new u8)) (push g 1) (set (at g 0) 2) \
(println (len (slice g))) (let [c (clone g)] (free c)))";
(println (length (slice g))) (let [c (clone g)] (free c)))";
rejects_check "try is milestone 6" "(defn f [] i32 (try 1))"
~needle:"try (Result) is not implemented";
(* dotimes and defer are implemented, and a defer in a [let] is now one of
@ -3307,7 +3350,7 @@ let () =
~needle:"would leave a (loop ...)";
accepts "a while condition is an ordinary expression"
"(defn f [] () (let [v (vec-new i32) n 0] \
(while (and (< n 10) (> (len v) 0)) (set n (+ n 1))) (free v)))";
(while (and (< n 10) (> (length v) 0)) (set n (+ n 1))) (free v)))";
rejects_check "loop takes no label"
"(defn f [] () (loop :o [i 0] (recur i)))" ~needle:"loop takes no label";
rejects_check "a loop binding is a plain name"
@ -3918,9 +3961,9 @@ let () =
accepts "an array pattern naming every element"
"(defn f [] i32 (let [xs [1 2 3] [a b c] xs] (+ a (+ b c))))";
accepts "an array pattern with & rest"
"(defn f [] i32 (let [xs [1 2 3] [a & r] xs] (+ a (len r))))";
"(defn f [] i32 (let [xs [1 2 3] [a & r] xs] (+ a (length r))))";
accepts "& rest taking an empty tail"
"(defn f [] i32 (let [xs [1 2] [a b & r] xs] (+ a (+ b (len r)))))";
"(defn f [] i32 (let [xs [1 2] [a b & r] xs] (+ a (+ b (length r)))))";
accepts "a struct pattern nested in an array pattern"
(pt ^ "(defn f [ps [2 Point]] i32 \
(let [[{:keys [x]} {y .y}] ps] (+ x y)))");
@ -3943,7 +3986,7 @@ let () =
"(defn f [s [i32]] i32 (let [[a b] s] (+ a b)))"
~needle:"a slice's length is not known until the program runs";
rejects_check "an array pattern over a slice, even with & rest"
"(defn f [s [i32]] i32 (let [[a & r] s] (+ a (len r))))"
"(defn f [s [i32]] i32 (let [[a & r] s] (+ a (length r))))"
~needle:"a slice's length is not known until the program runs";
rejects_check "an array pattern over something with no elements at all"
"(defn f [n i32] i32 (let [[a b] n] (+ a b)))"
@ -3959,7 +4002,7 @@ let () =
"(defn f [] i32 (let [xs [1 2] [a & r s] xs] a))"
~needle:"& takes one name";
rejects_check "a pattern that is only & rest"
"(defn f [] i32 (let [xs [1 2] [& r] xs] (len r)))"
"(defn f [] i32 (let [xs [1 2] [& r] xs] (length r)))"
~needle:"binds the whole value";
rejects_check "one array pattern binding a name twice"
"(defn f [] i32 (let [xs [1 2] [a a] xs] a))"
@ -5236,48 +5279,54 @@ let () =
resolution, which is a check that cannot fail found in review. *)
(match
Check.program
(program "(defn len [a string b string] i32 999)"
@ Parse.program (read ~file:"<elsewhere>" "(defn g [] i32 (len \"a\" \"b\"))"))
(program "(defn length [a string b string] i32 999)"
@ Parse.program
(read ~file:"<elsewhere>" "(defn g [] i32 (length \"a\" \"b\"))"))
with
| _ -> check "a call in another file does not reach the shadow" false
| exception Loc.Error d ->
check "a call in another file reaches the builtin, at the builtin's arity"
(contains d.Loc.dmsg "len takes 1 argument, given 2"));
(contains d.Loc.dmsg "length takes 1 argument, given 2"));
(* ── builtin/, the reserved qualifier ──────────────────────────────
The escape from the dead end above: [builtin/len] is the builtin [len]
whatever the file has decided [len] means. Pinned from both ends
The escape from the dead end above: [builtin/length] is the builtin
[length]
whatever the file has decided [length] means. Pinned from both ends
with a shadow in the way and with nothing in the way at all because a
spelling that only worked while some other declaration existed would be
one nobody could write down in advance. *)
accepts "builtin/ is legal with nothing shadowed"
"(defn f [] i32 (builtin/len \"abcd\"))";
"(defn f [] i32 (builtin/length \"abcd\"))";
(* The pair that says the two spellings part company. One declaration list,
two calls: the shadow takes two arguments and the builtin takes one, so
each call is refusable only under one of the two readings. The bare name
at two arguments checks, and the qualified one at two arguments is
measured against the builtin's arity. *)
accepts "the bare name reaches the shadowing defn"
"(defn len [a string b string] i32 999)\n\
(defn f [] i32 (len \"a\" \"b\"))";
"(defn length [a string b string] i32 999)\n\
(defn f [] i32 (length \"a\" \"b\"))";
rejects_check "and builtin/ beside it reaches the builtin"
"(defn len [a string b string] i32 999)\n\
(defn f [] i32 (builtin/len \"a\" \"b\"))"
~needle:"len takes 1 argument, given 2";
"(defn length [a string b string] i32 999)\n\
(defn f [] i32 (builtin/length \"a\" \"b\"))"
~needle:"length takes 1 argument, given 2";
(* The program the earlier lane could not write: a shadowing defn that
*wraps* what it shadows. Without the qualifier the inner call reached
the definition being written and the program stack-overflowed at run
time; what is pinned here is that the body holds no call to [len] at
time; what is pinned here is that the body holds no call to [length] at
all, which is the difference between a wrapper and a loop. *)
(match checked "(defn len [s string] i32 (builtin/+ 1 (builtin/len s)))" with
(match
checked "(defn length [s string] i32 (builtin/+ 1 (builtin/length s)))"
with
| p ->
let recurs = ref false in
(match List.find_opt (fun (f : Tast.fn) -> f.Tast.name = "len") p.Tast.fns with
(match
List.find_opt (fun (f : Tast.fn) -> f.Tast.name = "length") p.Tast.fns
with
| Some f ->
List.iter
(Tast.walk (fun (e : Tast.expr) ->
match e.Tast.e with
| Tast.Call ("len", _) -> recurs := true
| Tast.Call ("length", _) -> recurs := true
| _ -> ()))
f.Tast.body;
check "a shadowing defn wraps the builtin instead of recurring"
@ -5309,8 +5358,9 @@ let () =
than falling through to the function table, which holds the very
definition the qualifier was written to get away from. *)
rejects_check "a call-only builtin is refused as a value, not resolved"
"(defn len [s string] i32 1)\n(defn f [] () (println builtin/len))"
~needle:"builtin/len is the builtin len, which is a call and not a value";
"(defn length [s string] i32 1)\n(defn f [] () (println builtin/length))"
~needle:
"builtin/length is the builtin length, which is a call and not a value";
(* The qualifier reaching nothing. Named as not a builtin, and the
did-you-mean is over the builtins alone. *)
(match diag_of "(defn f [] i32 (builtin/nosuch))" with
@ -5324,26 +5374,26 @@ let () =
else; an ordinary function is called by the name it was defined \
under")
| None -> check "builtin/nosuch is refused" false);
(match diag_of "(defn f [] i32 (builtin/lne \"ab\"))" with
(match diag_of "(defn f [] i32 (builtin/lenght \"ab\"))" with
| Some d ->
check "and a near miss is offered in the qualified spelling"
(d.Loc.dmsg
= "lne is not a builtin, so builtin/lne reaches nothing — did you \
mean builtin/len?")
| None -> check "builtin/lne is refused" false);
= "lenght is not a builtin, so builtin/lenght reaches nothing — did you \
mean builtin/length?")
| None -> check "builtin/lenght is refused" false);
(* And the did-you-mean everywhere else is untouched: a bare typo is still
answered with a bare name, not with a qualifier nobody reached for. *)
rejects_check "an unqualified typo is not answered with builtin/"
"(defn f [] i32 (lne \"ab\"))" ~needle:"did you mean len?";
(* The reservation from the other side. [(defn builtin/len ...)] reads —
"(defn f [] i32 (lenght \"ab\"))" ~needle:"did you mean length?";
(* The reservation from the other side. [(defn builtin/length ...)] reads —
'/' is an ordinary symbol character and would land in the function
table under a name nothing can ever call, because the prefix is stripped
before any table is consulted. *)
(match diag_of "(defn builtin/len [s string] i32 1)" with
(match diag_of "(defn builtin/length [s string] i32 1)" with
| Some d ->
check "a declaration cannot take the reserved qualifier"
(contains d.Loc.dmsg
"builtin/len cannot be declared: builtin/ is a reserved qualifier")
"builtin/length cannot be declared: builtin/ is a reserved qualifier")
| None -> check "a declaration under builtin/ is refused" false);
(* and's last operand is the then arm and the sentinel carrying the previous
@ -5913,9 +5963,9 @@ let () =
itself is refused where it is written, at the definition. *)
rejects_check "a map keyed by a type variable that is not hashable?"
~needle:"is not a map key"
"(defn f [m (Map $t i32)] i32 {:where (numeric? $t)} (len m))";
"(defn f [m (Map $t i32)] i32 {:where (numeric? $t)} (length m))";
accepts "and hashable? is what says it is"
"(defn f [m (Map $t i32)] i32 {:where (hashable? $t)} (len m))";
"(defn f [m (Map $t i32)] i32 {:where (hashable? $t)} (length m))";
accepts "and under it the operations are deferred, not refused"
"(defn f [m (Map $t i32) k $t] () {:where (hashable? $t)} (put m k 1))";
accepts "get over a type-variable key answers an (Option V)"
@ -5951,7 +6001,7 @@ let () =
(let [v (vec-new $t a)] (push v x) v))";
accepts "map-new over type variables written with the sigil"
"(defn f [k $t] i32 {:where (hashable? $t)} \
(let [m (map-new $t i32)] (put m k 1) (let [n (len m)] (free m) n)))";
(let [m (map-new $t i32)] (put m k 1) (let [n (length m)] (free m) n)))";
accepts "a zeroed fixed array of a type variable"
"(defn f [x $t] $t (let [a (array 3 $t)] (set (at a 1) x) (at a 1)))";
accepts "a cast to a type variable written with the sigil"
@ -6153,7 +6203,7 @@ let () =
\ (put tm \"k\" 1)\n\
\ (reserve tv 4)\n\
\ (arith 1 2)\n\
\ (print (len txt))))"
\ (print (length txt))))"
[ (5, 11, native,
"allocates: an arena takes its whole region from the host here");
(8, 13, native,

View File

@ -1092,7 +1092,7 @@ let () =
let c =
Session.eval t
"(defn pick [xs [$t]] $t {:where (ordered? $t)} (let [m (at xs 0)] \
(dotimes [i (len xs)] (set m (max m (at xs i)))) m))"
(dotimes [i (length xs)] (set m (max m (at xs i)))) m))"
in
if not (List.mem "pick-i32" c.Session.fns) then
fail "redefining a generic did not reinstall pick-i32";

View File

@ -359,7 +359,7 @@ let padded_key =
(defn main [] i32\n\
\ (let [m (map-new Padded i32)]\n\
\ (dotimes [i 40] (put m (Padded {.a (i8 i) .b (i64 i) .c 7}) i))\n\
\ (print (len m)) (println \"\")\n\
\ (print (length m)) (println \"\")\n\
\ (match (get m (Padded {.a (i8 9) .b (i64 9) .c 7}))\n\
\ (Some v) (do (print v) (println \"\"))\n\
\ None (println \"missing\"))\n\

View File

@ -39,7 +39,7 @@
;;; dropped — they are spliced into [start-at], which then refuses them by its
;;; own arity, at the call site, in the usual words.
(defmacro start [& args]
(if (= (len args) 0)
(if (= (length args) 0)
`(start-auto)
`(start-at ~@args)))

Some files were not shown because too many files have changed in this diff Show More