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
|
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
|
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
|
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
|
(save-excursion
|
||||||
(let* ((bol (flan-fln--bol pos))
|
(let* ((bol (flan-fln--bol pos))
|
||||||
(s (syntax-ppss bol))
|
(s (syntax-ppss bol))
|
||||||
@ -337,10 +339,18 @@ depth, outside strings and comments, or nil."
|
|||||||
(flan-fln--joined-end l))))
|
(flan-fln--joined-end l))))
|
||||||
(and m (car m))))
|
(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)
|
(defun flan-fln--clause-value (l)
|
||||||
"Bounds of the value on the clause line L itself: `x' of `else x' or of
|
"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."
|
`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)))
|
(then (flan-fln--then l)))
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(goto-char (flan-fln--first-char l))
|
(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)))
|
(let* ((pat (string-trim (buffer-substring-no-properties start arrow)))
|
||||||
(vbeg (save-excursion (goto-char (+ arrow 2))
|
(vbeg (save-excursion (goto-char (+ arrow 2))
|
||||||
(skip-chars-forward " \t") (point)))
|
(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))))
|
(flan-fln--body-bounds l))))
|
||||||
(and value
|
(and value
|
||||||
(list :arrow arrow :value 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 app(x: i64, f: Fn(i64) -> i64) -> i64 = f(x)
|
||||||
|
|
||||||
fn lam2(k: i64) -> i64
|
fn lam2(k: i64) -> i64
|
||||||
app(k, fn(a) =>
|
let a = 1
|
||||||
let b = a + 1
|
let b = app(k, fn(x) =>
|
||||||
b * 2)
|
let y = x + a
|
||||||
|
y)
|
||||||
|
let c = fn(z: i64) -> i64 =>
|
||||||
|
z * 2
|
||||||
|
c(b)
|
||||||
|
|
||||||
fn rs() -> i64
|
fn rs() -> i64
|
||||||
restart-case
|
restart-case
|
||||||
@ -302,9 +306,11 @@ comment():
|
|||||||
("else 2" "a one-line else after a block, at its value")
|
("else 2" "a one-line else after a block, at its value")
|
||||||
("a: i64, b" "a typed lambda, from its fn")
|
("a: i64, b" "a typed lambda, from its fn")
|
||||||
("a * 2" "a typed lambda's block")
|
("a * 2" "a typed lambda's block")
|
||||||
("a) =>\n let b" "a block lambda in a call, from its fn")
|
("x) =>" "a block lambda in a call, from its fn")
|
||||||
("let b = a + 1" "a statement in a block lambda's block")
|
("let y = x" "a statement in a block lambda's block")
|
||||||
("b * 2" "the block's last statement, less the call's closer")
|
("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")
|
("restart retry" "a restart with a report, at its block")
|
||||||
("Dir.north" "an enum member's arm, at its value")
|
("Dir.north" "an enum member's arm, at its value")
|
||||||
("let b = 2" "a let the let above takes in, 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))
|
(get-text-property (match-beginning 0) 'face))
|
||||||
'font-lock-keyword-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
|
;;; Block editing
|
||||||
|
|
||||||
(test-flan-fln--in "fn f() -> ()\n if a\n |b()\n c()\n d()\n"
|
(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 arr = Array.of_list toks in
|
||||||
let n = Array.length arr in
|
let n = Array.length arr in
|
||||||
(* A snippet from the editor starts wherever it was written, and its first
|
(* 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. *)
|
line is its base: a later line may not go left of it. One cut from the
|
||||||
let base = ref (if snippet && n > 0 then arr.(0).loc.Loc.col else base) in
|
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 out = ref [] in
|
||||||
let add tok loc = out := { tok; loc; sp = true } :: !out in
|
let add tok loc = out := { tok; loc; sp = true } :: !out in
|
||||||
let stack = ref [ !base ] 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
|
for i = 0 to n - 1 do
|
||||||
let t = arr.(i) in
|
let t = arr.(i) in
|
||||||
(if i = 0 then begin
|
(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
|
failk "unexpected-indent" t.loc
|
||||||
"the first line starts at column %d, and a file's top-level lines \
|
"the first line starts at column %d, and a file's top-level lines \
|
||||||
start at column %d. Remove the indentation"
|
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 ->
|
&& arr.(i + 1).loc.Loc.line > t.loc.Loc.eline ->
|
||||||
frames := { f_base = !base; f_stack = !stack; f_opens = !opens; f_arrow = t }
|
frames := { f_base = !base; f_stack = !stack; f_opens = !opens; f_arrow = t }
|
||||||
:: !frames;
|
:: !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 ];
|
stack := [ !base ];
|
||||||
opens := []
|
opens := []
|
||||||
| _ -> ())
|
| _ -> ())
|
||||||
@ -723,7 +737,7 @@ and postfix p =
|
|||||||
loop (mk p l0 (Form.List (f :: args)), 9)
|
loop (mk p l0 (Form.List (f :: args)), 9)
|
||||||
| LB ->
|
| LB ->
|
||||||
ignore (advance p);
|
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)
|
loop (mk p l0 (Form.List (sym t.loc "at" :: f :: idx)), 9)
|
||||||
| NAME s when String.length s > 1 && s.[0] = '.' ->
|
| NAME s when String.length s > 1 && s.[0] = '.' ->
|
||||||
ignore (advance p);
|
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)),
|
(sym t.loc "fn" :: Form.make (Form.Vec ps) (span_of_list lp.loc args) :: body)),
|
||||||
0)
|
0)
|
||||||
| NAME "=" when names -> lambda_equals p (header ())
|
| 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 ->
|
| NEWLINE when names && (peek_at p 1).tok = INDENT ->
|
||||||
lambda_arrow (peek_at p 2).loc (header ())
|
lambda_arrow (peek_at p 2).loc (header ())
|
||||||
(* Inside brackets a line break is no token: the next line's first token
|
(* 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"
|
function is written. Write:\n\n %s => %s"
|
||||||
header body
|
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 [=>]. *)
|
(* A lambda header with lines under it and no [=>]. *)
|
||||||
and lambda_arrow : 'a. Loc.t -> string -> 'a = fun at header ->
|
and lambda_arrow : 'a. Loc.t -> string -> 'a = fun at header ->
|
||||||
failk "lambda-arrow" at
|
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-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
|
comma, for [Ptr(const u8)]: const is a reserved word in a type and never a
|
||||||
value. *)
|
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 opener = if closer = RB then '[' else '(' in
|
||||||
let rec go acc =
|
let rec go acc =
|
||||||
let t = peek p in
|
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
|
else if starts_value n.tok && n.sp && not (negative_literal n.tok) then
|
||||||
failk "missing-comma" n.loc
|
failk "missing-comma" n.loc
|
||||||
"%s follows %s with no comma between them. Separate %s with \
|
"%s follows %s with no comma between them. Separate %s with \
|
||||||
commas: f(a, b)"
|
commas: %s"
|
||||||
(show n.tok) (text_of e) what
|
(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))
|
else stray p ~after:(text_of e))
|
||||||
in
|
in
|
||||||
go []
|
go []
|
||||||
@ -1265,6 +1293,7 @@ let () = typed_fn_expr := fun p ->
|
|||||||
let body = lambda_body p ~header:(header ()) in
|
let body = lambda_body p ~header:(header ()) in
|
||||||
(wrap body, 0)
|
(wrap body, 0)
|
||||||
| NAME "=" -> lambda_equals p (header ())
|
| 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 ())
|
| 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
|
(* Inside brackets a line break is no token: the next line's first token
|
||||||
is what follows. *)
|
is what follows. *)
|
||||||
|
|||||||
@ -615,6 +615,12 @@ let () =
|
|||||||
"indent/lambda-block-left" "still inside the brackets";
|
"indent/lambda-block-left" "still inside the brackets";
|
||||||
refuses "a block lambda's brackets left open" "f(fn(a) =>\n a\n"
|
refuses "a block lambda's brackets left open" "f(fn(a) =>\n a\n"
|
||||||
"indent/unclosed" "ends where this bracket closes";
|
"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"
|
refuses "a lambda written with =" "x = fn(a, b) = a + b"
|
||||||
"indent/lambda-equals" "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"
|
refuses "a typed lambda written with =" "x = fn(a: C) -> bool = a.n > 1"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user