;;; 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 _ -> twice(3) comment(): 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")) ;; 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 "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"))) (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