The pause wrap takes the expanded expression's location
C-u C-x C-e was never tried on a macro call. Ast.pause_call takes the expanded loc, which Loc.from_macro has stamped -- it sets a name and leaves file, line and column the call site's, so the frame the break loop reports is the line the reader is looking at. Asserted rather than argued. Also: the ring rule stated generally (refused at the parse of whichever file first has both members in scope, always before a session exists), and the declaration refusal's sentence made build-neutral, since the arm fires in an ordinary file parse too.
This commit is contained in:
parent
6bc4726ddd
commit
8ca63a7717
11
BUILT.md
11
BUILT.md
@ -700,16 +700,19 @@ expression can do.
|
||||
inside the daemon, and a hang there wedges the editor with the program still on screen. The **spin** — a macro that
|
||||
expands into a call to itself and does not get smaller — fires on this path, bounded at 200 rounds, and comes back as
|
||||
a `Loc.Error` that `Dev.eval_expr` already answers as an error reply. The **ring** never reaches this path, and
|
||||
finding out why was worth the trip: a ring is refused while *the package it lives in* is parsed, because that file's
|
||||
own bodies name each other, so no importer of a ring can be loaded and no session over one can exist. The refusal is
|
||||
in front of the path rather than on it, which is the stronger place for it; `test_session` asserts it at
|
||||
finding out why was worth the trip: a ring is refused at the parse of whichever file first has both of
|
||||
its members in scope — the package itself when the ring is internal to one, the importer when it is not — and that is
|
||||
always a file `Load` reads before any session exists. So no program holding a ring can be loaded and no session over
|
||||
one can be created. The refusal is in front of the path rather than on it, which is the stronger place for it; `test_session` asserts it at
|
||||
`Session.create` so that moving the check later shows up as a failing test and not as a wedged daemon.
|
||||
`test/programs/pkg-macro-idle.flan` is the fixture the spin needs — it imports and calls nothing, so the session is
|
||||
created without expanding anything and the expression is the first thing that ever expands the macro.
|
||||
|
||||
Expansion happens **before** the thunk is built, so the three-way `` `Value | `Stopped | `Timeout `` wait in
|
||||
`Dev.eval_expr` is untouched: a cold macro module costs its ~300ms before that 5-second clock starts, and `before` is
|
||||
sampled after `Session.eval_expr` has returned. `temps` is not reset on this path, unlike `decl`'s — a declaration is
|
||||
sampled after `Session.eval_expr` has returned. `C-u C-x C-e` wraps the **expanded** expression, so `Ast.pause_call`
|
||||
takes a macro-stamped location — and `Loc.from_macro` sets a name and leaves file, line and column the call site's, so
|
||||
the frame the break loop reports is still the line the reader is looking at. `temps` is not reset on this path, unlike `decl`'s — a declaration is
|
||||
a fresh top level, an expression is evaluated into a session that has been handing out temporaries all along.
|
||||
|
||||
Still missing: a package-private marker for anything other than `main`, which is why `rl/get-color-raw` is callable.
|
||||
|
||||
11
NEXT.md
11
NEXT.md
@ -117,6 +117,17 @@ on this path; the ring never can, because a ring is refused while its own packag
|
||||
expands is the prelude's macros and the imported packages' — not the file's own, which is a session
|
||||
limit `C-c C-c` shares and which is now pinned rather than fixed.
|
||||
|
||||
Two things left behind it, neither this lane's. **A session expands no macro the buffer itself
|
||||
declares** — `Macro.program` scans the forms it is handed, and an evaluation hands it the one form
|
||||
that was sent, so `(tenfold 7)` is an unknown name at `C-x C-e` and `(defn x [] i32 (tenfold 7))` is
|
||||
one at `C-c C-c`. Fixing it means the session keeping its file's own `defmacro`s the way it keeps the
|
||||
imports', which is a `Session` change and a decision about what a `defmacro` typed at the REPL does.
|
||||
And **`Dev.eval_expr` catches only `Loc.Error`** around `Session.eval_expr`: the two non-termination
|
||||
refusals are that, so they answer as replies, but `C-x C-e` can now reach `Build.macro_module` for
|
||||
the first time, and a macro that fails to *compile* raises `Failure` from the clang driver or
|
||||
`Loc.Errors` from the checker. Neither is caught, and an unhandled exception there is a dead session.
|
||||
Pre-existing for `C-c C-c`, which reaches the same builder; `lib/dev.ml` is another lane's file.
|
||||
|
||||
Two things found while finishing it. `Session` held the imported macro set but **replaced** it on
|
||||
every evaluation, and the one form `C-c C-c` sends carries no import — so a package macro worked on
|
||||
the build and was an unknown name on the first reload. It unions now, and `test_session` drives two
|
||||
|
||||
@ -458,9 +458,9 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
|
||||
| Sym ("defmacro" | "defn" | "defvar" | "defconst" | "defstruct" | "defunion"
|
||||
| "defenum" | "defalias" | "import" as name) ->
|
||||
fail f
|
||||
"%s is a top-level declaration, not an expression. A macro may answer \
|
||||
with one only where a declaration is expected, so this cannot come back \
|
||||
from evaluating an expression" name
|
||||
"%s is a top-level declaration, not an expression. A quasiquoted one is \
|
||||
a value and a macro may answer with it; an evaluated one is not a thing \
|
||||
anything can do" name
|
||||
|
||||
(* Recognised, deliberately unimplemented. Rejected rather than left to fall
|
||||
through to Call, where they would parse and mean nothing. *)
|
||||
|
||||
@ -323,6 +323,18 @@ let () =
|
||||
| _ -> ()
|
||||
| exception Loc.Error { Loc.dmsg = m; _ } ->
|
||||
fail "a prelude macro through C-x C-e: %s" m);
|
||||
(* And with a pause on it, which is C-u C-x C-e. [Ast.pause_call] takes the
|
||||
*expanded* expression's location, and expansion stamps every node a macro
|
||||
answered with [Loc.from_macro] — which sets a name and leaves the file,
|
||||
line and column the call site's, so the frame the break loop reports is
|
||||
still the line the reader is looking at. Untested until now because every
|
||||
pause case was an expression no macro touched. *)
|
||||
(match Session.eval_expr ~origin:"programs/pkg-macro.flan" ~pause:true tm
|
||||
"(mac/twice 21)" with
|
||||
| _ -> ()
|
||||
| exception Loc.Error { Loc.dmsg = m; _ } ->
|
||||
fail "C-u C-x C-e on a macro call: %s" m);
|
||||
|
||||
(* Not the file's own macro, and deliberately not: [Macro.program] collects
|
||||
those by scanning the forms it is handed, and the forms handed to an
|
||||
evaluation are the one thing that was sent. That limit is the session's
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user