;;; test-flan-fln-live.el --- The .fln keys against a real daemon -*- lexical-binding: t; -*- ;; Loaded by test-flan.el, near its end, with `flan-command' already the ;; compiler under test. test-flan-fln.el checks which text each key picks; ;; this checks the reader accepts that text as a whole form, on both backends: ;; a daemon of its own on a .fln file with no main, once on x86 and once on ;; LLVM, each key sent at least once, and a pause mark the daemon must find. ;;; Code: (require 'flan-fln-mode) (declare-function test-flan--check "test-flan" (name ok)) (declare-function test-flan--result "test-flan" ()) (defvar test-flan-fln-live-dir) (defvar test-flan-fln-live-socket) (defconst test-flan-fln-live--program "fn fib(n: i64) -> i64 if n < 2 n else fib(n - 1) + fib(n - 2) fn twice(n: i64) -> i64 = n * 2 fn total(n: i32) -> i32 let t = 0 for i in range(n) t = t + i t fn sign(n: i64) -> i64 if n < 0 -1 elif n == 0 0 else 1 enum Dir north south east fn pick(d: Dir) -> i64 match d :north -> 10 :south -> 20 :east -> 1 + 2 _ -> twice(3) fn sign2(k: i64) if k < 0 then -1 elif k == 0 then 0 else 1 fn size(k: i64) -> i64 if k < 10 1 else 2 fn lam(k: i64) -> i64 let add = fn(a: i64, b: i64) -> i64 = a + b let dbl = fn(a: i64) -> i64 a * 2 add(dbl(k), 1) fn rs() -> i64 restart-case 3 restart retry() \"Try again\" 4 fn t2(k: i64) -> i64 let a = 1 let b = 2 let c: i64 = 3 a + b + c + k fn dir(d: Dir) -> i64 match d Dir.north -> 7 _ -> 8 comment(): if 1 < 2 and 3 < 4 twice(1) elif 1 > 2 or 3 > 4 0 if 2 < 1 then 5 elif 2 == 1 then 6 else 7 twice(4) if 2 > 1 twice(2) else 0 let x = 3 twice(x) + 1 ") (defun test-flan-fln-live--run (backend args) (let* ((sock (concat test-flan-fln-live-socket "-fln-" backend)) (file (expand-file-name (format "fln-live-%s.fln" backend) test-flan-fln-live-dir)) (flan-daemon-args args) (name (lambda (s) (format "%s: %s" backend s))) (value (lambda (code) (plist-get (flan--request (list :op "eval-expr" :code code :file "")) :value))) (goto (lambda (needle &optional after) (goto-char (point-min)) (search-forward needle) (unless after (goto-char (match-beginning 0))))) (shows (lambda (v) (let ((r (test-flan--result))) (prog1 (and r (string-match-p (concat "=> " (regexp-quote v) "\\'") (string-trim r))) (flan-clear-result)))))) (with-temp-file file (insert test-flan-fln-live--program)) (ignore-errors (delete-file sock)) (flan file sock) (test-flan--check (funcall name "a daemon starts on a .fln file") (process-live-p flan--connection)) (unwind-protect (with-current-buffer (find-file-noselect file) (test-flan--check (funcall name "which opens in flan-fln-mode") (eq major-mode 'flan-fln-mode)) ;; C-c C-c, from inside a fn changed in the buffer. (funcall goto "n * 2") (delete-char 5) (insert "n * 3") (flan-fln-eval-defun) (test-flan--check (funcall name "C-c C-c installs the fn at point") (equal (funcall value "(twice 7)") "21")) ;; C-x C-e at the end of a column-0 declaration. (funcall goto "n * 3") (delete-char 5) (insert "n * 4") (flan-fln-eval-last) (test-flan--check (funcall name "C-x C-e at the end of a column-0 fn installs it") (equal (funcall value "(twice 7)") "28")) ;; C-x C-e at the end of an inner statement: text from column 3. (funcall goto "twice(4)" t) (flan-fln-eval-last) (test-flan--check (funcall name "C-x C-e sends the statement ending at point") (funcall shows "16")) ;; ...and inside a line, the term before point. (funcall goto "twice(4)") (let ((at (point))) (insert "fib(10) + ") (goto-char (+ at (length "fib(10)"))) (flan-fln-eval-last) (test-flan--check (funcall name "C-x C-e inside a line sends the term before point") (funcall shows "55")) (delete-region at (+ at (length "fib(10) + ")))) ;; C-c C-e on a clause: the whole if, from column 3, clauses and all. (funcall goto "else\n 0") (flan-fln-eval-statement) (test-flan--check (funcall name "C-c C-e on a clause sends its if, and it reads") (funcall shows "8")) ;; A bare let: it and the rest of its block, which is its scope. (funcall goto "let x = 3") (flan-fln-eval-statement) (test-flan--check (funcall name "C-c C-e on a bare let sends its scope with it") (funcall shows "13")) ;; A region of several statements reads as one (do ...). (funcall goto "twice(4)") (transient-mark-mode 1) (set-mark (point)) (funcall goto "else\n 0" t) (flan-fln-eval-statement) (test-flan--check (funcall name "C-c C-e on a region of statements evaluates them in order") (funcall shows "8")) ;; C-c C-n sends and moves on. (funcall goto "twice(4)") (flan-fln-eval-statement-and-next) (test-flan--check (funcall name "C-c C-n sends the statement") (funcall shows "16")) (test-flan--check (funcall name "and moves to the next") (looking-at "if 2 > 1")) ;; A one-line if with its clauses on the lines under it. (funcall goto "if 2 < 1 then 5" t) (flan-fln-eval-last) (test-flan--check (funcall name "C-x C-e at the end of a one-line if that goes on evaluates the whole if") (funcall shows "7")) (funcall goto "elif 2 == 1") (flan-fln-eval-statement) (test-flan--check (funcall name "C-c C-e on a clause under a one-line if sends its if") (funcall shows "7")) ;; The newer forms install from the buffer and run. (pcase-dolist (`(,needle ,call ,want) '(("fn sign2" "(sign2 0)" "0") ("fn size" "(size 20)" "2") ("fn lam" "(lam 3)" "7") ("fn rs" "(rs)" "3") ("fn dir" "(dir :north)" "7"))) (funcall goto needle) (flan-fln-eval-defun) (test-flan--check (funcall name (format "C-c C-c installs %s" needle)) (equal (funcall value call) want))) ;; The pause mark. What is sent is a line and column, and the daemon ;; answers `:pause' only when a form the reader made starts exactly ;; there (`Ast.mark_pause'). Each kind of target once, and one position ;; a column off to show the answer can be no. ;; C-x C-e on an arm's value, and on a condition line. (funcall goto ":south -> 20" t) (flan-fln-eval-last) (test-flan--check (funcall name "C-x C-e at the end of a match arm evaluates its value") (funcall shows "20")) (funcall goto ":east -> 1 +" t) (flan-fln-eval-last) (test-flan--check (funcall name "C-x C-e on an arm's wrapped value evaluates all of it") (funcall shows "3")) (funcall goto "if 1 < 2 and" t) (flan-fln-eval-last) (test-flan--check (funcall name "C-x C-e on a wrapped condition evaluates all of it") (funcall shows "true")) ;; An error on the wrapped line of a condition or value cut out ;; mid-line is reported where it is in the buffer, column and all. (let ((refused (lambda (needle bad fix) (funcall goto needle t) (let ((line (1+ (line-number-at-pos))) col) (save-excursion (forward-line 1) (search-forward fix (line-end-position)) (replace-match bad t t) (setq col (1+ (- (point) (line-beginning-position) (length (car (last (split-string bad " ")))))))) (prog1 (list (condition-case err (progn (flan-fln-eval-last) nil) (user-error (error-message-string err))) (format ":%d:%d)" line col)) (save-excursion (goto-char (point-min)) (search-forward bad) (replace-match fix t t))))))) (pcase-dolist (`(,what ,needle ,bad ,fix) '(("an if condition" "if 1 < 2 and" "3 < 4 4" "3 < 4") ("an elif condition" "elif 1 > 2 or" "3 > 4 4" "3 > 4") ("an arm's value" ":east -> 1 +" "2 2" "2"))) (let ((r (funcall refused needle bad fix))) (test-flan--check (funcall name (format "an error on the wrapped line of %s is reported at its column" what)) (and (car r) (string-suffix-p (cadr r) (car r)))) (unless (and (car r) (string-suffix-p (cadr r) (car r))) (message " want ...%s\n got %S" (cadr r) (car r)))))) (flan-clear-errors) (funcall goto "if 2 > 1" t) (flan-fln-eval-last) (test-flan--check (funcall name "C-x C-e at the end of an if line evaluates the condition") (funcall shows "true")) (dolist (c '(("n - 1)" "a call, from its name") ("elif n" "an elif, at its condition") ("else\n 1" "an else, at its block") (":south -> 20" "a match arm, at its value") ("_ ->" "a match arm, at its block") ("n < 2" "an if statement") ("t = 0" "a let") ("i in range" "a for") ("+ i" "an assignment") ("if k < 0 then" "a one-line if") ("elif k" "a one-line elif, at its condition") ("else 1" "a one-line else, at its value") ("else 2" "a one-line else after a block, at its value") ("a: i64, b" "a typed lambda, from its fn") ("a * 2" "a typed lambda's block") ("restart retry" "a restart with a report, at its block") ("Dir.north" "an enum member's arm, at its value") ("let b = 2" "a let the let above takes in, at its value") ("let c: i64" "a typed one, at its value"))) (funcall goto (car c)) (let ((reply (flan-fln-eval-defun '(4)))) (test-flan--check (funcall name (format "C-u C-c C-c marks %s where the reader starts it" (cadr c))) (plist-get reply :pause))) (flan-fln-eval-defun)) (test-flan--check (funcall name "and a plain C-c C-c takes the mark down") (null (flan--pause-overlays))) (funcall goto "fib(n - 1)") (let* ((b (flan-fln--toplevel-bounds (point))) (off (condition-case nil (plist-get (flan--eval (flan--text (car b) (cdr b)) "defn" nil nil (cons (1+ (point)) (+ 3 (point)))) :pause) (user-error nil)))) (test-flan--check (funcall name "a position one column off the call is not taken") (null off))) (flan-fln-eval-defun) (set-buffer-modified-p nil) (kill-buffer)) ;; Stopped whatever happened above: a daemon this started is its own to end. (flan-quit)) (ignore-errors (delete-file sock)) (ignore-errors (delete-file file)))) (message "\nthe .fln keys, against a daemon on each backend") (test-flan-fln-live--run "x86" nil) (test-flan-fln-live--run "llvm" '("--llvm")) ;;; test-flan-fln-live.el ends here