.fln lambdas are written with =>, and one with a block can sit last inside a call's brackets
This commit is contained in:
commit
06eaf738b9
9
TODO.org
9
TODO.org
@ -643,11 +643,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 Indices separate like vector elements
|
||||
Decided 2026-09-26: ~grid[r c]~ and ~grid[(r + 1) (c - 1)]~ read like a vector's
|
||||
|
||||
@ -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`, `= loop i = 0`, 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`, `= loop i = 0`, 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.
|
||||
@ -234,15 +235,63 @@ 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.
|
||||
Each call scans the lines from the open bracket to POS, so walking a long
|
||||
bracketed literal line by line costs the square of its length."
|
||||
(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."
|
||||
@ -255,16 +304,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))
|
||||
|
||||
@ -295,10 +349,18 @@ depth, outside strings and comments, or nil."
|
||||
(flan-fln--joined-end l))))
|
||||
(and m (car m))))
|
||||
|
||||
(defun flan-fln--value-end (l)
|
||||
"Where a value written on the joined line L ends: at the line's code end,
|
||||
or, when the line ends in `=>', at the end of the lambda's block under it."
|
||||
(let ((last (flan-fln--logical-end l)))
|
||||
(if (flan-fln--ends-in-arrow-p last)
|
||||
(cdr (flan-fln--span l (flan-fln--statement-last l t)))
|
||||
(flan-fln--code-end last))))
|
||||
|
||||
(defun flan-fln--clause-value (l)
|
||||
"Bounds of the value on the clause line L itself: `x' of `else x' or of
|
||||
`elif c then x'; nil when the clause's value is its block."
|
||||
(let ((end (flan-fln--joined-end l))
|
||||
(let ((end (flan-fln--value-end l))
|
||||
(then (flan-fln--then l)))
|
||||
(save-excursion
|
||||
(goto-char (flan-fln--first-char l))
|
||||
@ -319,18 +381,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:
|
||||
@ -357,6 +424,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)
|
||||
@ -366,9 +435,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."
|
||||
@ -698,7 +784,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))
|
||||
@ -770,7 +860,7 @@ pattern names something, so the value cannot be evaluated alone."
|
||||
(let* ((pat (string-trim (buffer-substring-no-properties start arrow)))
|
||||
(vbeg (save-excursion (goto-char (+ arrow 2))
|
||||
(skip-chars-forward " \t") (point)))
|
||||
(value (if (< vbeg last) (cons vbeg last)
|
||||
(value (if (< vbeg last) (cons vbeg (flan-fln--value-end l))
|
||||
(flan-fln--body-bounds l))))
|
||||
(and value
|
||||
(list :arrow arrow :value value
|
||||
@ -1175,15 +1265,27 @@ 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 `let colors =' at the top level with the value as a block,
|
||||
;; which the author's list leaves out and the reader reads.
|
||||
(looking-back "[ \t]=" bol))))))
|
||||
|
||||
(defun flan-fln--outside-block (p pos)
|
||||
"P, or when P's line is in a lambda's block inside brackets that POS is not
|
||||
inside, the first line of the statement that block's `=>' is in: a block
|
||||
whose brackets have closed is no block POS can join."
|
||||
(let ((a (flan-fln--lambda-arrow p)))
|
||||
(if (and a (not (memq (nth 1 (save-excursion (syntax-ppss (flan-fln--bol p))))
|
||||
(nth 9 (save-excursion (syntax-ppss (flan-fln--bol pos)))))))
|
||||
(flan-fln--outside-block (flan-fln--logical-start a) pos)
|
||||
p)))
|
||||
|
||||
(defun flan-fln--stack (pos)
|
||||
"The open block columns above POS's line, deepest first, as (COL . LINE)."
|
||||
(let ((p (flan-fln--prev-code pos)) out (min most-positive-fixnum))
|
||||
(while p
|
||||
(setq p (flan-fln--logical-start p))
|
||||
(setq p (flan-fln--outside-block (flan-fln--logical-start p) pos))
|
||||
(let ((i (flan-fln--indent-at p)))
|
||||
(when (< i min) (push (cons i p) out) (setq min i)))
|
||||
(setq p (and (> min 0) (flan-fln--prev-code p))))
|
||||
@ -1194,11 +1296,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."
|
||||
@ -1243,19 +1362,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.
|
||||
@ -1301,11 +1421,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)
|
||||
@ -1323,7 +1445,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))))))))
|
||||
@ -1630,6 +1752,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,22 @@ 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
|
||||
let a = 1
|
||||
let b = app(k, fn(x) =>
|
||||
let y = x + a
|
||||
y)
|
||||
let c = fn(z: i64) -> i64 =>
|
||||
z * 2
|
||||
c(b)
|
||||
|
||||
fn rs() -> i64
|
||||
restart-case
|
||||
3
|
||||
@ -141,6 +152,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
|
||||
@ -257,6 +271,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")
|
||||
("struct Oops" "(oops-code)" "7")
|
||||
@ -347,6 +362,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")
|
||||
@ -366,6 +391,11 @@ 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")
|
||||
("x) =>" "a block lambda in a call, from its fn")
|
||||
("let y = x" "a statement in a block lambda's block")
|
||||
("y)" "the block's last statement, less the call's closer")
|
||||
("let b = app" "a merged let's call with a block lambda, at its value")
|
||||
("let c = fn" "a merged let's block lambda, at its value")
|
||||
("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
|
||||
@ -876,16 +876,16 @@ defconst(k, 3)
|
||||
("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")
|
||||
("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")
|
||||
("let r = loop i = 0, acc = 1" "a let's loop")
|
||||
("loop i = 0, acc = 1" "a loop")
|
||||
("fn(a: i64) -> i64" "a typed lambda as a statement")))
|
||||
("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 header word being assigned opens nothing"
|
||||
(test-flan-fln--tabs "fn f()\n for = 1\n|" 1) 2)
|
||||
(test-flan-fln--in "fn f()\n handler-case\n g()\n on E(c)\n h(c)\n on = 2\n data += 1\n"
|
||||
@ -1029,6 +1029,108 @@ defconst(k, 3)
|
||||
(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 "after the block's brackets close, its column is no longer offered"
|
||||
(list (test-flan-fln--tabs "fn f()\n app(k, fn(x) =>\n x)\n|g()" 1)
|
||||
(test-flan-fln--tabs "fn f()\n app(k, fn(x) =>\n x)\n|g()" 2)
|
||||
(test-flan-fln--tabs "fn f()\n app(k, fn(x) =>\n x)\n|g()" 3))
|
||||
'(0 2 0))
|
||||
(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))
|
||||
|
||||
(defconst test-flan-fln--lam-arm
|
||||
"fn f(n: i64) -> i64
|
||||
match n
|
||||
1 -> app(1, fn(a) =>
|
||||
a * 2)
|
||||
_ -> 0
|
||||
")
|
||||
(test-flan-fln--is "C-x C-e at the end of an arm whose value ends in => sends the value, block and all"
|
||||
(test-flan-fln--in (test-flan-fln--at test-flan-fln--lam-arm "1 ->")
|
||||
(end-of-line)
|
||||
(test-flan-fln--sent-code (test-flan-fln--sending (flan-fln-eval-last))))
|
||||
"app(1, fn(a) =>\n a * 2)")
|
||||
(test-flan-fln--is "and C-u C-c C-c on its pattern stops at the value"
|
||||
(test-flan-fln--in (test-flan-fln--at test-flan-fln--lam-arm "1 ->")
|
||||
(plist-get (test-flan-fln--sending (flan-fln-eval-defun '(4))) :pause))
|
||||
'(3 10))
|
||||
(test-flan-fln--is "an else whose value ends in => takes the block too"
|
||||
(test-flan-fln--in "fn f(c)\n if c\n g()\n |else app(1, fn(a) =>\n a)\n"
|
||||
(test-flan-fln--text (flan-fln--clause-value (line-beginning-position))))
|
||||
"app(1, fn(a) =>\n a)")
|
||||
|
||||
;;; Block editing
|
||||
|
||||
(test-flan-fln--in "fn f() -> ()\n if a\n |b()\n c()\n d()\n"
|
||||
|
||||
@ -6500,7 +6500,7 @@ and check_fn ctx ~want ?gen loc (params : string list) body =
|
||||
"nothing here says what this fn's parameters are — an fn takes \
|
||||
its types from the position it is written in. Pass it where a \
|
||||
Fn(T, ...) -> R is expected, or name the type where it is \
|
||||
bound: let f: Fn(T, ...) -> R = fn(...)"
|
||||
bound: let f: Fn(T, ...) -> R = fn(...) => ..."
|
||||
else
|
||||
fail loc
|
||||
"nothing here says what this fn's parameters are — an fn takes \
|
||||
@ -8653,7 +8653,7 @@ and array_build ctx loc ns elem ~pre ~element =
|
||||
[n T] the literal is, since an array literal is never a slice. *)
|
||||
and check_the ctx ~want loc (t : Ast.texpr) (v : Ast.expr) =
|
||||
let ty = resolve ctx.env t in
|
||||
(* A typed .fln lambda, [fn(c: C) -> bool = ...], reads as [(the (Fn [C]
|
||||
(* A typed .fln lambda, [fn(c: C) -> bool => ...], reads as [(the (Fn [C]
|
||||
bool) (fn ...))]; where a CFn of the same signature is wanted, the
|
||||
literal is that CFn, as an untyped one would be. *)
|
||||
let ty =
|
||||
|
||||
@ -74,6 +74,11 @@ let is_sym s (f : Form.t) = match f.v with Form.Sym x -> x = s | _ -> false
|
||||
[let] there takes in what follows it and no one-argument [and] is dropped. *)
|
||||
let quasi = ref 0
|
||||
|
||||
(* While [hole_lines] looks for where a lambda sits in a line, the lambda is
|
||||
printed as [hole_sym], at a lambda's level. *)
|
||||
let hole = ref false
|
||||
let hole_sym = "\003lambda\003"
|
||||
|
||||
let in_quasi (f : Form.t) k =
|
||||
match f.v with
|
||||
| Form.List ({ v = Form.Sym "quasiquote"; _ } :: _) ->
|
||||
@ -296,6 +301,7 @@ let flatten (f : Form.t) (rest : Form.t list) =
|
||||
one-line [if] or a lambda. *)
|
||||
let rec expr (f : Form.t) : string * int =
|
||||
match f.v with
|
||||
| Form.Sym s when !hole && s = hole_sym -> (s, 0)
|
||||
| Form.Sym s -> sym f s
|
||||
| Form.Kw k ->
|
||||
if kw_ok k then (":" ^ k, 10) else unprintable f "a keyword with no spelling"
|
||||
@ -420,10 +426,10 @@ and list f h args =
|
||||
(s ^ fst (expr m), 9)
|
||||
| Form.Sym "the", _ when (match typed_lambda f with Some (_, [ _ ]) -> true | _ -> false) ->
|
||||
(match typed_lambda f with
|
||||
| Some (head, [ body ]) -> (head ^ " = " ^ unit_text body, 0)
|
||||
| Some (head, [ body ]) -> (head ^ " => " ^ unit_text body, 0)
|
||||
| _ -> assert false)
|
||||
| Form.Sym "fn", [ { v = Form.Vec ps; _ }; body ] when List.for_all sym_param ps ->
|
||||
("fn(" ^ commas ps ^ ") = " ^ unit_text body, 0)
|
||||
("fn(" ^ commas ps ^ ") => " ^ unit_text body, 0)
|
||||
| Form.Sym "if", [ c; a; b ] ->
|
||||
("if " ^ at 1 c ^ " then " ^ inline_text ~lvl:1 a ^ " else " ^ inline_text b, 0)
|
||||
| _ -> call ()
|
||||
@ -718,9 +724,12 @@ and plain n (f : Form.t) : string list =
|
||||
| _ -> head_text h ^ "(" ^ commas fixed ^ "):"
|
||||
in
|
||||
[ ind n ^ guard opener ] @ block ~seq (n + 2) rest
|
||||
| _ when n + String.length text > width && fst (expr f) = text ->
|
||||
wrapped n "" f
|
||||
| _ -> one)
|
||||
| _ ->
|
||||
match hole_lines ~guarded:true n "" f with
|
||||
| Some ls -> ls
|
||||
| None ->
|
||||
if n + String.length text > width && fst (expr f) = text then wrapped n "" f
|
||||
else one)
|
||||
| _ -> one
|
||||
|
||||
(* A call too long for its line, broken after commas inside its
|
||||
@ -776,6 +785,131 @@ and wrapped n prefix (f : Form.t) =
|
||||
go (ind n ^ open_) [] ts
|
||||
| _ -> [ ind n ^ prefix ^ at 0 f ]
|
||||
|
||||
(* A lambda as its header, [fn(a, b)] or [fn(a: C) -> R], and its body. *)
|
||||
and lambda_parts (f : Form.t) =
|
||||
match typed_lambda f with
|
||||
| Some _ as l -> l
|
||||
| None ->
|
||||
match f.v with
|
||||
| Form.List ({ v = Form.Sym "fn"; _ } :: { v = Form.Vec ps; _ } :: (_ :: _ as body))
|
||||
when List.for_all sym_param ps ->
|
||||
Some ("fn(" ^ commas ps ^ ")", body)
|
||||
| _ -> None
|
||||
|
||||
(* A lambda body that reads better as a block under [=>] than on the line:
|
||||
several statements, or one that is a statement. *)
|
||||
and block_body (body : Form.t list) =
|
||||
match body with
|
||||
| [ { v = Form.List ({ v = Form.Sym h; _ } :: _); _ } ] ->
|
||||
List.mem h sugar_heads && h <> "if" && h <> "update"
|
||||
| [ _ ] -> false
|
||||
| _ -> true
|
||||
|
||||
(* A lambda that takes a block: one whose body does, or has a comment in
|
||||
it, or holds a lambda that takes one. *)
|
||||
and wants_block (l : Form.t) body =
|
||||
let rec holds (x : Form.t) =
|
||||
match lambda_parts x with
|
||||
| Some (_, b) -> wants_block x b
|
||||
| None ->
|
||||
match x.v with
|
||||
| Form.List ({ v = Form.Sym ("quote" | "quasiquote"); _ } :: _) -> false
|
||||
| Form.List xs | Form.Vec xs | Form.Map xs -> List.exists holds xs
|
||||
| _ -> false
|
||||
in
|
||||
block_body body || !inside l || List.exists holds body
|
||||
|
||||
(* A block under [=>]: a body that is one [do] is its statements, written
|
||||
straight under the header rather than in a [do:] of their own. *)
|
||||
and lambda_block n body =
|
||||
match body with
|
||||
| [ { Form.v = Form.List ({ v = Form.Sym "do"; _ } :: (_ :: _ :: _ as ss)); _ } ] -> block (n + 2) ss
|
||||
| _ -> block (n + 2) body
|
||||
|
||||
(* A line holding a lambda that takes its block there: [prefix] and the
|
||||
line's text up to the lambda, [fn(x) =>], the block under it, and what
|
||||
followed the lambda on the line at the end of the block's last line. The
|
||||
reader ends a block inside brackets where they close, so the lambda must
|
||||
be the last thing in its brackets: what follows it starts with a closer.
|
||||
Each lambda in [f], outside the others, is tried in turn, a lambda that
|
||||
wants a block (several statements, a statement, a comment inside) or any
|
||||
when the line is too long; its place is found by printing the line with
|
||||
a placeholder where it stands. [guarded]: the text is a statement's. *)
|
||||
and hole_lines ?(guarded = false) n prefix (f : Form.t) =
|
||||
let rec cands (x : Form.t) =
|
||||
if lambda_parts x <> None then [ x ]
|
||||
else
|
||||
match x.v with
|
||||
| Form.List ({ v = Form.Sym ("quote" | "quasiquote"); _ } :: _) -> []
|
||||
| Form.List xs | Form.Vec xs | Form.Map xs -> List.concat_map cands xs
|
||||
| _ -> []
|
||||
in
|
||||
let inner =
|
||||
match f.v with
|
||||
| Form.List xs | Form.Vec xs | Form.Map xs -> List.concat_map cands xs
|
||||
| _ -> []
|
||||
in
|
||||
if inner = [] then None
|
||||
else
|
||||
let long = lazy (n + String.length prefix + String.length (fst (expr f)) > width) in
|
||||
let rec subst (l : Form.t) (x : Form.t) =
|
||||
if x == l then Form.make (Form.Sym hole_sym) l.loc
|
||||
else
|
||||
match x.v with
|
||||
| Form.List xs -> { x with v = Form.List (List.map (subst l) xs) }
|
||||
| Form.Vec xs -> { x with v = Form.Vec (List.map (subst l) xs) }
|
||||
| Form.Map xs -> { x with v = Form.Map (List.map (subst l) xs) }
|
||||
| _ -> x
|
||||
in
|
||||
let find t =
|
||||
let k = String.length hole_sym in
|
||||
let rec go i =
|
||||
if i + k > String.length t then None
|
||||
else if String.sub t i k = hole_sym then Some i
|
||||
else go (i + 1)
|
||||
in
|
||||
go 0
|
||||
in
|
||||
let rec try_ = function
|
||||
| [] -> None
|
||||
| (l : Form.t) :: rest ->
|
||||
let head, body = Option.get (lambda_parts l) in
|
||||
if not (wants_block l body || Lazy.force long) then try_ rest
|
||||
else begin
|
||||
hole := true;
|
||||
let t =
|
||||
Fun.protect ~finally:(fun () -> hole := false)
|
||||
(fun () -> fst (expr (subst l f)))
|
||||
in
|
||||
let t = if guarded then guard t else t in
|
||||
match find t with
|
||||
| Some i
|
||||
when (let j = i + String.length hole_sym in
|
||||
j < String.length t && (t.[j] = ')' || t.[j] = ']' || t.[j] = '}')) ->
|
||||
let j = i + String.length hole_sym in
|
||||
let post = String.sub t j (String.length t - j) in
|
||||
let ls = lambda_block n body in
|
||||
let ls =
|
||||
match List.rev ls with
|
||||
| last :: before -> List.rev ((last ^ post) :: before)
|
||||
| [] -> ls
|
||||
in
|
||||
Some ((ind n ^ prefix ^ String.sub t 0 i ^ head ^ " =>") :: ls)
|
||||
| _ -> try_ rest
|
||||
end
|
||||
in
|
||||
try_ inner
|
||||
|
||||
(* [prefix = fn(a) =>] or [prefix = f(x, fn(a) =>] and a lambda's block,
|
||||
when the value is a lambda that takes one or ends a bracket with one. *)
|
||||
and lambda_value n prefix (v : Form.t) =
|
||||
match lambda_parts v with
|
||||
| Some (head, body)
|
||||
when wants_block v body
|
||||
|| n + String.length prefix + 3 + String.length (fst (expr v)) > width ->
|
||||
Some ((ind n ^ prefix ^ " = " ^ head ^ " =>") :: lambda_block n body)
|
||||
| _ -> hole_lines n (prefix ^ " = ") v
|
||||
|
||||
(* [prefix = v], or [prefix =] and the value as an indented block when it is
|
||||
too long for the line. *)
|
||||
and value_lines n prefix (v : Form.t) =
|
||||
@ -789,15 +923,13 @@ and value_lines n prefix (v : Form.t) =
|
||||
else if loop_head v <> None then
|
||||
let head, body = Option.get (loop_head v) in
|
||||
[ ind n ^ prefix ^ " = " ^ head ] @ block (n + 2) body
|
||||
else if n + String.length inline <= width then [ ind n ^ inline ]
|
||||
else
|
||||
match lambda_value n prefix v with
|
||||
| Some ls -> ls
|
||||
| None ->
|
||||
if n + String.length inline <= width then [ ind n ^ inline ]
|
||||
else
|
||||
match v.v with
|
||||
| _ when typed_lambda v <> None ->
|
||||
let head, body = Option.get (typed_lambda v) in
|
||||
[ ind n ^ prefix ^ " = " ^ head ] @ block (n + 2) body
|
||||
| Form.List ({ v = Form.Sym "fn"; _ } :: { v = Form.Vec ps; _ } :: (_ :: _ as body))
|
||||
when List.for_all sym_param ps ->
|
||||
[ ind n ^ prefix ^ " = fn(" ^ commas ps ^ ")" ] @ block (n + 2) body
|
||||
| Form.List ({ v = Form.Sym h; _ } :: _)
|
||||
when not (List.mem h sugar_heads || h = "fn" || h = "if") ->
|
||||
wrapped n (prefix ^ " = ") v
|
||||
@ -840,7 +972,8 @@ and sugar n (f : Form.t) : string list option =
|
||||
Some [ i ^ guard (inline_text f) ]
|
||||
| Form.List [ { v = Form.Sym "set"; _ }; t; v ] ->
|
||||
let line = i ^ guard (assign_text t v) in
|
||||
if String.length line <= width then Some [ line ]
|
||||
if String.length line <= width && lambda_value n (guard (at 9 t)) v = None
|
||||
then Some [ line ]
|
||||
else Some (value_lines n (guard (at 9 t)) v)
|
||||
| Form.List [ { v = Form.Sym "if"; _ }; c; a; b ] ->
|
||||
let simple (x : Form.t) =
|
||||
@ -912,7 +1045,10 @@ and sugar n (f : Form.t) : string list option =
|
||||
((i ^ "for " ^ lbl ^ v ^ " in range(" ^ commas bs ^ ")") :: block (n + 2) body)
|
||||
| _ -> None)
|
||||
| Form.List [ { v = Form.Sym "return"; _ } ] -> Some [ i ^ "return" ]
|
||||
| Form.List [ { v = Form.Sym "return"; _ }; v ] -> Some [ i ^ "return " ^ at 0 v ]
|
||||
| Form.List [ { v = Form.Sym "return"; _ }; v ] ->
|
||||
(match hole_lines n "return " v with
|
||||
| Some ls -> Some ls
|
||||
| None -> Some [ i ^ "return " ^ at 0 v ])
|
||||
| Form.List [ { v = Form.Sym (("break" | "continue") as w); _ } ] -> Some [ i ^ w ]
|
||||
| Form.List [ { v = Form.Sym (("break" | "continue") as w); _ }; { v = Form.Kw k; _ } ]
|
||||
when kw_ok k ->
|
||||
@ -974,9 +1110,9 @@ and sugar n (f : Form.t) : string list option =
|
||||
@ List.concat_map Option.get cs)
|
||||
| Form.List [ { v = Form.Sym "quasiquote"; _ }; x ] ->
|
||||
Some ((i ^ "quote") :: slot (n + 2) x)
|
||||
| Form.List ({ v = Form.Sym "fn"; _ } :: { v = Form.Vec ps; _ } :: (_ :: _ :: _ as body))
|
||||
when List.for_all sym_param ps ->
|
||||
Some ((guard (i ^ "fn(" ^ commas ps ^ ")")) :: block (n + 2) body)
|
||||
| _ when (match lambda_parts f with Some (_, body) -> wants_block f body | None -> false) ->
|
||||
let head, body = Option.get (lambda_parts f) in
|
||||
Some ((i ^ head ^ " =>") :: lambda_block n body)
|
||||
| Form.List ({ v = Form.Sym (("defn" | "defn-") as d); _ } :: { v = Form.Sym name; _ }
|
||||
:: { v = Form.Vec ps; _ } :: ret :: body)
|
||||
when def_name name ->
|
||||
@ -1006,7 +1142,8 @@ and sugar n (f : Form.t) : string list option =
|
||||
| Form.List (({ v = Form.Sym h; _ } as hf) :: args) ->
|
||||
(* A call that takes a block is a statement, not a value. *)
|
||||
not (List.mem h sugar_heads) && body_split hf args = None
|
||||
| _ -> true)
|
||||
&& lambda_value (n + 2) "" x = None
|
||||
| _ -> lambda_value (n + 2) "" x = None)
|
||||
&& String.length head + 3 + String.length (at 0 x) <= width
|
||||
&& not (!inside f) ->
|
||||
Some [ head ^ " = " ^ unit_text x ]
|
||||
|
||||
@ -261,35 +261,82 @@ let point (l : Loc.t) = { l with Loc.line = l.Loc.eline; col = l.Loc.ecol }
|
||||
|
||||
(* NEWLINE, INDENT and DEDENT, at bracket depth zero only: inside ( [ { a
|
||||
line break is whitespace. A line continues the one before it when either
|
||||
side of the break is a spaced binary operator (spec §2 "Continuation"). *)
|
||||
side of the break is a spaced binary operator (spec §2 "Continuation").
|
||||
|
||||
The one exception is a lambda's block. A [=>] that ends its line inside
|
||||
brackets opens a block there: the lines under it are laid out as they
|
||||
would be at depth zero, against a base of their own (the column the [=>]
|
||||
line starts at), until the bracket around the lambda closes. That closer
|
||||
ends the block, whether it ends the block's last line or has a line of
|
||||
its own. The block is the last thing in its brackets: a comma after it,
|
||||
or a line back at the header's column, is refused. *)
|
||||
type frame = {
|
||||
f_base : int;
|
||||
f_stack : int list;
|
||||
f_opens : token list; (* the brackets open around the lambda *)
|
||||
f_arrow : token; (* the [=>] that opened the block *)
|
||||
}
|
||||
|
||||
let lambda_not_last (fr : frame) (t : token) =
|
||||
failk "lambda-block-last" t.loc
|
||||
"%s follows the block of the lambda on line %d, inside the same \
|
||||
brackets. A lambda with a block is the last thing in its brackets, and \
|
||||
its block ends where they close. Name the lambda with a let first and \
|
||||
pass the name:\n\n\
|
||||
\ let f = fn(a) =>\n ...\n g(f, x)"
|
||||
(if t.tok = COMMA then "a comma" else show t.tok) fr.f_arrow.loc.Loc.line
|
||||
|
||||
let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token array =
|
||||
let arr = Array.of_list toks in
|
||||
let n = Array.length arr in
|
||||
(* A snippet from the editor starts wherever it was written, and its first
|
||||
line is its base: a later line may not go left of it. *)
|
||||
let base = if snippet && n > 0 then arr.(0).loc.Loc.col else base in
|
||||
line is its base: a later line may not go left of it. One cut from the
|
||||
middle of a line (see [indent]) has that line's start as its base, so a
|
||||
block under the line, a [let]'s [match] arms or a lambda's, reads as it
|
||||
does in the file. *)
|
||||
let base =
|
||||
ref (if snippet && n > 0 then
|
||||
match indent with
|
||||
| Some c -> min c arr.(0).loc.Loc.col
|
||||
| None -> arr.(0).loc.Loc.col
|
||||
else base)
|
||||
in
|
||||
let out = ref [] in
|
||||
let add tok loc = out := { tok; loc; sp = true } :: !out in
|
||||
let stack = ref [ base ] in
|
||||
let stack = ref [ !base ] in
|
||||
(* [indent] is the column of the statement a snippet was cut out of, when
|
||||
the snippet starts after that statement's first word (an elif's
|
||||
condition, an arm's value). Its first joined line continues as it does
|
||||
in the file: deeper than the statement, not than the cut. *)
|
||||
let first_line = ref true in
|
||||
let depth = ref 0 in
|
||||
(* The brackets open in the current layout, innermost first. A lambda's
|
||||
block starts with none, and [frames] holds what it interrupted. *)
|
||||
let opens = ref [] in
|
||||
let frames = ref [] in
|
||||
let binop t = match t.tok with NAME s -> is_binop s | _ -> false in
|
||||
let closer t = match t.tok with RP | RB | RC -> true | _ -> false in
|
||||
(* The column [i]'s line starts at. *)
|
||||
let line_col i =
|
||||
let rec go j =
|
||||
if j > 0 && arr.(j - 1).loc.Loc.eline = arr.(i).loc.Loc.line then go (j - 1) else j
|
||||
in
|
||||
arr.(go i).loc.Loc.col
|
||||
in
|
||||
for i = 0 to n - 1 do
|
||||
let t = arr.(i) in
|
||||
(if i = 0 then begin
|
||||
if t.loc.Loc.col <> base then
|
||||
if t.loc.Loc.col <> !base && indent = None then
|
||||
failk "unexpected-indent" t.loc
|
||||
"the first line starts at column %d, and a file's top-level lines \
|
||||
start at column %d. Remove the indentation"
|
||||
t.loc.Loc.col base
|
||||
t.loc.Loc.col !base
|
||||
end
|
||||
else
|
||||
let p = arr.(i - 1) in
|
||||
if !depth = 0 && t.loc.Loc.line > p.loc.Loc.eline then begin
|
||||
(* The closer that ends a lambda's block takes the block's end with
|
||||
it, below: the line break before it is nothing. *)
|
||||
let ends_block = !frames <> [] && !opens = [] && closer t in
|
||||
if !opens = [] && t.loc.Loc.line > p.loc.Loc.eline && not ends_block then begin
|
||||
let spaced_after =
|
||||
i + 1 < n && arr.(i + 1).loc.Loc.line = t.loc.Loc.line
|
||||
&& arr.(i + 1).sp
|
||||
@ -324,15 +371,33 @@ let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token ar
|
||||
if not continues then begin
|
||||
first_line := false;
|
||||
let at = point p.loc in
|
||||
add NEWLINE at;
|
||||
let col = t.loc.Loc.col in
|
||||
(* Inside a lambda's brackets, a line at or left of the line its
|
||||
header is on would be a statement beside the lambda. *)
|
||||
(match !frames with
|
||||
(* Left of the block's own column, after the block: the next
|
||||
element of the brackets, which the block must end. *)
|
||||
| fr :: _ when List.length !stack > 1
|
||||
&& col < List.nth !stack (List.length !stack - 2) ->
|
||||
lambda_not_last fr t
|
||||
| fr :: _ when col <= !base ->
|
||||
if t.tok = COMMA then lambda_not_last fr t
|
||||
else
|
||||
failk "lambda-block-left" t.loc
|
||||
"this line starts at column %d and is still inside the \
|
||||
brackets of the lambda on line %d, whose block is indented \
|
||||
past column %d. Indent it into the block, or close the \
|
||||
brackets at the end of the block's last line"
|
||||
col fr.f_arrow.loc.Loc.line !base
|
||||
| _ -> ());
|
||||
add NEWLINE at;
|
||||
let top = List.hd !stack in
|
||||
if col > top then begin
|
||||
stack := col :: !stack;
|
||||
add INDENT at
|
||||
end
|
||||
else if col < top then begin
|
||||
if col < base then
|
||||
if col < !base then
|
||||
failk "dedent" t.loc
|
||||
"%s"
|
||||
(if snippet then
|
||||
@ -342,11 +407,11 @@ let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token ar
|
||||
edge, and no later line can go left of it: send the \
|
||||
enclosing form, or line this up at column %d or right \
|
||||
of it"
|
||||
col base base
|
||||
col !base !base
|
||||
else
|
||||
Printf.sprintf
|
||||
"this line starts at column %d, left of the top level at \
|
||||
column %d" col base);
|
||||
column %d" col !base);
|
||||
let closed = ref top in
|
||||
let rec pop () =
|
||||
match !stack with
|
||||
@ -368,12 +433,46 @@ let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token ar
|
||||
end
|
||||
end
|
||||
end);
|
||||
(match !frames with
|
||||
(* A comma at the top of a lambda's block, on one of the block's lines. *)
|
||||
| fr :: _ when !opens = [] && t.tok = COMMA -> lambda_not_last fr t
|
||||
(* The closer of the brackets a lambda's block is in: the block ends. *)
|
||||
| fr :: rest when !opens = [] && closer t ->
|
||||
let at = point arr.(i - 1).loc in
|
||||
add NEWLINE at;
|
||||
List.iter (fun _ -> add DEDENT at) (List.tl !stack);
|
||||
base := fr.f_base;
|
||||
stack := fr.f_stack;
|
||||
opens := fr.f_opens;
|
||||
frames := rest
|
||||
| _ -> ());
|
||||
out := t :: !out;
|
||||
(match t.tok with
|
||||
| LP | LB | LC -> incr depth
|
||||
| RP | RB | RC -> if !depth > 0 then decr depth
|
||||
| LP | LB | LC -> opens := t :: !opens
|
||||
| RP | RB | RC -> (match !opens with _ :: r -> opens := r | [] -> ())
|
||||
| NAME "=>" when !opens <> [] && i + 1 < n
|
||||
&& arr.(i + 1).loc.Loc.line > t.loc.Loc.eline ->
|
||||
frames := { f_base = !base; f_stack = !stack; f_opens = !opens; f_arrow = t }
|
||||
:: !frames;
|
||||
(* A snippet cut from the middle of a line starts where its line
|
||||
does in the file, [indent], not where the cut does. *)
|
||||
base :=
|
||||
(match indent with
|
||||
| Some c when t.loc.Loc.line = arr.(0).loc.Loc.line -> min c (line_col i)
|
||||
| _ -> line_col i);
|
||||
stack := [ !base ];
|
||||
opens := []
|
||||
| _ -> ())
|
||||
done;
|
||||
(match !frames with
|
||||
| fr :: _ ->
|
||||
let o = match fr.f_opens with o :: _ -> o | [] -> fr.f_arrow in
|
||||
failk "unclosed" o.loc
|
||||
~notes:[ Loc.note (point arr.(n - 1).loc) "the input ends here, still inside it" ]
|
||||
"unclosed %s: the block of the lambda on line %d ends where this \
|
||||
bracket closes"
|
||||
(show o.tok) fr.f_arrow.loc.Loc.line
|
||||
| [] -> ());
|
||||
(if n > 0 then
|
||||
let at = point arr.(n - 1).loc in
|
||||
add NEWLINE at;
|
||||
@ -384,11 +483,17 @@ let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token ar
|
||||
|
||||
(* ── Parsing ───────────────────────────────────────────────────────── *)
|
||||
|
||||
type p = { toks : token array; mutable i : int }
|
||||
(* [closed] is where a lambda's block that ended its statement stopped:
|
||||
the block took the line's end with it, so a check for that end passes
|
||||
there. *)
|
||||
type p = { toks : token array; mutable i : int; mutable closed : int }
|
||||
|
||||
(* Set below [params] and [ty], which the expression parser comes before. *)
|
||||
let typed_fn_expr : (p -> Form.t * int) ref = ref (fun _ -> assert false)
|
||||
|
||||
(* A block's statements, for a lambda's; set once the statement parser is. *)
|
||||
let block_of : (p -> Form.t list) ref = ref (fun _ -> assert false)
|
||||
|
||||
let peek p = p.toks.(p.i)
|
||||
let peek_at p k = p.toks.(min (p.i + k) (Array.length p.toks - 1))
|
||||
let advance p =
|
||||
@ -487,6 +592,7 @@ let expect_name p s ~what =
|
||||
|
||||
(* The end of a line that is not followed by a block. *)
|
||||
let expect_eol p ~after =
|
||||
if p.i = p.closed then () else
|
||||
match (peek p).tok with
|
||||
| NEWLINE ->
|
||||
ignore (advance p);
|
||||
@ -637,7 +743,7 @@ and postfix p =
|
||||
loop (mk p l0 (Form.List (f :: args)), 9)
|
||||
| LB ->
|
||||
ignore (advance p);
|
||||
let idx = items p RB t.loc ~what:"indices" in
|
||||
let idx = items p RB t.loc ~what:"indices" ~head:(text_of f) in
|
||||
loop (mk p l0 (Form.List (sym t.loc "at" :: f :: idx)), 9)
|
||||
| NAME s when String.length s > 1 && s.[0] = '.' ->
|
||||
ignore (advance p);
|
||||
@ -785,36 +891,83 @@ and inline_stmt p : Form.t =
|
||||
mk p t.loc (compound eq.loc (List.assoc op assign_ops) e v (span p e.loc))
|
||||
| _ -> unit_slot p i0 t e
|
||||
|
||||
(* [fn(a, b) = body] is a lambda; [fn(...)] followed by anything else is the
|
||||
(* [fn(a, b) => body] is a lambda; [fn(...)] followed by anything else is the
|
||||
fallback call spelling of [(fn ...)]. *)
|
||||
and fn_expr p =
|
||||
if typed_lambda p then !typed_fn_expr p else
|
||||
let t = advance p in
|
||||
let lp = advance p in
|
||||
let args = items p RP lp.loc ~what:"parameters" in
|
||||
match (peek p).tok with
|
||||
| NAME "=" ->
|
||||
let rp = last p in
|
||||
let names = List.for_all (fun (a : Form.t) -> match a.v with Form.Sym _ -> true | _ -> false) args in
|
||||
let header () = "fn(" ^ String.concat ", " (List.map text_of args) ^ ")" in
|
||||
let n = peek p in
|
||||
match n.tok with
|
||||
| NAME "=>" ->
|
||||
ignore (advance p);
|
||||
let ps = lambda_params args in
|
||||
let i0 = p.i and t0 = peek p in
|
||||
let body, _ = expr p in
|
||||
let body = unit_slot p i0 t0 body in
|
||||
let body = lambda_body p ~header:(header ()) in
|
||||
(mk p t.loc
|
||||
(Form.List
|
||||
[ sym t.loc "fn"; Form.make (Form.Vec ps) (span_of_list lp.loc args); body ]),
|
||||
(sym t.loc "fn" :: Form.make (Form.Vec ps) (span_of_list lp.loc args) :: body)),
|
||||
0)
|
||||
| NAME "=" when names -> lambda_equals p (header ())
|
||||
| NAME w when names && glued_arrow w -> lambda_glued n (header ()) w
|
||||
| NEWLINE when names && (peek_at p 1).tok = INDENT ->
|
||||
lambda_arrow (peek_at p 2).loc (header ())
|
||||
(* Inside brackets a line break is no token: the next line's first token
|
||||
is what follows. *)
|
||||
| tk when names && n.loc.Loc.line > rp.loc.Loc.eline && starts_value tk ->
|
||||
lambda_arrow n.loc (header ())
|
||||
| _ -> (mk p t.loc (Form.List (sym t.loc "fn" :: args)), 9)
|
||||
|
||||
(* A lambda with a block written inside a call's brackets, where no block can
|
||||
open. The fix shown is the typed form, since a lambda bound by [let] has
|
||||
no call to take its types from; [header] is [fn(a: T) -> R] or the
|
||||
header as written. *)
|
||||
and lambda_in_brackets : 'a. Loc.t -> string -> 'a = fun at header ->
|
||||
failk "lambda-block-in-brackets" at
|
||||
"a lambda's block cannot go inside brackets, where a line break is only \
|
||||
a space. Name it first, with its types and the block under it:\n\n\
|
||||
\ let f = %s\n ...\n\n\
|
||||
and pass f, or write it on one line: %s = value"
|
||||
(* What follows a lambda's [=>]: a value on the line, or the indented block
|
||||
under it. [header] is the lambda's header as written, for a message. *)
|
||||
and lambda_body p ~header =
|
||||
match (peek p).tok, (peek_at p 1).tok with
|
||||
| NEWLINE, INDENT ->
|
||||
ignore (advance p);
|
||||
let body = !block_of p in
|
||||
p.closed <- p.i;
|
||||
body
|
||||
| (NEWLINE | EOF | DEDENT), _ ->
|
||||
failk "lambda-body" (where_ p)
|
||||
"the line ends after %s =>, and the lambda's body is not under it. Put \
|
||||
the body after the =>, or on the lines under it, indented:\n\n\
|
||||
\ %s =>\n ..."
|
||||
header header
|
||||
| _ ->
|
||||
let i0 = p.i and t0 = peek p in
|
||||
let body, _ = expr p in
|
||||
[ unit_slot p i0 t0 body ]
|
||||
|
||||
(* [fn(a) = x]: a lambda written with a named function's [=]. *)
|
||||
and lambda_equals : 'a. p -> string -> 'a = fun p header ->
|
||||
let eq = advance p in
|
||||
let body =
|
||||
match expr p with
|
||||
| b, _ -> text_of b
|
||||
| exception _ -> "..."
|
||||
in
|
||||
failk "lambda-equals" eq.loc
|
||||
"a lambda's body follows =>, and this one has =, which is how a named \
|
||||
function is written. Write:\n\n %s => %s"
|
||||
header body
|
||||
|
||||
(* [fn(a) =>x]: the body glued to the arrow reads as one name. *)
|
||||
and glued_arrow w = String.length w > 2 && String.sub w 0 2 = "=>"
|
||||
|
||||
and lambda_glued : 'a. token -> string -> string -> 'a = fun t header w ->
|
||||
failk "lambda-arrow-space" t.loc
|
||||
"%s is one name, with nothing between => and the body. Put a space \
|
||||
after the arrow: %s => %s"
|
||||
w header (String.sub w 2 (String.length w - 2))
|
||||
|
||||
(* A lambda header with lines under it and no [=>]. *)
|
||||
and lambda_arrow : 'a. Loc.t -> string -> 'a = fun at header ->
|
||||
failk "lambda-arrow" at
|
||||
"the lines under %s are a lambda's body only after =>. End the header \
|
||||
with it:\n\n %s =>\n ..."
|
||||
header header
|
||||
|
||||
(* Whether the [fn(] at point has a [:] among its parameters or a [->]
|
||||
@ -852,7 +1005,8 @@ and lambda_params args =
|
||||
(* Comma-separated values up to [closer]. [const T] is two elements without a
|
||||
comma, for [Ptr(const u8)]: const is a reserved word in a type and never a
|
||||
value. *)
|
||||
and items p closer open_loc ~what =
|
||||
(* [head] is the text of what is indexed, for [[ ]]'s message. *)
|
||||
and items ?head p closer open_loc ~what =
|
||||
let opener = if closer = RB then '[' else '(' in
|
||||
let rec go acc =
|
||||
let t = peek p in
|
||||
@ -871,22 +1025,7 @@ and items p closer open_loc ~what =
|
||||
| EOF -> unclosed p opener open_loc
|
||||
| _ ->
|
||||
let n = peek p in
|
||||
let block_lambda =
|
||||
match e.v with
|
||||
| Form.List ({ v = Form.Sym "fn"; _ } :: ps) ->
|
||||
List.for_all (fun (a : Form.t) -> match a.v with Form.Sym _ -> true | _ -> false) ps
|
||||
&& n.loc.Loc.line > e.loc.Loc.eline
|
||||
| _ -> false
|
||||
in
|
||||
if block_lambda then
|
||||
let names =
|
||||
match e.v with
|
||||
| Form.List (_ :: ps) -> List.map text_of ps
|
||||
| _ -> []
|
||||
in
|
||||
lambda_in_brackets n.loc
|
||||
("fn(" ^ String.concat ", " (List.map (fun x -> x ^ ": T") names) ^ ") -> R")
|
||||
else if starts_value n.tok && n.sp && not (negative_literal n.tok)
|
||||
if starts_value n.tok && n.sp && not (negative_literal n.tok)
|
||||
&& n.loc.Loc.line > e.loc.Loc.eline then
|
||||
(* Most often the bracket was never closed: the next statement
|
||||
has been read as one more argument. *)
|
||||
@ -898,8 +1037,11 @@ and items p closer open_loc ~what =
|
||||
else if starts_value n.tok && n.sp && not (negative_literal n.tok) then
|
||||
failk "missing-comma" n.loc
|
||||
"%s follows %s with no comma between them. Separate %s with \
|
||||
commas: f(a, b)"
|
||||
commas: %s"
|
||||
(show n.tok) (text_of e) what
|
||||
(match head with
|
||||
| Some h -> Printf.sprintf "%s[%s, %s]" h (text_of e) (show n.tok)
|
||||
| None -> "f(a, b)")
|
||||
else stray p ~after:(text_of e))
|
||||
in
|
||||
go []
|
||||
@ -1042,12 +1184,6 @@ let blk (s : st) l (ss : Form.t list) =
|
||||
| (first : Form.t) :: _ -> mk s.p first.loc (Form.List (sym first.loc "do" :: ss))
|
||||
| [] -> mk s.p l (Form.List [ sym l "do" ])
|
||||
|
||||
let is_lambda_candidate (e : Form.t) =
|
||||
match e.v with
|
||||
| Form.List ({ v = Form.Sym "fn"; _ } :: args) ->
|
||||
List.for_all (fun (a : Form.t) -> match a.v with Form.Sym _ -> true | _ -> false) args
|
||||
| _ -> false
|
||||
|
||||
(* The word at the head of the line is a variable being assigned, [data = 3]
|
||||
or [on += 1], whatever else it could start. *)
|
||||
let assigns p =
|
||||
@ -1171,11 +1307,10 @@ let named_params p (lp : token) =
|
||||
in
|
||||
go []
|
||||
|
||||
(* [fn(a: C, b) -> R = body] is [(the (Fn [C dyn] R) (fn [a b] body))]: the
|
||||
(* [fn(a: C, b) -> R => body] is [(the (Fn [C dyn] R) (fn [a b] body))]: the
|
||||
paren [fn] takes its parameters' types from where it is written, and [the]
|
||||
is the form that says what a value is, as in [let x: T = v]. An untyped
|
||||
parameter is dyn, as in a definition, and the return type is required. A
|
||||
block body is added by [lambda_block]. *)
|
||||
parameter is dyn, as in a definition, and the return type is required. *)
|
||||
let () = typed_fn_expr := fun p ->
|
||||
let t = advance p in
|
||||
let lp = advance p in
|
||||
@ -1185,18 +1320,22 @@ let () = typed_fn_expr := fun p ->
|
||||
| _ -> ([], [])
|
||||
in
|
||||
let names, tys = split ps in
|
||||
let params_text () =
|
||||
String.concat ", "
|
||||
(List.map2 (fun n (ty : Form.t) ->
|
||||
if ty.v = Form.Sym "dyn" then text_of n else text_of n ^ ": " ^ text_of ty)
|
||||
names tys)
|
||||
in
|
||||
let r =
|
||||
match (peek p).tok with
|
||||
| NAME "->" -> ignore (advance p); ty p
|
||||
| _ ->
|
||||
failk "lambda-return" (where_ p)
|
||||
"a lambda that states its parameters' types states its return type \
|
||||
too: fn(%s) -> R = value"
|
||||
(String.concat ", "
|
||||
(List.map2 (fun n (ty : Form.t) ->
|
||||
if ty.v = Form.Sym "dyn" then text_of n else text_of n ^ ": " ^ text_of ty)
|
||||
names tys))
|
||||
too: fn(%s) -> R => value"
|
||||
(params_text ())
|
||||
in
|
||||
let header () = "fn(" ^ params_text () ^ ") -> " ^ text_of r in
|
||||
let fty = mk p lp.loc (Form.List [ sym t.loc "Fn"; Form.make (Form.Vec tys) lp.loc; r ]) in
|
||||
let vec = Form.make (Form.Vec names) lp.loc in
|
||||
let wrap body =
|
||||
@ -1204,26 +1343,21 @@ let () = typed_fn_expr := fun p ->
|
||||
mk p t.loc (Form.List (sym t.loc "fn" :: vec :: body)) ])
|
||||
in
|
||||
match (peek p).tok with
|
||||
| NAME "=" ->
|
||||
| NAME "=>" ->
|
||||
ignore (advance p);
|
||||
let i0 = p.i and t0 = peek p in
|
||||
let body, _ = expr p in
|
||||
(wrap [ unit_slot p i0 t0 body ], 0)
|
||||
| NEWLINE when (peek_at p 1).tok = INDENT -> (wrap [], 0)
|
||||
let body = lambda_body p ~header:(header ()) in
|
||||
(wrap body, 0)
|
||||
| NAME "=" -> lambda_equals p (header ())
|
||||
| NAME w when glued_arrow w -> lambda_glued (peek p) (header ()) w
|
||||
| NEWLINE when (peek_at p 1).tok = INDENT -> lambda_arrow (peek_at p 2).loc (header ())
|
||||
(* Inside brackets a line break is no token: the next line's first token
|
||||
is what follows. *)
|
||||
| tk when (peek p).loc.Loc.line > (last p).loc.Loc.eline && tk <> EOF ->
|
||||
let header =
|
||||
"fn(" ^ String.concat ", "
|
||||
(List.map2 (fun n (ty : Form.t) ->
|
||||
if ty.v = Form.Sym "dyn" then text_of n else text_of n ^ ": " ^ text_of ty)
|
||||
names tys)
|
||||
^ ") -> " ^ text_of r
|
||||
in
|
||||
lambda_in_brackets (peek p).loc header
|
||||
lambda_arrow (peek p).loc (header ())
|
||||
| _ ->
|
||||
failk "lambda-body" (where_ p)
|
||||
"a lambda's body follows = on its line, or is the block under it"
|
||||
"a lambda's body follows => on its line, or is the block under it: \
|
||||
%s => value" (header ())
|
||||
|
||||
let rec stmts (s : st) : Form.t list =
|
||||
let p = s.p in
|
||||
@ -1298,33 +1432,13 @@ and then_on_line p =
|
||||
in
|
||||
go 1 0
|
||||
|
||||
(* The end of a statement's line, which a lambda's block may already have
|
||||
taken. *)
|
||||
and lambda_block ?(block_ok = false) (s : st) (e : Form.t) ~after =
|
||||
let p = s.p in
|
||||
match e.v with
|
||||
(* A typed lambda waiting for its block, from [typed_fn_expr]. *)
|
||||
| Form.List [ ({ v = Form.Sym "the"; _ } as th); fty;
|
||||
({ v = Form.List [ ({ v = Form.Sym "fn"; _ } as fh); ({ v = Form.Vec _; _ } as vec) ]; _ } as fn_) ]
|
||||
when (peek p).tok = NEWLINE && (peek_at p 1).tok = INDENT ->
|
||||
ignore (advance p);
|
||||
let body = block s ~after in
|
||||
mk p e.loc (Form.List [ th; fty; { fn_ with v = Form.List (fh :: vec :: body) } ])
|
||||
| _ ->
|
||||
if is_lambda_candidate e && (last p).tok = RP && (peek p).tok = NEWLINE
|
||||
&& (peek_at p 1).tok = INDENT
|
||||
then begin
|
||||
ignore (advance p);
|
||||
let body = block s ~after in
|
||||
match e.v with
|
||||
| Form.List (h :: args) ->
|
||||
mk p e.loc
|
||||
(Form.List (h :: Form.make (Form.Vec args) (span_of_list e.loc args) :: body))
|
||||
| _ -> assert false
|
||||
end
|
||||
else begin
|
||||
if block_ok && (peek p).tok = NEWLINE then ignore (advance p)
|
||||
else expect_eol p ~after;
|
||||
e
|
||||
end
|
||||
if block_ok && p.i <> p.closed && (peek p).tok = NEWLINE then ignore (advance p)
|
||||
else expect_eol p ~after;
|
||||
e
|
||||
|
||||
and let_stmt (s : st) : Form.t list =
|
||||
let p = s.p in
|
||||
@ -2116,6 +2230,7 @@ and clause_end p head =
|
||||
|
||||
(* The end of a header line whose block must follow. *)
|
||||
and expect_line_end p ~after =
|
||||
if p.i = p.closed then () else
|
||||
match (peek p).tok with
|
||||
| NEWLINE -> ignore (advance p)
|
||||
| _ -> stray p ~after
|
||||
@ -2140,6 +2255,8 @@ and lines (s : st) (one : unit -> Form.t list) : Form.t list =
|
||||
go []
|
||||
end
|
||||
|
||||
let () = block_of := fun p -> block { p; lets = [] } ~after:"=>"
|
||||
|
||||
(** All top-level forms in a [.fln] source string. [col] is the column the
|
||||
text's top level starts at, 1 for a file. *)
|
||||
let read_all ?(line = 1) ?col ?indent ?(global_let = true) ~file src =
|
||||
@ -2153,7 +2270,7 @@ let read_all ?(line = 1) ?col ?indent ?(global_let = true) ~file src =
|
||||
(String.make (line - 1) '\n' ^ String.make (col - 1) ' ' ^ src)));
|
||||
Fun.protect ~finally:(fun () -> source := saved) (fun () ->
|
||||
let toks = layout ~snippet ~base:col ?indent (lex ~line ~col ~file src) in
|
||||
let s = { p = { toks; i = 0 }; lets = [] } in
|
||||
let s = { p = { toks; i = 0; closed = -1 }; lets = [] } in
|
||||
(* At the top level, a [let] is a global, [(def x dyn v)]: a let there has
|
||||
no block to be local to. Not in an expression the editor sends, where
|
||||
a let is the statement it is in a body. *)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -401,16 +418,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.
|
||||
@ -478,7 +494,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.
|
||||
|
||||
@ -51,7 +51,7 @@ fn main() -> i32
|
||||
let items = [stock/item("bolts", 12, 400), stock/item("nuts", 5, 3),
|
||||
stock/item("gears", 1250, 7), stock/item("belts", 899, 0)]
|
||||
let rules = [Rule{.label "reorder", .applies low?},
|
||||
Rule{.label "valuable", .applies fn(it) = stock/value(it) > 5000}]
|
||||
Rule{.label "valuable", .applies fn(it) => stock/value(it) > 5000}]
|
||||
let frame = arena-new(4096)
|
||||
repeat(pass, 2):
|
||||
runs += 1
|
||||
@ -75,7 +75,7 @@ fn main() -> i32
|
||||
println("sign bits", stock/sign-bit(-2.5), stock/sign-bit(2.5),
|
||||
"masked", bit-and(-total, 0xFF))
|
||||
let big = 1000
|
||||
println("worth over", big, count-if(slice(items), fn(it) = stock/value(it) > big))
|
||||
println("worth over", big, count-if(slice(items), fn(it) => stock/value(it) > big))
|
||||
expect(total == 407, "407 on even rows")
|
||||
expect(runs == 2, "two runs")
|
||||
0
|
||||
|
||||
@ -60,7 +60,7 @@ fn repeat-apply(f: CFn($t) -> $t, x: $t, n: i32) -> $t
|
||||
v = f(v)
|
||||
v
|
||||
|
||||
fn halve-all(x: $t) -> $t where numeric?($t) = repeat-apply(fn(a: $t) -> $t = a / 2, x, 3)
|
||||
fn halve-all(x: $t) -> $t where numeric?($t) = repeat-apply(fn(a: $t) -> $t => a / 2, x, 3)
|
||||
|
||||
fn main() -> i32
|
||||
let r: Ring(5, i32) = zeroed()
|
||||
@ -80,16 +80,16 @@ fn main() -> i32
|
||||
let d = distinct(slice(samples))
|
||||
println("distinct", slice(d))
|
||||
free(d)
|
||||
let big = filter(slice(samples), fn(x) = x >= 7)
|
||||
println("seven and up", slice(big), "sum", reduce(slice(big), 0, fn(a, b) = a + b))
|
||||
let big = filter(slice(samples), fn(x) => x >= 7)
|
||||
println("seven and up", slice(big), "sum", reduce(slice(big), 0, fn(a, b) => a + b))
|
||||
free(big)
|
||||
let rs = [Reading{.sensor 2, .value 40} Reading{.sensor 1, .value 15}
|
||||
Reading{.sensor 3, .value 22}]
|
||||
sort-by(slice(rs), fn(a, b) = a.value < b.value)
|
||||
sort-by(slice(rs), fn(a, b) => a.value < b.value)
|
||||
for i in range(length(rs))
|
||||
println("sensor", rs[i].sensor, rs[i].value)
|
||||
let raw: [4 u32] = [1 2 3 4]
|
||||
let p = Ptr(u8)(addr(raw[0]))
|
||||
println("checksum", checksum(p, 16))
|
||||
println("doubled", repeat-apply(fn(a: i32) -> i32 = a * 2, 1, 10), "halved", halve-all(800.0))
|
||||
println("doubled", repeat-apply(fn(a: i32) -> i32 => a * 2, 1, 10), "halved", halve-all(800.0))
|
||||
0
|
||||
|
||||
@ -58,7 +58,7 @@ fn main() -> i32
|
||||
let text = "The cat saw the dog. The dog didn't see the cat, but the bird saw both!"
|
||||
let ws = words(bytes-view(text))
|
||||
let counts = tally(slice(ws))
|
||||
let by-count = fn(a: Count, b: Count) -> bool
|
||||
let by-count = fn(a: Count, b: Count) -> bool =>
|
||||
if a.n != b.n
|
||||
return a.n > b.n
|
||||
bytes<?(a.word, b.word)
|
||||
@ -73,7 +73,7 @@ fn main() -> i32
|
||||
let distinct = i32(length(counts))
|
||||
let g = grade(distinct, total)
|
||||
println(distinct, "of", total, "distinct:", describe(g))
|
||||
let longest = reduce(slice(ws), slice(ws[0], 0, 0), fn(a, b) =
|
||||
let longest = reduce(slice(ws), slice(ws[0], 0, 0), fn(a, b) =>
|
||||
if length(b) > length(a) then b else a)
|
||||
println("longest", str(longest))
|
||||
let short = 0 < length(longest) < 5
|
||||
|
||||
@ -58,7 +58,8 @@ let describe_diff a b =
|
||||
[let] takes in the statements after it (the printer's flat [let]; with
|
||||
every [let] name unique by now, nothing after it can mean one of them). A
|
||||
[(do x)] whose [x] is a [let] is [x], a [let] whose whole body is another
|
||||
[let] is the merged [let], and [(and x)] and [(or x)] are [x]. The flat
|
||||
[let] is the merged [let], [(fn [a] (do x y))] is [(fn [a] x y)], and
|
||||
[(and x)] and [(or x)] are [x]. The flat
|
||||
[let] and the one-argument [and] stop at a quote or quasiquote: data, or
|
||||
a template whose unquotes could name anything. *)
|
||||
|
||||
@ -203,6 +204,12 @@ let rec shape ?(q = false) (f : Form.t) : Form.t =
|
||||
| [ { v = Form.List ({ v = Form.Sym "let"; _ } :: { v = Form.Vec bs2; _ } :: body2); _ } ] ->
|
||||
Form.List (h :: Form.make (Form.Vec (List.map sh bs @ bs2)) loc :: body2)
|
||||
| body -> Form.List (h :: Form.make (Form.Vec (List.map sh bs)) loc :: body))
|
||||
(* A lambda whose body is one [do] is the lambda of its statements:
|
||||
the printer writes them straight under [=>]. *)
|
||||
| Form.List [ ({ v = Form.Sym "fn"; _ } as h); ({ v = Form.Vec _; _ } as ps);
|
||||
{ v = Form.List ({ v = Form.Sym "do"; _ } :: (_ :: _ :: _ as ss)); _ } ]
|
||||
when not q ->
|
||||
(sh { f with v = Form.List (h :: ps :: ss) }).v
|
||||
| Form.List (({ v = Form.Sym "handler-case"; _ } as h) :: body :: ({ v = Form.Vec cls; _ } as cv) :: more) ->
|
||||
Form.List (h :: sh body :: { cv with v = Form.Vec (List.map clause cls) }
|
||||
:: List.map sh more)
|
||||
@ -547,8 +554,8 @@ let () =
|
||||
reads "quote block"
|
||||
"defmacro(m, [x & ys]):\n quote\n f(~x)\n ~@ys"
|
||||
"(defmacro m [x & ys] (quasiquote (do (f (unquote x)) (unquote-splicing ys))))";
|
||||
reads "lambda" "g = fn(i, j) = i * 10 + j" "(set g (fn [i j] (+ (* i 10) j)))";
|
||||
reads "lambda with a block" "g = fn(i)\n a(i)\n b(i)" "(set g (fn [i] (a i) (b i)))";
|
||||
reads "lambda" "g = fn(i, j) => i * 10 + j" "(set g (fn [i j] (+ (* i 10) j)))";
|
||||
reads "lambda with a block" "g = fn(i) =>\n a(i)\n b(i)" "(set g (fn [i] (a i) (b i)))";
|
||||
reads "where" "fn s(xs: [$t]) -> () where ordered?($t) = f(xs)"
|
||||
"(defn s [xs [$t]] () {:where (ordered? $t)} (f xs))";
|
||||
reads "data" "data Shape\n Circle(r: f32)\n Empty"
|
||||
@ -650,10 +657,51 @@ let () =
|
||||
refuses "field with =" "p = P{x = 1}" "indent/brace-field" "{.x value}";
|
||||
refuses "field with a colon" "p = P{x: 1}" "indent/brace-field" "no colon";
|
||||
refuses "a dotted range" "for i in 0..10\n g(i)" "indent/dot-range" "range(0, 10)";
|
||||
refuses "a block lambda inside a call" "sort-by(xs, fn(a, b)\n a < b)"
|
||||
"indent/lambda-block-in-brackets" "let f = fn(a: T, b: T) -> R";
|
||||
refuses "a typed block lambda inside a call" "sort-by(xs, fn(a: C, b: C) -> bool\n a < b)"
|
||||
"indent/lambda-block-in-brackets" "let f = fn(a: C, b: C) -> bool";
|
||||
(* A lambda's block inside brackets ends where they close. *)
|
||||
reads "a block lambda inside a call" "sort-by(xs, fn(a, b) =>\n let c = a + 1\n c < b)"
|
||||
"(sort-by xs (fn [a b] (let [c (+ a 1)] (< c b))))";
|
||||
reads "its closer on a line of its own" "sort-by(xs, fn(a, b) =>\n a < b\n)\ng()"
|
||||
"(sort-by xs (fn [a b] (< a b)))\n(g)";
|
||||
reads "a typed block lambda inside a call" "sort-by(xs, fn(a: C, b: C) -> bool =>\n a < b)"
|
||||
"(sort-by xs (the (Fn [C C] bool) (fn [a b] (< a b))))";
|
||||
reads "nested block lambdas"
|
||||
"map(xs, fn(x) =>\n let ys = map(x, fn(y) =>\n if y > 0\n y\n else\n 0)\n sum(ys))"
|
||||
"(map xs (fn [x] (let [ys (map x (fn [y] (if (> y 0) y 0)))] (sum ys))))";
|
||||
reads "a block lambda on a wrapped argument line" "f(a,\n fn(b) =>\n g(b)\n h(b))"
|
||||
"(f a (fn [b] (g b) (h b)))";
|
||||
reads "a block lambda in a vector" "x = [1, fn(b) =>\n b]" "(set x [1 (fn [b] b)])";
|
||||
reads ~global:false "a call after a block lambda's call" "let v = f(fn(a) =>\n a)\ng(v)"
|
||||
"(let [v (f (fn [a] a))] (g v))";
|
||||
refuses "a block lambda is the last argument" "sort-by(fn(a, b) =>\n a < b, xs)"
|
||||
"indent/lambda-block-last" "let f = fn(a) =>";
|
||||
refuses "one block lambda to a call" "f(fn(a) =>\n a\n, fn(b) =>\n b)"
|
||||
"indent/lambda-block-last" "on line 1";
|
||||
refuses "a line back at the header's column" "f(fn(a) =>\n a\nb)"
|
||||
"indent/lambda-block-last" "b follows the block of the lambda on line 1";
|
||||
refuses "an element after a block lambda, left of its block" "m = {:a fn(x) =>\n x\n :b 2}"
|
||||
"indent/lambda-block-last" ":b follows the block";
|
||||
refuses "a block's first line not indented" "f(fn(a) =>\na)"
|
||||
"indent/lambda-block-left" "Indent it into the block";
|
||||
refuses "a block lambda's brackets left open" "f(fn(a) =>\n a\n"
|
||||
"indent/unclosed" "ends where this bracket closes";
|
||||
refuses "indices with no comma" "x = grid[row col].color-idx"
|
||||
"indent/missing-comma" "Separate indices with commas: grid[row, col]";
|
||||
refuses "arguments with no comma" "x = f(a b)"
|
||||
"indent/missing-comma" "Separate arguments with commas: f(a, b)";
|
||||
refuses "a body glued to =>" "x = fn(a) =>a"
|
||||
"indent/lambda-arrow-space" "fn(a) => a";
|
||||
refuses "a lambda written with =" "x = fn(a, b) = a + b"
|
||||
"indent/lambda-equals" "fn(a, b) => a + b";
|
||||
refuses "a typed lambda written with =" "x = fn(a: C) -> bool = a.n > 1"
|
||||
"indent/lambda-equals" "fn(a: C) -> bool => a.n > 1";
|
||||
refuses "a block lambda with no =>" "let f = fn(a)\n a\nf"
|
||||
"indent/lambda-arrow" "fn(a) =>";
|
||||
refuses "a block lambda with no => inside a call" "sort-by(xs, fn(a, b)\n a < b)"
|
||||
"indent/lambda-arrow" "fn(a, b) =>";
|
||||
refuses "a typed block lambda with no => inside a call" "sort-by(xs, fn(a: C, b: C) -> bool\n a < b)"
|
||||
"indent/lambda-arrow" "fn(a: C, b: C) -> bool =>";
|
||||
refuses "a => with nothing under it" "let f = fn(a) =>\nf"
|
||||
"indent/lambda-body" "fn(a) =>";
|
||||
refuses "an else after else-if on one line" "if a then x\nelse if b then y\nelse z"
|
||||
"indent/orphan-else" "Write that line as elif";
|
||||
refuses "else deeper than a one-line if" "if a then b\n else c"
|
||||
@ -668,17 +716,17 @@ let () =
|
||||
"(cond a b c d e (do (f) (g)) :else (h))";
|
||||
refuses "else left of a one-line if" "while x\n if a then b\nelse c"
|
||||
"indent/orphan-else" "goes at the if's column";
|
||||
reads "a typed lambda" "f = fn(a: C, b) -> bool = a.n < b"
|
||||
reads "a typed lambda" "f = fn(a: C, b) -> bool => a.n < b"
|
||||
"(set f (the (Fn [C dyn] bool) (fn [a b] (< (.n a) b))))";
|
||||
reads ~global:false "a typed lambda with a block" "let f = fn(x: i32) -> i32\n let y = x + 1\n y\ng(f)"
|
||||
reads ~global:false "a typed lambda with a block" "let f = fn(x: i32) -> i32 =>\n let y = x + 1\n y\ng(f)"
|
||||
"(let [f (the (Fn [i32] i32) (fn [x] (let [y (+ x 1)] y)))] (g f))";
|
||||
refuses "a typed lambda states its return type" "f = fn(a: C) = a"
|
||||
"indent/lambda-return" "fn(a: C) -> R = value";
|
||||
"indent/lambda-return" "fn(a: C) -> R => value";
|
||||
reads "a restart's report on its header"
|
||||
"restart-case\n go()\nrestart retry(n: i32) \"Try again\"\n n"
|
||||
"(restart-case (go) (retry [n i32] :report \"Try again\" n))";
|
||||
reads "a bare () in a body slot does nothing"
|
||||
"fn f() -> () = ()\nfn g(x) -> ()\n match x\n 1 -> h()\n _ -> ()\n k = fn() = ()"
|
||||
"fn f() -> () = ()\nfn g(x) -> ()\n match x\n 1 -> h()\n _ -> ()\n k = fn() => ()"
|
||||
"(defn f [] () (do))\n(defn g [x dyn] () (match x 1 (h) _ (do)) (set k (fn [] (do))))";
|
||||
reads "a parenthesised () stays a value" "x = (())" "(set x ())";
|
||||
reads "a template's for takes an unquoted variable"
|
||||
@ -758,7 +806,7 @@ let () =
|
||||
prints "comment is a body in order" "(comment (let [a 1] (g a)) (h))" "comment:\n let a = 1\n g(a)\n h()";
|
||||
prints "a later lambda keeps the outer name"
|
||||
"(defn f [] i32 (let [x 1] (let [x 5] (g x)) (app (fn [y] (+ x y)) 2)))"
|
||||
" let x = 1\n let x-2 = 5\n g(x-2)\n app(fn(y) = x + y, 2)";
|
||||
" let x = 1\n let x-2 = 5\n g(x-2)\n app(fn(y) => x + y, 2)";
|
||||
prints "a renamed name renamed again counts on"
|
||||
"(defn f [] () (let [x 1] (let [x 2] (let [x 3] (g x)) (g x)) (g x)))"
|
||||
" let x = 1\n let x-2 = 2\n let x-3 = 3\n g(x-3)\n g(x-2)\n g(x)";
|
||||
@ -848,7 +896,53 @@ let () =
|
||||
"(defn f [a bool b bool c bool] bool (or (and a b) c))" "= (a and b) or c";
|
||||
prints "a typed lambda prints as one"
|
||||
"(defn f [] () (let [g (the (Fn [C] bool) (fn [c] (> (.n c) 3)))] (h g)))"
|
||||
"let g = fn(c: C) -> bool = c.n > 3";
|
||||
"let g = fn(c: C) -> bool => c.n > 3";
|
||||
(* A lambda with a block as a call's last argument prints inside the call,
|
||||
and reads back as it was. *)
|
||||
let round name src want =
|
||||
prints name src want;
|
||||
let forms = Reader.read_all ~file:"<p>" src in
|
||||
let text = Indent_printer.program ~source:src forms in
|
||||
match Indent_reader.read_all ~file:"<p>" text with
|
||||
| back ->
|
||||
if not (same_forms forms back) then
|
||||
fail "%s: read back %s from %S" name (describe_diff forms back) text
|
||||
| exception e -> fail "%s: its text is refused: %s\n%s" name (diag_text e) text
|
||||
in
|
||||
round "a one-line lambda" "(defn f [] () (h (fn [a] (+ a 1)) 2))" "= h(fn(a) => a + 1, 2)";
|
||||
round "a block lambda as a call's last argument"
|
||||
"(defn f [] () (sort-by xs (fn [a b] (g a) (< a b))))"
|
||||
" sort-by(xs, fn(a, b) =>\n g(a)\n a < b)";
|
||||
round "one closer for a call in a call"
|
||||
"(defn f [] () (println (run (fn [] (g) 1))))" " println(run(fn() =>\n g()\n 1))";
|
||||
round "a typed block lambda in a call"
|
||||
"(defn f [] () (h (the (Fn [C] bool) (fn [c] (g c) (> (.n c) 3)))))"
|
||||
" h(fn(c: C) -> bool =>\n g(c)\n c.n > 3)";
|
||||
round "a let's call with a block lambda"
|
||||
"(defn f [] () (let [v (m xs (fn [x] (g x) x))] (h v)))"
|
||||
" let v = m(xs, fn(x) =>\n g(x)\n x)\n h(v)";
|
||||
round "nested block lambdas"
|
||||
"(defn f [] () (m xs (fn [x] (let [y (m x (fn [z] (g z) z))] (h y)))))"
|
||||
" m(xs, fn(x) =>\n let y = m(x, fn(z) =>\n g(z)\n z)\n h(y))";
|
||||
round "a block lambda inside an expression, the line going on after it"
|
||||
"(defn f [] i64 (+ (ap 1 (fn [x] (g x) x)) 1))" " ap(1, fn(x) =>\n g(x)\n x) + 1";
|
||||
round "one in a lambda that fits on its line"
|
||||
"(defn f [] i64 (ap 1 (fn [x] (ap x (fn [y] (g y) y)))))"
|
||||
" ap(1, fn(x) =>\n ap(x, fn(y) =>\n g(y)\n y))";
|
||||
round "a struct literal's last field"
|
||||
"(defn f [] i64 (let [o (Ops {.k 1 .run (fn [x] (g x) x)})] (.k o)))"
|
||||
" let o = Ops{.k 1, .run fn(x) =>\n g(x)\n x}";
|
||||
round "a returned call"
|
||||
"(defn f [] i64 (return (ap 2 (fn [x] (g x) x))))" " return ap(2, fn(x) =>\n g(x)\n x)";
|
||||
round "a let in a lambda inside a call's argument"
|
||||
"(defn f [] i64 (println (call0 (fn [] (let [n 0] (set n 1) n)))) 0)"
|
||||
" println(call0(fn() =>\n let n = 0\n n = 1\n n))";
|
||||
prints "a body that is one do goes straight under =>"
|
||||
"(defn f [] i64 (call0 (fn [] (do (g 1) 2))))" " call0(fn() =>\n g(1)\n 2)";
|
||||
round "a block lambda not last keeps the fallback"
|
||||
"(defn f [] () (r (fn [a b] (g a) b) 0))" "= r(fn([a b], g(a), b), 0)";
|
||||
round "a lambda bound by let"
|
||||
"(defn f [] () (let [k (fn [a] (g a) a)] (k 1)))" " let k = fn(a) =>\n g(a)\n a\n k(1)";
|
||||
prints "a restart's report goes on its header"
|
||||
"(defn f [] i32 (restart-case (go) (retry [] :report \"Try again\" 7)))"
|
||||
"restart retry() \"Try again\"\n 7";
|
||||
@ -900,7 +994,7 @@ let () =
|
||||
prints "a let-bound loop" "(defn f [] i32 (let [r (loop [i 0] (recur i))] r))"
|
||||
" let r = loop i = 0\n recur(i)";
|
||||
prints "a lambda as a loop's value is parenthesised"
|
||||
"(defn f [] () (loop [g (fn [x] x) n 0] (recur g n)))" "loop g = (fn(x) = x), n = 0"
|
||||
"(defn f [] () (loop [g (fn [x] x) n 0] (recur g n)))" "loop g = (fn(x) => x), n = 0"
|
||||
|
||||
(* ── Spans, for pause marks and error overlays ──────────────────────── *)
|
||||
|
||||
@ -1107,8 +1201,8 @@ let () =
|
||||
[ "it has to be written: i32(x)" ];
|
||||
refused "unknown-type.fln" "fn f(p: Keyword) -> i32 = 0\n\nfn main() -> i32 = 0\n"
|
||||
[ "unknown type Keyword" ];
|
||||
refused "untyped-lambda.fln" "fn main() -> i32\n let f = fn(a)\n a\n 0\n"
|
||||
[ "let f: Fn(T, ...) -> R = fn(...)" ];
|
||||
refused "untyped-lambda.fln" "fn main() -> i32\n let f = fn(a) =>\n a\n 0\n"
|
||||
[ "let f: Fn(T, ...) -> R = fn(...) =>" ];
|
||||
refused "plusplus.fln" "fn main() -> i32\n let x = 1\n x++\n x\n"
|
||||
[ "write ++(x) or x += 1" ];
|
||||
refused "plusplus-global.fln" "once g = 0\n\nfn main() -> i32\n g--\n 0\n"
|
||||
@ -1138,25 +1232,28 @@ let () =
|
||||
fail "map-key.fln: %d errors, wanted one" (List.length ds)
|
||||
| exception _ -> ()
|
||||
| _ -> ());
|
||||
(* The fix the lambda-in-brackets refusal shows compiles, with its
|
||||
placeholders filled in. *)
|
||||
(* The fix a block lambda with no => is shown compiles, inside the call. *)
|
||||
(match read "sort-by(xs, fn(a, b)\n a.n < b.n)" with
|
||||
| _ -> fail "lambda in brackets: read"
|
||||
| exception Loc.Error d ->
|
||||
let header =
|
||||
let m = d.Loc.dmsg in
|
||||
let i = String.index m '=' + 2 in
|
||||
let i = String.index m '\n' + 6 in
|
||||
String.sub m i (String.index_from m i '\n' - i)
|
||||
in
|
||||
let header =
|
||||
String.concat "C" (String.split_on_char 'T' header)
|
||||
|> String.split_on_char 'R' |> String.concat "bool"
|
||||
in
|
||||
checks "lambda-fix.fln"
|
||||
("struct C\n n: i32\n\nfn main() -> i32\n let xs = [C{.n 2} C{.n 1}]\n"
|
||||
^ " let f = " ^ header ^ "\n a.n < b.n\n sort-by(slice(xs), f)\n xs[0].n\n"));
|
||||
^ " sort-by(slice(xs), " ^ header ^ "\n a.n < b.n)\n xs[0].n\n"));
|
||||
(* A typed block lambda inside a call, a closer on its own line, and one
|
||||
nested in another's block. *)
|
||||
checks "block-lambdas.fln"
|
||||
("struct C\n n: i32\n\nfn app(x: i64, f: Fn(i64) -> i64) -> i64 = f(x)\n\n"
|
||||
^ "fn main() -> i32\n let xs = [C{.n 2} C{.n 1}]\n"
|
||||
^ " sort-by(slice(xs), fn(a: C, b: C) -> bool =>\n let d = a.n - b.n\n d < 0)\n"
|
||||
^ " let k = app(2, fn(a) =>\n let b = app(a, fn(c) =>\n c * 10\n )\n b + 1)\n"
|
||||
^ " i32(k) + xs[0].n\n");
|
||||
refused "cfn-captures.fln"
|
||||
"fn app(f: CFn(Option(i32)) -> i32) -> i32 = f(None)\n\nfn main() -> i32\n let k = 1\n app(fn(o) = k)\n"
|
||||
"fn app(f: CFn(Option(i32)) -> i32) -> i32 = f(None)\n\nfn main() -> i32\n let k = 1\n app(fn(o) => k)\n"
|
||||
[ "so it is a Fn(Option(i32)) -> i32 and not a CFn(Option(i32)) -> i32" ];
|
||||
refused "defvar.fln" "defvar(x, 1)\n\nfn main() -> i32 = 0\n"
|
||||
[ "once x = 1 initialises once"; "def x = 1 re-initialises" ]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user