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
|
;; 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.
|
;; 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
|
;; C-x C-e evaluates the form before point *in the running program*. An
|
||||||
;; shows its value. That is a different primitive from redefining a name:
|
;; expression is a different primitive from redefining a name: there is
|
||||||
;; there is nothing to install a body into, so the expression is wrapped in a
|
;; nothing to install a body into, so it is wrapped in a thunk the program
|
||||||
;; thunk the program runs at its next frame boundary. Only scalars, bool and
|
;; runs at its next frame boundary, and its value is shown. Only scalars,
|
||||||
;; strings render so far — a Flan value carries no header, so a printer has to
|
;; bool and strings render so far — a Flan value carries no header, so a
|
||||||
;; be derived per type at compile time, and the ones that are not derived yet
|
;; printer has to be derived per type at compile time, and the ones that are
|
||||||
;; say so rather than guessing.
|
;; 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:
|
;;; 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
|
;; `:fns' are the bodies that were installed and `:names' is
|
||||||
;; everything the evaluation declared; a buffer of five functions
|
;; everything the evaluation declared; a buffer of five functions
|
||||||
;; and two vars should not report as "five".
|
;; 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"
|
(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)
|
(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)"
|
(if vars (format " (also %s)"
|
||||||
(flan-dev--names-phrase vars ""))
|
(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.
|
;; buffer is not feedback, it is a flicker.
|
||||||
(flan-dev-clear-pause))
|
(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
|
;;;###autoload
|
||||||
(defun flan-eval-last-sexp (&optional arg)
|
(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
|
`(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
|
break loop gets the frame. For a declaration it is a position, because a
|
||||||
expression sent *is* the target — and because this path sends a raw
|
declaration has an inside: the mark goes on the form itself, which the daemon
|
||||||
substring, so buffer line numbers would not survive it anyway.
|
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
|
An expression's mark does not stick and cannot — a thunk is built and thrown
|
||||||
no declaration for the mark to live in. Nothing is drawn in the buffer for
|
away, so there is no declaration for it to live in — while a declaration's
|
||||||
the same reason."
|
does, until the same form is evaluated again without a prefix."
|
||||||
(interactive "P")
|
(interactive "P")
|
||||||
(let ((code (buffer-substring-no-properties
|
(let ((decl (flan-dev--declaration-before-point)))
|
||||||
(save-excursion (backward-sexp) (point))
|
(if decl
|
||||||
(point))))
|
(pcase-let ((`(,head ,start ,end) decl))
|
||||||
(flan-dev--report
|
;; `flan-dev--text', not `flan-dev--text-at': a top-level form starts
|
||||||
(flan-dev--request
|
;; at column 1, so the line padding is the whole fix and the columns
|
||||||
(append
|
;; already agree. HEAD is what the echo area falls back to when the
|
||||||
(list :op "eval-expr" :code code :file (or buffer-file-name "<buffer>"))
|
;; daemon installed no bodies and declared no names.
|
||||||
(when arg (list :pause t))))
|
(flan-dev--eval (flan-dev--text start end) head start end
|
||||||
"expression")))
|
(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
|
;;;###autoload
|
||||||
(defun flan-eval-region (start end)
|
(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)))))
|
(string-match-p "HELLO" (buffer-string)))))
|
||||||
(test-flan--check "the program's output reaches its buffer" seen))
|
(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 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
|
;; 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.
|
;; *flan-output*. Conflating those two is the bug worth testing for.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user