The break loop was reachable from a raw socket. This is the half that makes it reachable from an editor, and it all follows from one fact: a program stops at a moment nobody asked about. So the state is learned twice, on purpose. It rides on every reply, beside the program's output and for the same reason -- the likeliest instant for a program to stop is the one just after an evaluation, which is a reply the client is already reading, and learning it a second later from a poll would mean learning it after the echo area had said the evaluation was fine. And a timer asks anyway, once a second with `describe', because a program that stops in a frame of its own game loop produces no reply at all and folding state into replies that never come says nothing. The timer never reconnects -- that would quietly erase the `lost' state that exists to be seen -- and skips while a request is in flight, since accept-process-output runs timers and a poll firing inside a read would eat that read's reply. Three ops: `break' for the restart names, `restart' and `abort'. The annotation owns :stopped and :condition rather than the ops, so one place in the daemon decides whether the program is stopped and the poll and the prompt cannot disagree. "ok" from `restart' means accepted, not resumed: the choice is validated against the stopped stack and taken when that thread next comes round, so it says so and the client clears its own flag rather than polling once, finding it stopped, and re-opening the prompt it just answered. The agent grew one verb, `status', answered in both states. Everything else the break loop offers is refused while running, rightly; but the question an editor asks without already knowing had to have an answer either way or there would be nothing to poll. And flan_agent_poll had to become re-entrant, which was a bug rather than an addition. A C-x C-e thunk may itself error, and the break loop that catches it polls again from inside that call. The old loop cached both indices and stored tail at the end, rewinding over everything the nested poll consumed -- re-running the thunk that had just stopped the program, which is an unbounded recursion of breaks. Each job is now claimed before it is run. test_dev.ml evaluates an expression that errors and resumes it, which fails against the old shape.
25 lines
873 B
Plaintext
25 lines
873 B
Plaintext
;;;; A program that just runs, for evaluating expressions against.
|
|
;;;;
|
|
;;;; Unlike dev-loop.flan it does not count reloads and stop: C-x C-e is a
|
|
;;;; thunk the agent runs at a frame boundary, so a program under test has to
|
|
;;;; keep reaching them. (agent/wait 5) is both the poll and the pacing — 5ms
|
|
;;;; of nothing, which is what a frame is when there is no frame.
|
|
(import agent "vendor:agent")
|
|
|
|
;;; A condition, so that a body typed in later can error and stop the program
|
|
;;; where an editor can see it. It is here rather than in the evaluated form
|
|
;;; because the break loop matches on a class the *host* was compiled with.
|
|
(defstruct Missing [id i32])
|
|
|
|
(defvar ticks i64)
|
|
(defn step [] i64
|
|
(set ticks (+ ticks 1))
|
|
ticks)
|
|
|
|
(defn main [] i32
|
|
(agent/start "/tmp/flan-dev-repl-fallback.sock")
|
|
(dotimes [i 4000]
|
|
(agent/wait 5)
|
|
(set ticks (step)))
|
|
0)
|