A block lambda in code cut mid-line reads against its line's start, an arm or else value ending in => takes its block, and a missing comma between indices shows the indexed spelling
This commit is contained in:
parent
6e56fce7fa
commit
34ef99d1f2
@ -239,7 +239,9 @@ fine here. Brackets and strings are still paired."
|
||||
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."
|
||||
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))
|
||||
@ -337,10 +339,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))
|
||||
@ -839,7 +849,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
|
||||
|
||||
@ -71,9 +71,13 @@ fn lam(k: i64) -> i64
|
||||
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)
|
||||
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
|
||||
@ -302,9 +306,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")
|
||||
("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")
|
||||
("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")
|
||||
|
||||
@ -925,6 +925,27 @@ its line with AT-END."
|
||||
(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"
|
||||
|
||||
@ -289,8 +289,17 @@ let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token ar
|
||||
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 = ref (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
|
||||
@ -315,7 +324,7 @@ let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token ar
|
||||
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"
|
||||
@ -439,7 +448,12 @@ let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token ar
|
||||
&& arr.(i + 1).loc.Loc.line > t.loc.Loc.eline ->
|
||||
frames := { f_base = !base; f_stack = !stack; f_opens = !opens; f_arrow = t }
|
||||
:: !frames;
|
||||
base := line_col i;
|
||||
(* 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 := []
|
||||
| _ -> ())
|
||||
@ -723,7 +737,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);
|
||||
@ -892,6 +906,7 @@ and fn_expr p =
|
||||
(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
|
||||
@ -933,6 +948,15 @@ and lambda_equals : 'a. p -> string -> 'a = fun p header ->
|
||||
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
|
||||
@ -975,7 +999,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
|
||||
@ -1006,8 +1031,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 []
|
||||
@ -1265,6 +1293,7 @@ let () = typed_fn_expr := fun p ->
|
||||
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. *)
|
||||
|
||||
@ -615,6 +615,12 @@ let () =
|
||||
"indent/lambda-block-left" "still inside the brackets";
|
||||
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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user