flan-fln-mode reads a lambda's block inside brackets as statements, and the spec and manual write lambdas with =>
This commit is contained in:
parent
3c962197f4
commit
6e56fce7fa
9
TODO.org
9
TODO.org
@ -644,11 +644,10 @@ awaiting confirmation, and the build order. Rules out Parinfer, wisp and
|
||||
sweet-expressions, and a simplified in-paren syntax — all thin the parens
|
||||
without removing them.
|
||||
|
||||
** NEXT Lambdas are written with =>
|
||||
Decided 2026-09-26: a lambda's body follows ~=>~, and ~=>~ is its only spelling
|
||||
(~fn(a) = x~ is refused for a lambda; named functions keep ~=~). A header ending in ~=>~
|
||||
takes an indented block even inside brackets, closing where the brackets close:
|
||||
~sort-by(xs, fn(a, b) =>~ plus a block.
|
||||
** DONE Lambdas are written with =>
|
||||
CLOSED: [2026-09-26]
|
||||
A block lambda inside brackets is the last thing in them, its block ending where they close: a comma
|
||||
after it (so a second block lambda, or one not last) is refused, and the fix names it with ~let~.
|
||||
|
||||
** NEXT A condition struct with a parent has no sugar
|
||||
On the author's decision. =defstruct(DiskFull, :parent, IoError, [free i64])= is the fallback, with a
|
||||
|
||||
@ -1205,7 +1205,7 @@ Use `C-c C-g` if you need frames.
|
||||
| `M-a` / `M-e` | `(` / `)` | statement: start / end (`)`: start of the next) |
|
||||
| `C-M-u` | same | up to the enclosing bracket, or the line that owns the block |
|
||||
| `C-M-f` / `C-M-b` | same | brackets and terms, as everywhere |
|
||||
| `TAB` | same | a line at a valid column stays; an empty or misplaced line goes deepest; each repeat steps out a level. One level deeper only after a line that opens a block: never after a `let`, unless its value goes on under it (`= match x`, `= if c`, a lambda header) |
|
||||
| `TAB` | same | a line at a valid column stays; an empty or misplaced line goes deepest; each repeat steps out a level. One level deeper only after a line that opens a block: never after a `let`, unless its value goes on under it (`= match x`, `= if c`, a lambda header). After a line ending in `=>`, one level in from that line, inside brackets too; a line of that block keeps to the block's columns |
|
||||
| `DEL` in indentation | same | drop one level |
|
||||
| `C-c <` / `C-c >` | `<` / `>` | shift the region's lines a level |
|
||||
| `M-<up>` / `M-<down>` | same | move the statement past its neighbour |
|
||||
@ -1225,7 +1225,7 @@ enclosing statement, top-level form.
|
||||
|
||||
- **term**: a run with no space outside brackets — `f(a, b)`, `grid[r, c]`, `p.x`.
|
||||
- **group**: a bracket pair and what is inside it.
|
||||
- **statement**: a line, the deeper lines under it, lines inside brackets it leaves open, lines an operator continues, and `else`/`elif`/`on`/`restart` at its column. Blank and comment lines inside never end it.
|
||||
- **statement**: a line, the deeper lines under it, lines inside brackets it leaves open, lines an operator continues, and `else`/`elif`/`on`/`restart` at its column. Blank and comment lines inside never end it. A lambda's block inside brackets, `sort-by(xs, fn(a, b) =>` and the lines under it, is part of the call's statement, and each of its lines is a statement too: `C-c C-e` or `C-u C-c C-c` there takes that line, not the call, and never the call's closing `)` at the end of it.
|
||||
- **body**: a statement's own block, up to its first clause.
|
||||
- **clause**: one `else`/`elif`/`on`/`restart` line and its block, or the value on its line (`else x`).
|
||||
- **top-level form**: a column-0 line that is code, not a clause and not a continuation, through the last code line before the next one.
|
||||
|
||||
@ -25,7 +25,8 @@
|
||||
;; leaves open, the lines an operator continues, and the
|
||||
;; `else'/`elif'/`on'/`restart' clauses at its own column. Blank
|
||||
;; and comment lines inside never end it; trailing ones are not
|
||||
;; part of it.
|
||||
;; part of it. A `=>' ending a line inside brackets opens a
|
||||
;; lambda's block there, whose lines are statements again.
|
||||
;; body a statement's own block: the deeper lines under its first line,
|
||||
;; up to its first clause.
|
||||
;; clause one `else'/`elif'/`on'/`restart' line and its block.
|
||||
@ -224,15 +225,61 @@ fine here. Brackets and strings are still paired."
|
||||
(setq hit (line-beginning-position)))
|
||||
hit)))
|
||||
|
||||
(defun flan-fln--closer-line-p (pos)
|
||||
"Non-nil if POS's line starts inside a bracket with the bracket's closer."
|
||||
(save-excursion
|
||||
(let ((s (syntax-ppss (flan-fln--bol pos))))
|
||||
(and (nth 1 s) (not (nth 3 s))
|
||||
(progn (goto-char (flan-fln--bol pos))
|
||||
(skip-chars-forward " \t")
|
||||
(looking-at "\\s)"))))))
|
||||
|
||||
(defun flan-fln--lambda-arrow (pos)
|
||||
"The `=>' whose block POS's line is in, when that block is inside brackets.
|
||||
A `=>' ending a line inside a bracket opens a block there, which lasts until
|
||||
the bracket closes (lib/indent_reader.ml's `layout'): a line inside the same
|
||||
bracket after it is a statement of that block, not a continuation. A line
|
||||
that starts with the bracket's closer is not in the block."
|
||||
(save-excursion
|
||||
(let* ((bol (flan-fln--bol pos))
|
||||
(s (syntax-ppss bol))
|
||||
(open (nth 1 s)))
|
||||
(when (and open (not (nth 3 s)) (not (flan-fln--closer-line-p bol)))
|
||||
(goto-char open)
|
||||
(let (hit)
|
||||
(while (and (not hit) (< (line-end-position) bol))
|
||||
(let ((end (flan-fln--code-end (point))))
|
||||
(goto-char end)
|
||||
(when (and (looking-back "[ \t]=>" (line-beginning-position))
|
||||
(= (nth 1 (save-excursion (syntax-ppss (- end 2)))) open))
|
||||
(setq hit (- end 2))))
|
||||
(forward-line 1))
|
||||
hit)))))
|
||||
|
||||
(defun flan-fln--bracketed-p (pos)
|
||||
"Non-nil if POS's line is inside a bracket or a string, where a line break
|
||||
is only a space: not in a lambda's block."
|
||||
(and (flan-fln--in-open-p pos) (not (flan-fln--lambda-arrow pos))))
|
||||
|
||||
(defun flan-fln--continuation-p (pos)
|
||||
"Non-nil if POS's line continues the line above it.
|
||||
Inside a bracket or a string, after a line that ends in a spaced operator, or
|
||||
starting with one: the reader's three ways a line break is not a new line."
|
||||
(or (flan-fln--in-open-p pos)
|
||||
starting with one: the reader's three ways a line break is not a new line.
|
||||
Not a line of a lambda's block inside brackets, which is a statement."
|
||||
(or (flan-fln--bracketed-p pos)
|
||||
(flan-fln--starts-with-op-p pos)
|
||||
(let ((p (flan-fln--prev-code pos)))
|
||||
(and p (flan-fln--ends-in-op-p p)))))
|
||||
|
||||
(defun flan-fln--continues-p (pos start)
|
||||
"Non-nil if POS's line continues the statement that starts at START.
|
||||
A line that starts with the closer of a bracket opened before START closes
|
||||
something START is inside, a lambda's block's call, and is not START's."
|
||||
(and (flan-fln--continuation-p pos)
|
||||
(not (and (flan-fln--closer-line-p pos)
|
||||
(< (nth 1 (save-excursion (syntax-ppss (flan-fln--bol pos))))
|
||||
(flan-fln--bol start))))))
|
||||
|
||||
(defun flan-fln--clause-line-p (pos)
|
||||
"Non-nil if POS's line starts a clause: else, elif, on or restart.
|
||||
A word, and only when a space or the end of the line follows it."
|
||||
@ -245,16 +292,21 @@ A word, and only when a space or the end of the line follows it."
|
||||
(defun flan-fln--logical-start (pos)
|
||||
"The first line of the line POS is on, after continuation lines are joined."
|
||||
(let ((bol (flan-fln--bol pos)) p)
|
||||
(while (and (flan-fln--continuation-p bol)
|
||||
(setq p (flan-fln--prev-code bol)))
|
||||
(setq bol p))
|
||||
(while (cond
|
||||
;; A closer's line belongs with the line its bracket opens on,
|
||||
;; not with a lambda's block just above it.
|
||||
((flan-fln--closer-line-p bol)
|
||||
(setq bol (flan-fln--bol (nth 1 (save-excursion (syntax-ppss bol))))))
|
||||
((and (flan-fln--continuation-p bol)
|
||||
(setq p (flan-fln--prev-code bol)))
|
||||
(setq bol p))))
|
||||
bol))
|
||||
|
||||
(defun flan-fln--logical-end (pos)
|
||||
"The last line of the joined line whose first line is POS's."
|
||||
(let ((bol (flan-fln--bol pos)) n)
|
||||
(let* ((bol (flan-fln--bol pos)) (start bol) n)
|
||||
(while (and (setq n (flan-fln--next-code bol))
|
||||
(flan-fln--continuation-p n))
|
||||
(flan-fln--continues-p n start))
|
||||
(setq bol n))
|
||||
bol))
|
||||
|
||||
@ -309,18 +361,23 @@ depth, outside strings and comments, or nil."
|
||||
(flan-fln--joined-end l))))
|
||||
(and m (cdr m))))
|
||||
|
||||
(defun flan-fln--ends-in-arrow-p (pos)
|
||||
"Non-nil if POS's line ends in `=>': a lambda's header, its block under it."
|
||||
(save-excursion
|
||||
(let ((end (flan-fln--code-end pos)))
|
||||
(goto-char end)
|
||||
(and (not (nth 8 (syntax-ppss end)))
|
||||
(looking-back "[ \t]=>" (line-beginning-position))))))
|
||||
|
||||
(defun flan-fln--lambda-header-p (pos end)
|
||||
"Non-nil if a lambda with its body under it starts at POS and runs to END:
|
||||
`fn(a, b)', or `fn(a: C, b) -> R', with no `= body' after it."
|
||||
`fn(a, b) =>', or `fn(a: C, b) -> R =>', with nothing after the `=>'."
|
||||
(save-excursion
|
||||
(goto-char pos)
|
||||
(and (looking-at "fn(")
|
||||
(let ((close (ignore-errors (scan-lists (+ pos 2) 1 0))))
|
||||
(and close (<= close end)
|
||||
(progn (goto-char close) (skip-chars-forward " \t")
|
||||
(or (>= (point) end)
|
||||
(and (looking-at "->[ \t]")
|
||||
(not (flan-fln--find-top "[ \t]=[ \t]" (point) end))))))))))
|
||||
(progn (goto-char end) (looking-back "[ \t]=>" close)))))))
|
||||
|
||||
(defun flan-fln--value-opens-p (l)
|
||||
"Non-nil if the value the joined line L binds or assigns goes on under it:
|
||||
@ -346,6 +403,8 @@ line's own block only."
|
||||
(last (flan-fln--logical-end start))
|
||||
(next (flan-fln--next-code last)))
|
||||
(while (and next
|
||||
(not (and (flan-fln--closer-line-p next)
|
||||
(not (flan-fln--continues-p next start))))
|
||||
(or (> (flan-fln--indent-at next) indent)
|
||||
(flan-fln--continuation-p next)
|
||||
(and (not no-clauses)
|
||||
@ -355,9 +414,26 @@ line's own block only."
|
||||
next (flan-fln--next-code last)))
|
||||
last))
|
||||
|
||||
(defun flan-fln--trim-closers (beg end)
|
||||
"END, less the closers before it whose brackets open before BEG.
|
||||
The last statement of a lambda's block inside a call ends with the call's `)'
|
||||
on its line, which is not the statement's."
|
||||
(save-excursion
|
||||
(goto-char end)
|
||||
(let (done)
|
||||
(while (not done)
|
||||
(skip-chars-backward " \t" beg)
|
||||
(let ((open (and (> (point) beg) (memq (char-before) '(?\) ?\] ?\}))
|
||||
(nth 1 (save-excursion (syntax-ppss (1- (point))))))))
|
||||
(if (and open (< open beg))
|
||||
(backward-char 1)
|
||||
(setq done t))))
|
||||
(point))))
|
||||
|
||||
(defun flan-fln--span (start last)
|
||||
"(BEG . END) from the text of START's line to the code end of LAST's."
|
||||
(cons (flan-fln--first-char start) (flan-fln--code-end last)))
|
||||
(let ((beg (flan-fln--first-char start)))
|
||||
(cons beg (flan-fln--trim-closers beg (flan-fln--code-end last)))))
|
||||
|
||||
(defun flan-fln--statement-bounds (start)
|
||||
"Bounds of the statement whose first line is START."
|
||||
@ -687,7 +763,11 @@ forms, where the clause line itself is not."
|
||||
(g (flan-fln--group-bounds pos)))
|
||||
(cond
|
||||
((and arm (< pos (plist-get arm :arrow))) (plist-get arm :value))
|
||||
((and g (cdr g) (> (car g) (car b)))
|
||||
;; Not the brackets a lambda's block is in: a line of the block is a
|
||||
;; statement of its own.
|
||||
((and g (cdr g) (> (car g) (car b))
|
||||
(not (let ((a (flan-fln--lambda-arrow pos)))
|
||||
(and a (< (car g) a) (< a pos)))))
|
||||
(cons (flan-fln--group-form-start (car g)) (cdr g)))
|
||||
(arm (plist-get arm :value))
|
||||
((and l (flan-fln--clause-line-p l)) (flan-fln--clause-target l))
|
||||
@ -1158,6 +1238,8 @@ Before it at the same level, else out to the line that owns this block."
|
||||
(let ((bol (line-beginning-position)))
|
||||
(or (looking-back ":" bol)
|
||||
(looking-back "[ \t]->" bol)
|
||||
;; A lambda's header, at the top of a line or inside brackets.
|
||||
(looking-back "[ \t]=>" bol)
|
||||
;; `let x =' and `def colors =' with the value as a block,
|
||||
;; which the author's list leaves out and the reader reads.
|
||||
(looking-back "[ \t]=" bol))))))
|
||||
@ -1177,11 +1259,28 @@ Before it at the same level, else out to the line that owns this block."
|
||||
"Columns TAB offers POS's line outside brackets, deepest first."
|
||||
(let* ((prev (flan-fln--prev-code pos))
|
||||
(stack (mapcar #'car (flan-fln--stack pos))))
|
||||
(if (and prev (flan-fln--opener-p (flan-fln--logical-start prev) prev))
|
||||
(cons (+ (flan-fln--indent-at (flan-fln--logical-start prev))
|
||||
flan-fln-indent-offset)
|
||||
stack)
|
||||
stack)))
|
||||
(cond
|
||||
;; A lambda's block goes under the line its `=>' ends, which may be a
|
||||
;; line of a call wrapped inside its brackets.
|
||||
((and prev (flan-fln--ends-in-arrow-p prev))
|
||||
(cons (+ (flan-fln--indent-at prev) flan-fln-indent-offset) stack))
|
||||
((and prev (flan-fln--opener-p (flan-fln--logical-start prev) prev))
|
||||
(cons (+ (flan-fln--indent-at (flan-fln--logical-start prev))
|
||||
flan-fln-indent-offset)
|
||||
stack))
|
||||
(t stack))))
|
||||
|
||||
(defun flan-fln--block-levels (pos)
|
||||
"Columns TAB offers POS's line at a block's level, deepest first.
|
||||
In a lambda's block inside brackets, only those right of the line its `=>'
|
||||
ends: a line at or left of it would be outside the block, still inside the
|
||||
brackets, which the reader refuses."
|
||||
(let ((arrow (flan-fln--lambda-arrow pos)))
|
||||
(if (not arrow)
|
||||
(flan-fln--levels pos)
|
||||
(let ((base (flan-fln--indent-at arrow)))
|
||||
(or (seq-filter (lambda (c) (> c base)) (flan-fln--levels pos))
|
||||
(list (+ base flan-fln-indent-offset)))))))
|
||||
|
||||
(defun flan-fln--clause-columns (word pos)
|
||||
"Columns of the lines above POS a clause WORD may sit under, deepest first."
|
||||
@ -1226,19 +1325,20 @@ opening line's column."
|
||||
(let ((s (syntax-ppss (point))))
|
||||
(cond
|
||||
((nth 3 s) nil)
|
||||
((> (car s) 0) (list (flan-fln--bracket-column (nth 1 s))))
|
||||
((and (> (car s) 0) (not (flan-fln--lambda-arrow (point))))
|
||||
(list (flan-fln--bracket-column (nth 1 s))))
|
||||
(t
|
||||
(let ((prev (flan-fln--prev-code (point))))
|
||||
(cond
|
||||
((null prev) (list 0))
|
||||
((save-excursion (back-to-indentation) (looking-at flan-fln--clause-re))
|
||||
(or (flan-fln--clause-columns (match-string-no-properties 1) (point))
|
||||
(flan-fln--levels (point))))
|
||||
(flan-fln--block-levels (point))))
|
||||
((or (flan-fln--starts-with-op-p (point))
|
||||
(flan-fln--ends-in-op-p prev))
|
||||
(list (+ (flan-fln--indent-at (flan-fln--logical-start prev))
|
||||
flan-fln-indent-offset)))
|
||||
(t (flan-fln--levels (point))))))))))
|
||||
(t (flan-fln--block-levels (point))))))))))
|
||||
|
||||
(defun flan-fln-indent-line ()
|
||||
"Indent the line to a block column.
|
||||
@ -1284,11 +1384,13 @@ Never re-indents a line against the others: the columns are the program."
|
||||
(if (and (= arg 1) (not (use-region-p))
|
||||
(> (current-column) 0)
|
||||
(= (current-column) (current-indentation))
|
||||
(not (flan-fln--in-open-p (point))))
|
||||
(not (flan-fln--bracketed-p (point))))
|
||||
(let ((cur (current-indentation)))
|
||||
(indent-line-to (or (seq-find (lambda (c) (< c cur))
|
||||
(flan-fln--levels (point)))
|
||||
0)))
|
||||
(flan-fln--block-levels (point)))
|
||||
;; A lambda's block inside brackets has no
|
||||
;; level left of its own.
|
||||
(if (flan-fln--lambda-arrow (point)) cur 0))))
|
||||
(let ((cmd (or (command-remapping 'delete-backward-char)
|
||||
#'delete-backward-char)))
|
||||
(setq this-command cmd)
|
||||
@ -1306,7 +1408,7 @@ Run when the word is finished by a space or a newline."
|
||||
(if nl (line-end-position) (point)))))
|
||||
(when (and (string-match "\\`[ \t]*\\(else\\|elif\\|on\\|restart\\)[ \t]*\\'"
|
||||
text)
|
||||
(not (flan-fln--in-open-p (point))))
|
||||
(not (flan-fln--bracketed-p (point))))
|
||||
(let ((cols (flan-fln--clause-columns (match-string 1 text) (point))))
|
||||
(when (and cols (not (memq (current-indentation) cols)))
|
||||
(indent-line-to (car cols))))))))
|
||||
@ -1567,6 +1669,8 @@ lambda or a `Fn(...)' type, and not after a match arm's."
|
||||
1 font-lock-function-name-face)
|
||||
;; A lambda's `fn', glued to its parameters.
|
||||
("\\(?:^\\|[ \t=(,]\\)\\(fn\\)(" 1 font-lock-keyword-face)
|
||||
;; And the `=>' its body follows.
|
||||
("[ \t]\\(=>\\)\\(?:[ \t]\\|$\\)" 1 font-lock-keyword-face)
|
||||
;; An enum member written `Dir.north', a constant as `:north' is.
|
||||
("\\_<[A-Z][^][ \t\n(){},;\":.]*\\.[^][ \t\n(){},;\":.]+\\_>"
|
||||
. font-lock-constant-face)
|
||||
|
||||
@ -63,11 +63,18 @@ fn size(k: i64) -> i64
|
||||
else 2
|
||||
|
||||
fn lam(k: i64) -> i64
|
||||
let add = fn(a: i64, b: i64) -> i64 = a + b
|
||||
let dbl = fn(a: 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 app(x: i64, f: Fn(i64) -> i64) -> i64 = f(x)
|
||||
|
||||
fn lam2(k: i64) -> i64
|
||||
app(k, fn(a) =>
|
||||
let b = a + 1
|
||||
b * 2)
|
||||
|
||||
fn rs() -> i64
|
||||
restart-case
|
||||
3
|
||||
@ -92,6 +99,9 @@ comment():
|
||||
elif 1 > 2 or
|
||||
3 > 4
|
||||
0
|
||||
app(5, fn(a) =>
|
||||
a * 3
|
||||
)
|
||||
if 2 < 1 then 5
|
||||
elif 2 == 1 then 6
|
||||
else 7
|
||||
@ -208,6 +218,7 @@ comment():
|
||||
'(("fn sign2" "(sign2 0)" "0")
|
||||
("fn size" "(size 20)" "2")
|
||||
("fn lam" "(lam 3)" "7")
|
||||
("fn lam2" "(lam2 3)" "8")
|
||||
("fn rs" "(rs)" "3")
|
||||
("fn dir" "(dir :north)" "7")))
|
||||
(funcall goto needle)
|
||||
@ -262,6 +273,16 @@ comment():
|
||||
(unless (and (car r) (string-suffix-p (cadr r) (car r)))
|
||||
(message " want ...%s\n got %S" (cadr r) (car r))))))
|
||||
(flan-clear-errors)
|
||||
;; A lambda's block inside a call's brackets: the call goes whole
|
||||
;; from its header line, and reads.
|
||||
(funcall goto "app(5")
|
||||
(flan-fln-eval-statement)
|
||||
(test-flan--check (funcall name "C-c C-e on a call with a block lambda sends it whole, and it reads")
|
||||
(funcall shows "15"))
|
||||
(funcall goto "app(5, fn(a) =>" t)
|
||||
(flan-fln-eval-last)
|
||||
(test-flan--check (funcall name "C-x C-e at the end of its header line too")
|
||||
(funcall shows "15"))
|
||||
(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")
|
||||
@ -281,6 +302,9 @@ comment():
|
||||
("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")
|
||||
("a) =>\n let b" "a block lambda in a call, from its fn")
|
||||
("let b = a + 1" "a statement in a block lambda's block")
|
||||
("b * 2" "the block's last statement, less the call's closer")
|
||||
("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")
|
||||
|
||||
@ -598,8 +598,8 @@ fn size(n: i64) -> i64
|
||||
else 2
|
||||
|
||||
fn k(n: i64) -> i64
|
||||
let add = fn(a: i64, b) -> i64 = a + b
|
||||
let dbl = fn(a: Fn(i64) -> i64, b: i64) -> i64
|
||||
let add = fn(a: i64, b) -> i64 => a + b
|
||||
let dbl = fn(a: Fn(i64) -> i64, b: i64) -> i64 =>
|
||||
a(b) * 2
|
||||
let r = match n
|
||||
0 -> 1
|
||||
@ -689,7 +689,7 @@ of its line with AT-END."
|
||||
(null (funcall face "x:")))))
|
||||
|
||||
(test-flan-fln--in "fn f(d: Dir) -> i64
|
||||
let g = fn(a: Fn(i64) -> i64, b) -> Vec(i64)
|
||||
let g = fn(a: Fn(i64) -> i64, b) -> Vec(i64) =>
|
||||
a(b)
|
||||
restart-case
|
||||
3
|
||||
@ -779,14 +779,14 @@ of its line with AT-END."
|
||||
("let r = if c" "a let's if")
|
||||
("x = if c" "an assignment's if")
|
||||
("let r = handler-case" "a let's handler-case")
|
||||
("let f = fn(a, b)" "a lambda header")
|
||||
("let f = fn(a: i64, b) -> i64" "a typed lambda header")
|
||||
("let f = fn(g: Fn(i64) -> i64) -> Option(i64)" "one with a function type in it")
|
||||
("fn(a: i64) -> i64" "a typed lambda as a statement")))
|
||||
("let f = fn(a, b) =>" "a lambda header")
|
||||
("let f = fn(a: i64, b) -> i64 =>" "a typed lambda header")
|
||||
("let f = fn(g: Fn(i64) -> i64) -> Option(i64) =>" "one with a function type in it")
|
||||
("fn(a: i64) -> i64 =>" "a typed lambda as a statement")))
|
||||
(test-flan-fln--is (format "unless its value goes on under it: %s" (cadr c))
|
||||
(test-flan-fln--tabs (concat "fn f()\n " (car c) "\n|") 1) 4))
|
||||
(test-flan-fln--is "but not a typed lambda with its body on the line"
|
||||
(test-flan-fln--tabs "fn f()\n let f = fn(a: i64) -> i64 = a\n|" 1) 2)
|
||||
(test-flan-fln--tabs "fn f()\n let f = fn(a: i64) -> i64 => a\n|" 1) 2)
|
||||
(test-flan-fln--is "a one-line fn whose value is a match opens it"
|
||||
(test-flan-fln--tabs "fn f(x) = match x\n|" 1) 2)
|
||||
(test-flan-fln--is "no deeper after a one-line else"
|
||||
@ -849,6 +849,82 @@ of its line with AT-END."
|
||||
(buffer-string)
|
||||
"fn f() -> ()\n if a\n while x\n y\n\n"))
|
||||
|
||||
;;; A lambda's block inside brackets
|
||||
|
||||
(defconst test-flan-fln--lam
|
||||
"fn f(xs) -> i64
|
||||
let n = 1
|
||||
sort-by(xs, fn(a, b) =>
|
||||
let d = a - b
|
||||
d < n)
|
||||
map(xs, fn(x) =>
|
||||
g(x)
|
||||
x
|
||||
)
|
||||
h(n)
|
||||
")
|
||||
|
||||
(defun test-flan-fln--lam-at (needle fn &optional at-end)
|
||||
"What FN sends with point at NEEDLE in the lambda program, or at the end of
|
||||
its line with AT-END."
|
||||
(test-flan-fln--in (test-flan-fln--at test-flan-fln--lam needle)
|
||||
(when at-end (end-of-line))
|
||||
(let ((r (test-flan-fln--sending (funcall fn))))
|
||||
(and r (list (test-flan-fln--sent-code r) (plist-get r :pause))))))
|
||||
|
||||
(test-flan-fln--in (test-flan-fln--at test-flan-fln--lam "sort-by")
|
||||
(test-flan-fln--is "a call with a block lambda is one statement, block and closer too"
|
||||
(test-flan-fln--thing 'flan-fln-statement)
|
||||
"sort-by(xs, fn(a, b) =>\n let d = a - b\n d < n)")
|
||||
(test-flan-fln--is "its body is the lambda's block, less the call's closer"
|
||||
(test-flan-fln--thing 'flan-fln-body)
|
||||
"let d = a - b\n d < n"))
|
||||
(test-flan-fln--in (test-flan-fln--at test-flan-fln--lam "d < n")
|
||||
(test-flan-fln--is "a line of the block is a statement of its own, less the closer"
|
||||
(test-flan-fln--thing 'flan-fln-statement) "d < n"))
|
||||
(test-flan-fln--in (test-flan-fln--at test-flan-fln--lam " )")
|
||||
(test-flan-fln--is "a closer on a line of its own belongs to the call"
|
||||
(test-flan-fln--thing 'flan-fln-statement)
|
||||
"map(xs, fn(x) =>\n g(x)\n x\n )"))
|
||||
(test-flan-fln--in (test-flan-fln--at test-flan-fln--lam " x\n")
|
||||
(test-flan-fln--is "and not to the statement above it"
|
||||
(test-flan-fln--thing 'flan-fln-statement) "x"))
|
||||
(test-flan-fln--is "C-c C-e on the header sends the call, block and all"
|
||||
(car (test-flan-fln--lam-at "sort-by" #'flan-fln-eval-statement))
|
||||
"sort-by(xs, fn(a, b) =>\n let d = a - b\n d < n)")
|
||||
(test-flan-fln--is "and C-x C-e at the header's end"
|
||||
(car (test-flan-fln--lam-at "sort-by" #'flan-fln-eval-last t))
|
||||
"sort-by(xs, fn(a, b) =>\n let d = a - b\n d < n)")
|
||||
(test-flan-fln--is "C-x C-e at the end of the block's last line sends its statement"
|
||||
(car (test-flan-fln--lam-at "d < n" #'flan-fln-eval-last t))
|
||||
"d < n")
|
||||
(test-flan-fln--is "C-c C-e on a line of the block sends that statement"
|
||||
(car (test-flan-fln--lam-at "g(x)" #'flan-fln-eval-statement))
|
||||
"g(x)")
|
||||
(dolist (c '(("let d" (4 5) "a statement in the block, where the reader starts it")
|
||||
("g(x)" (7 5) "one in a block with its closer on a line of its own")
|
||||
("a, b)" (3 15) "the lambda, from its fn")
|
||||
("xs, fn(a" (3 3) "the call, from its name")))
|
||||
(test-flan-fln--is (format "C-u C-c C-c marks %s" (nth 2 c))
|
||||
(cadr (test-flan-fln--lam-at (car c) (lambda () (flan-fln-eval-defun '(4)))))
|
||||
(cadr c)))
|
||||
(test-flan-fln--is "TAB after a => inside brackets goes one level in"
|
||||
(test-flan-fln--tabs "fn f()\n sort-by(xs, fn(a, b) =>\n|" 1) 4)
|
||||
(test-flan-fln--is "and a line of the block stays in it"
|
||||
(test-flan-fln--tabs "fn f()\n sort-by(xs, fn(a, b) =>\n g(a)\n| a < b)" 1) 4)
|
||||
(test-flan-fln--is "under a header on a wrapped argument line, in from that line"
|
||||
(test-flan-fln--tabs "f(a,\n fn(b) =>\n|" 1) 4)
|
||||
(test-flan-fln--is "a closer on its own line goes to the call's column"
|
||||
(test-flan-fln--tabs "fn f()\n m(xs, fn(x) =>\n x\n|)" 1) 2)
|
||||
(test-flan-fln--is "inside a bracket in the block, under its first argument"
|
||||
(test-flan-fln--tabs "fn f()\n m(xs, fn(x) =>\n g(x,\n|y))" 1) 6)
|
||||
(test-flan-fln--in "fn f()\n m(xs, fn(x) => x)\n"
|
||||
(font-lock-ensure)
|
||||
(test-flan-fln--is "=> is a keyword"
|
||||
(save-excursion (goto-char (point-min)) (search-forward "=>")
|
||||
(get-text-property (match-beginning 0) 'face))
|
||||
'font-lock-keyword-face))
|
||||
|
||||
;;; Block editing
|
||||
|
||||
(test-flan-fln--in "fn f() -> ()\n if a\n |b()\n c()\n d()\n"
|
||||
|
||||
@ -117,10 +117,10 @@ Each item: the proposal, then the reason in one line.
|
||||
trailing block is allowed. Make it parser-driven, the way GDScript's
|
||||
`push_multiline` is (`gdscript_parser.cpp` 658-672, 3695-3770), not a paren
|
||||
counter in the lexer, or a block inside a call can't work.
|
||||
*Built as a depth counter instead: inside brackets a line break is always
|
||||
whitespace, so no block opens inside a call's parentheses (§3.1's blocks all
|
||||
open after the `)`; a lambda with a block body is a statement or a value,
|
||||
`let f = fn(x)` plus a block).*
|
||||
*Built as a depth counter instead: inside brackets a line break is
|
||||
whitespace, with one exception. A `=>` that ends a line inside brackets opens
|
||||
a lambda's block there, laid out as at the top level against the column its
|
||||
line starts at, and the block ends where the enclosing bracket closes.*
|
||||
- **Continuation outside brackets:** a line that starts with a spaced infix
|
||||
operator (`+`, `and`, `==`, …) continues the previous line; so does a line
|
||||
after one that ends in a spaced infix operator. (F# `LexFilter.fs` 360-380,
|
||||
@ -267,11 +267,28 @@ Each item: the proposal, then the reason in one line.
|
||||
- **Unit:** `()` as a statement reads `(do)`; in a type it is `()`. **Built**;
|
||||
inside an expression `()` stays `()`, and the printer writes a lone `()`
|
||||
statement as `(())`. A bare `()` in a one-line body slot (`fn f() -> () = ()`,
|
||||
`_ -> ()`, `fn() = ()`, `then ()`) is a statement too, and reads `(do)`.
|
||||
- **Lambda:** `fn(i, j) = i * 10 + j`, or `fn(i, j)` plus a block. **Built**;
|
||||
`_ -> ()`, `fn() => ()`, `then ()`) is a statement too, and reads `(do)`.
|
||||
- **Lambda:** `fn(i, j) => i * 10 + j`, or `fn(i, j) =>` plus a block. **Built**;
|
||||
its parameters are bare names, as `(fn [i j] …)` wants, with no `dyn`.
|
||||
`fn(…)` followed by anything else is the fallback call. A lambda may state
|
||||
its types, `fn(a: C, b) -> bool = …` or plus a block (section 3, item 7).
|
||||
its types, `fn(a: C, b) -> bool => …` or plus a block (section 3, item 7).
|
||||
`=>` is a lambda's only spelling: `fn(a) = x` and a lambda header with a
|
||||
block under it and no `=>` are refused, with the `=>` form as the fix.
|
||||
Named functions keep `=`. A block lambda may sit inside brackets:
|
||||
|
||||
```
|
||||
sort-by(slice(xs), fn(a, b) =>
|
||||
let d = a.n - b.n
|
||||
d < 0)
|
||||
```
|
||||
|
||||
The block ends where the brackets close, with the `)` at the end of its
|
||||
last line or on a line of its own at the call's column. It is the last
|
||||
thing in them: a comma after the block is refused (so a call takes one
|
||||
block lambda, as its last argument; name any other with `let`), as is a
|
||||
line inside the brackets at or left of the column the `=>` line starts at.
|
||||
Block lambdas nest, each block ending at its own brackets. `flan convert`
|
||||
writes a call whose last argument is a lambda with a block this way.
|
||||
|
||||
### Definitions
|
||||
|
||||
@ -376,16 +393,15 @@ Settled 2026-09-26, after writing programs by hand (`test/syntax/handwritten/`):
|
||||
is refused at its last line: the first `else` took `if b then y` as its
|
||||
value, and the chain is `if a then x else if b then y else z` on one line,
|
||||
or `elif b then y` on the second.
|
||||
7. **Typed lambdas.** `fn(a: C, b) -> R = body`, or plus a block, reads
|
||||
7. **Typed lambdas.** `fn(a: C, b) -> R => body`, or plus a block, reads
|
||||
`(the (Fn [C dyn] R) (fn [a b] body))`: the paren `fn` has no typed
|
||||
parameters, and `the` is how a value states its type, as in
|
||||
`let x: T = v`. An untyped parameter is `dyn`; the return type is
|
||||
required. Where a `CFn` of the same signature is wanted, the literal is
|
||||
that `CFn`; at a generic's `CFn($t) -> $t` parameter the literal is a
|
||||
`CFn` at its own types, which bind `$t` as any argument's would. The
|
||||
printer writes that form back as the typed lambda. A block lambda cannot
|
||||
sit inside a call's brackets; the refusal shows the typed `let` form to
|
||||
bind it with.
|
||||
printer writes that form back as the typed lambda. A typed block lambda
|
||||
sits inside brackets as an untyped one does.
|
||||
8. **`Dir.north` is the enum member `:north`**, in a value and in a match
|
||||
pattern, in both syntaxes. `:north` stays. A local named `Dir` shadows the
|
||||
enum as a local shadows any global: `Dir.north` is then its field.
|
||||
@ -453,7 +469,7 @@ Each step lands on its own, with `dune test --root .` green.
|
||||
(`ast.ml:491-492`).
|
||||
|
||||
**Built** (`emacs/flan-fln-mode.el`; keys and objects in `emacs/MANUAL.md`,
|
||||
"Indented files"). A line ending in `=` or `fn(…)` also opens a block for
|
||||
"Indented files"). A line ending in `=` or `=>` also opens a block for
|
||||
TAB, and a body is its statement's own block, up to its first clause.
|
||||
6. **Return-type inference** in `Check`, with the recursion refusal and the
|
||||
stale-caller cause. This is independent of steps 1-5 once the marker exists.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user