C-x C-e on a top-level form installs it
# Conflicts: # emacs/flan-dev.el
This commit is contained in:
commit
dd7a4eaea7
@ -22,13 +22,22 @@
|
||||
;; reset between runs — it is what calling (main) at a Common Lisp or Clojure
|
||||
;; prompt does, and the modeline says `flan:parked' while it waits.
|
||||
;;
|
||||
;; C-x C-e evaluates the expression before point *in the running program* and
|
||||
;; shows its value. That is a different primitive from redefining a name:
|
||||
;; there is nothing to install a body into, so the expression is wrapped in a
|
||||
;; thunk the program runs at its next frame boundary. Only scalars, bool and
|
||||
;; strings render so far — a Flan value carries no header, so a printer has to
|
||||
;; be derived per type at compile time, and the ones that are not derived yet
|
||||
;; say so rather than guessing.
|
||||
;; C-x C-e evaluates the form before point *in the running program*. An
|
||||
;; expression is a different primitive from redefining a name: there is
|
||||
;; nothing to install a body into, so it is wrapped in a thunk the program
|
||||
;; runs at its next frame boundary, and its value is shown. Only scalars,
|
||||
;; bool and strings render so far — a Flan value carries no header, so a
|
||||
;; printer has to be derived per type at compile time, and the ones that are
|
||||
;; not derived yet say so rather than guessing.
|
||||
;;
|
||||
;; A *declaration* before point takes the other path and is installed, the way
|
||||
;; C-c C-c installs one. The key is dispatched on the form it would send, so
|
||||
;; a `defvar' at the top of a file evaluates as a declaration while an
|
||||
;; expression inside a `defn' body still evaluates as an expression. The
|
||||
;; split used to be by keybinding, which meant a top-level `defvar' under
|
||||
;; C-x C-e came back as "defvar is a top-level declaration, not an
|
||||
;; expression" — an editor artifact, not a limit of the compiler, which has
|
||||
;; had both evaluators all along.
|
||||
|
||||
;;; Code:
|
||||
|
||||
@ -1485,10 +1494,20 @@ of the tenth name tells you neither how many there were nor which."
|
||||
;; `:fns' are the bodies that were installed and `:names' is
|
||||
;; everything the evaluation declared; a buffer of five functions
|
||||
;; and two vars should not report as "five".
|
||||
;;
|
||||
;; A declaration with no body at all — a `defvar', a `defstruct' —
|
||||
;; has none of the first, and leading with WHAT then read as "form
|
||||
;; installed in 4 ms (also ticks)": the one name that actually
|
||||
;; changed, parenthesised as an afterthought, beside a label that
|
||||
;; says nothing. Where there are no functions the names *are* what
|
||||
;; changed, so they are what the sentence is about, and the aside
|
||||
;; is then empty by construction rather than a repeat of it. Now
|
||||
;; that `C-x C-e' reaches this path too, that sentence is also how
|
||||
;; you tell an installed declaration from an expression's `=>'.
|
||||
(message "flan: %s installed in %.0f ms%s"
|
||||
(flan-dev--names-phrase fns what)
|
||||
(flan-dev--names-phrase (or fns names) what)
|
||||
(or (plist-get reply :ms) 0)
|
||||
(let ((vars (seq-difference names fns)))
|
||||
(let ((vars (and fns (seq-difference names fns))))
|
||||
(if vars (format " (also %s)"
|
||||
(flan-dev--names-phrase vars ""))
|
||||
"")))))))
|
||||
@ -1636,29 +1655,105 @@ arrive in the same load or the first refers to storage that does not exist."
|
||||
;; buffer is not feedback, it is a flicker.
|
||||
(flan-dev-clear-pause))
|
||||
|
||||
;; Which of the two evaluators `C-x C-e' runs is decided by the form it would
|
||||
;; send, not by where the cursor is sitting. Point inside a `defn' body, on
|
||||
;; `(+ ticks 1)', must still evaluate that expression — the enclosing `defn' is
|
||||
;; not what was asked for and reinstalling it would be a different command.
|
||||
;;
|
||||
;; Hardcoded, which is the thing to be careful about: the authority is the
|
||||
;; declaration arm of `Parse.expr' in lib/parse.ml (the "is a top-level
|
||||
;; declaration, not an expression" refusal, around line 458), plus the
|
||||
;; `declare'/`declare-c' arm a few forms below it. This list is that set and
|
||||
;; has to be changed with it. `defunion' is being renamed `defdata', with
|
||||
;; `defunion' becoming a C-style untagged union; both spellings are top-level
|
||||
;; declarations either way, so this list wants `defdata' adding when that
|
||||
;; lands rather than a swap.
|
||||
;;
|
||||
;; `package' is deliberately not here even though flan-mode.el's
|
||||
;; `flan--definers' has it. This is the set of heads that *fail* when sent to
|
||||
;; the expression evaluator, which is the bug being fixed; a package
|
||||
;; declaration is a statement about the file being read and not a change to
|
||||
;; make to a running program.
|
||||
(defconst flan-dev--declaration-heads
|
||||
'("defmacro" "defn" "defvar" "defconst" "defstruct" "defunion" "defenum"
|
||||
"defalias" "import" "declare" "declare-c")
|
||||
"Heads whose form is a declaration, and never an expression.")
|
||||
|
||||
(defun flan-dev--declaration-before-point ()
|
||||
"The top-level declaration `C-x C-e' would send, as (HEAD START END), or nil.
|
||||
|
||||
Two questions, and both have to answer yes. The depth at START says whether
|
||||
the form is top-level — `syntax-ppss' at the open delimiter reports the depth
|
||||
*before* it, so a form nobody has nested reads as 0 — and that is what keeps
|
||||
an inner expression inside a `defn' body on the expression path even when the
|
||||
enclosing form is a declaration. The head then says whether the thing is a
|
||||
declaration at all, which is what leaves a bare `(+ 1 1)' at column 1 alone.
|
||||
|
||||
Requiring both rather than the head alone is the more conservative of the two
|
||||
readings, and the one the user asked for: a `defn' written inside a `let' is
|
||||
not a definition of anything, and installing it as one would quietly accept
|
||||
code the compiler is right to refuse. Sent as an expression it gets the
|
||||
parser's own message, which says exactly that."
|
||||
(save-excursion
|
||||
(let ((end (point)))
|
||||
(condition-case nil
|
||||
(progn
|
||||
(backward-sexp)
|
||||
(let ((start (point)))
|
||||
(and (zerop (car (syntax-ppss start)))
|
||||
(looking-at "([ \t\n]*\\(\\(?:\\sw\\|\\s_\\)+\\)")
|
||||
(member (match-string-no-properties 1)
|
||||
flan-dev--declaration-heads)
|
||||
(list (match-string-no-properties 1) start end))))
|
||||
(scan-error nil)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun flan-eval-last-sexp (&optional arg)
|
||||
"Evaluate the expression before point in the running program and show it.
|
||||
"Evaluate the form before point in the running program and report it.
|
||||
|
||||
With a prefix ARG, stop at it instead: the expression is wrapped in a
|
||||
An expression is compiled into a thunk the program runs at its next frame
|
||||
boundary, and its value is shown. A top-level declaration — a `defvar', a
|
||||
`defn', anything in `flan-dev--declaration-heads' — is compiled and installed
|
||||
instead, and the reply names what changed. The compiler has always had both
|
||||
paths; this key used to reach only the first, so a `defvar' typed at the top
|
||||
of a file came back as \"defvar is a top-level declaration, not an
|
||||
expression\" and the only way to evaluate it was `C-c C-c'. That was an
|
||||
editor artifact and not a property of the language.
|
||||
|
||||
`flan-dev--declaration-before-point' decides which, off the form that would be
|
||||
sent rather than off where point is; `C-c C-c' is unchanged and stays the
|
||||
explicit \"reload the definition I am standing in\" command, which is still
|
||||
the one to use from inside a body.
|
||||
|
||||
With a prefix ARG, stop there instead. For an expression that is a flag and
|
||||
not a position — the expression sent *is* the target, and it is wrapped in a
|
||||
`(pause)' before it is checked, so the thunk breaks where it stands and the
|
||||
break loop gets the frame. A flag rather than a position, because the
|
||||
expression sent *is* the target — and because this path sends a raw
|
||||
substring, so buffer line numbers would not survive it anyway.
|
||||
break loop gets the frame. For a declaration it is a position, because a
|
||||
declaration has an inside: the mark goes on the form itself, which the daemon
|
||||
reads as stopping on entry, the same as `C-u C-u C-c C-c'.
|
||||
|
||||
It does not stick, and cannot: a thunk is built and thrown away, so there is
|
||||
no declaration for the mark to live in. Nothing is drawn in the buffer for
|
||||
the same reason."
|
||||
An expression's mark does not stick and cannot — a thunk is built and thrown
|
||||
away, so there is no declaration for it to live in — while a declaration's
|
||||
does, until the same form is evaluated again without a prefix."
|
||||
(interactive "P")
|
||||
(let ((code (buffer-substring-no-properties
|
||||
(save-excursion (backward-sexp) (point))
|
||||
(point))))
|
||||
(flan-dev--report
|
||||
(flan-dev--request
|
||||
(append
|
||||
(list :op "eval-expr" :code code :file (or buffer-file-name "<buffer>"))
|
||||
(when arg (list :pause t))))
|
||||
"expression")))
|
||||
(let ((decl (flan-dev--declaration-before-point)))
|
||||
(if decl
|
||||
(pcase-let ((`(,head ,start ,end) decl))
|
||||
;; `flan-dev--text', not `flan-dev--text-at': a top-level form starts
|
||||
;; at column 1, so the line padding is the whole fix and the columns
|
||||
;; already agree. HEAD is what the echo area falls back to when the
|
||||
;; daemon installed no bodies and declared no names.
|
||||
(flan-dev--eval (flan-dev--text start end) head start end
|
||||
(and arg (cons start end))))
|
||||
(let ((code (buffer-substring-no-properties
|
||||
(save-excursion (backward-sexp) (point))
|
||||
(point))))
|
||||
(flan-dev--report
|
||||
(flan-dev--request
|
||||
(append
|
||||
(list :op "eval-expr" :code code :file (or buffer-file-name "<buffer>"))
|
||||
(when arg (list :pause t))))
|
||||
"expression")))))
|
||||
|
||||
;;;###autoload
|
||||
(defun flan-eval-region (start end)
|
||||
|
||||
@ -399,6 +399,76 @@ is written instead — the real `message' call the real command makes."
|
||||
(string-match-p "HELLO" (buffer-string)))))
|
||||
(test-flan--check "the program's output reaches its buffer" seen))
|
||||
|
||||
;; ── Which evaluator C-x C-e reaches ──────────────────────────────────
|
||||
;;
|
||||
;; The dispatch is off the form that would be *sent*, so these four are
|
||||
;; checked with point where the key would be pressed rather than by handing
|
||||
;; the predicate a string: a form's depth is a fact about the buffer, and an
|
||||
;; off-by-one at the open delimiter would make every form read as nested,
|
||||
;; leave the feature switched permanently off, and still pass a test that
|
||||
;; only looked at the head. No daemon round trip here — nothing is sent —
|
||||
;; which is why all four are affordable.
|
||||
(goto-char (point-min))
|
||||
(search-forward "(defvar ticks i64)")
|
||||
(test-flan--check "a top-level defvar takes the declaration path"
|
||||
(equal (car (flan-dev--declaration-before-point)) "defvar"))
|
||||
(goto-char (point-min))
|
||||
(search-forward " ticks)")
|
||||
(test-flan--check "and so does a top-level defn"
|
||||
(equal (car (flan-dev--declaration-before-point)) "defn"))
|
||||
;; The case the whole "form, not cursor" rule exists for: this line is
|
||||
;; inside `step', so the enclosing declaration is a `defn' — and evaluating
|
||||
;; here must still mean this expression, not a reinstall of the function.
|
||||
(goto-char (point-min))
|
||||
(search-forward "(+ ticks 41)")
|
||||
(test-flan--check "an expression inside a defn body does not"
|
||||
(null (flan-dev--declaration-before-point)))
|
||||
(goto-char (point-max))
|
||||
(let ((beg (point)))
|
||||
(insert "\n(+ 2 3)")
|
||||
(test-flan--check "nor does a bare expression at top level"
|
||||
(null (flan-dev--declaration-before-point)))
|
||||
;; ...and it still evaluates as one, which is the half of this key that
|
||||
;; was already working and must not have moved.
|
||||
(let ((said (test-flan--said (flan-eval-last-sexp))))
|
||||
(test-flan--check "which C-x C-e evaluates and prints"
|
||||
(and said (string-match-p "5" said)
|
||||
(string-match-p "=>" said))))
|
||||
(delete-region beg (point-max)))
|
||||
|
||||
;; The bug this key had: a `defvar' typed at the top of a file could only be
|
||||
;; evaluated with C-c C-c, because C-x C-e sent it to the expression
|
||||
;; evaluator and the parser refused it as a declaration. One round trip, on
|
||||
;; the form the report has to be able to name.
|
||||
(goto-char (point-max))
|
||||
(let ((beg (point)))
|
||||
(insert "\n(defvar spark i64 9)")
|
||||
(let ((said (test-flan--said (flan-eval-last-sexp))))
|
||||
(test-flan--check "C-x C-e on a top-level defvar installs it"
|
||||
(and said (string-match-p "\\_<spark\\_>" said)
|
||||
(string-match-p "installed" said)))
|
||||
;; The two paths share a key now, so the echo area is the only thing
|
||||
;; left that says which of them ran. An installed declaration reports
|
||||
;; what changed; an expression reports `=>' and a value.
|
||||
(test-flan--check "and says so rather than printing a value"
|
||||
(and said (not (string-match-p "=>" said)))))
|
||||
(delete-region beg (point-max)))
|
||||
|
||||
;; And the literal complaint, on the `defvar' actually written in the file
|
||||
;; rather than on one typed in for the occasion. What is asserted is only
|
||||
;; what the client decides: the var is named and no value is printed.
|
||||
;; Whether the daemon answers "installed" or "nothing to install" is its
|
||||
;; call and depends on what this session has been through by now, and a test
|
||||
;; that pinned one of them here would be testing the order of the checks
|
||||
;; above it. A defvar declares no functions either way, which is what makes
|
||||
;; the name — and not the kind of form — the subject of the sentence.
|
||||
(goto-char (point-min))
|
||||
(search-forward "(defvar ticks i64)")
|
||||
(let ((said (test-flan--said (flan-eval-last-sexp))))
|
||||
(test-flan--check "C-x C-e on the file's own defvar reports the var"
|
||||
(and said (string-match-p "\\_<ticks\\_>" said)
|
||||
(not (string-match-p "=>" said)))))
|
||||
|
||||
;; The REPL buffer: typed input goes through the same eval-expr request, and
|
||||
;; the value lands at the prompt while the program's own output goes to
|
||||
;; *flan-output*. Conflating those two is the bug worth testing for.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user