Indices in .fln separate like a vector's elements, by spaces between single values or by commas

This commit is contained in:
Joseph Ferano 2026-09-26 06:19:59 +07:00
parent 6e006fce27
commit f91ed8ad27
4 changed files with 91 additions and 14 deletions

View File

@ -648,11 +648,6 @@ 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
space-separated single values; commas always work; an index with a bare operator
needs them, ~grid[r + 1, c]~.
** WAIT Calls without parentheses
Held 2026-09-26 by the author: F#-style ~f x y~ or Nim-style one-argument calls
without parentheses. Collides with space-separated vector and index elements.

View File

@ -743,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" ~head:(text_of f) in
let idx = index_items p t.loc ~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);
@ -1005,8 +1005,7 @@ 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. *)
(* [head] is the text of what is indexed, for [[ ]]'s message. *)
and items ?head p closer open_loc ~what =
and items p closer open_loc ~what =
let opener = if closer = RB then '[' else '(' in
let rec go acc =
let t = peek p in
@ -1037,15 +1036,77 @@ and items ?head 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: %s"
commas: f(a, b)"
(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 []
(* An index's values, [grid[r c]] or [grid[r + 1, c]]: separated as a
vector's elements are, by commas or, between single values only, by
spaces. The whole list is read before a mistake is named, so the fix can
be the index as written, commas put in. [head] is the text of what is
indexed. *)
and index_items p open_loc ~head =
let rec go acc =
let t = peek p in
match t.tok with
| RB -> ignore (advance p); List.rev acc
| EOF -> unclosed p '[' open_loc
| _ ->
let e, lvl = expr p in
(* The element as written, parentheses and all. *)
let src = text_of (Form.make (Form.Sym "") (span p t.loc)) in
let n = peek p in
match n.tok with
| COMMA -> ignore (advance p); go ((e, src, lvl, t.loc, Some n.loc) :: acc)
| RB -> ignore (advance p); List.rev ((e, src, lvl, t.loc, None) :: acc)
| EOF -> unclosed p '[' open_loc
(* [grid[i -1]]: most likely [i - 1] with its minus glued. *)
| (ATOM _ | NEG) when n.sp && (negative_literal n.tok || n.tok = NEG) ->
let x, _ = unary p in
let digits =
let s = text_of x in String.sub s 1 (String.length s - 1)
in
failk "glued-minus" n.loc
"%s is read as the value %s, right after %s with nothing between \
the minus and it. To subtract, space the minus: %s[%s - %s]. For \
two indices, separate them with a comma: %s[%s, %s]"
(text_of x) (text_of x) src head src digits head src (text_of x)
| tk when starts_value tk && n.sp -> go ((e, src, lvl, t.loc, None) :: acc)
| _ -> stray p ~after:(text_of e)
in
let xs = go [] in
let commas = List.exists (fun (_, _, _, _, c) -> c <> None) xs in
let spaces =
List.exists (fun (_, _, _, _, c) -> c = None) (match List.rev xs with _ :: r -> r | [] -> [])
in
let with_commas () =
Printf.sprintf "%s[%s]" head
(String.concat ", " (List.map (fun (_, s, _, _, _) -> s) xs))
in
if commas && spaces then begin
let at = match List.find_opt (fun (_, _, _, _, c) -> c <> None) xs with
| Some (_, _, _, _, Some l) -> l | _ -> open_loc
in
failk "mixed-separators" at
"these indices are separated some with commas and some with only \
spaces. Use one: %s%s"
(with_commas ())
(if List.for_all (fun (_, _, l, _, _) -> l >= 8) xs then
Printf.sprintf " or %s[%s]" head
(String.concat " " (List.map (fun (_, s, _, _, _) -> s) xs))
else "")
end;
(match List.find_opt (fun (_, _, l, _, _) -> l < 8) xs with
| Some (_, src, _, at, _) when spaces ->
failk "separate-elements" at
"%s has an operator in it and sits among indices separated by spaces, \
where only single values are. Separate the indices with commas: %s"
src (with_commas ())
| _ -> ());
List.map (fun (e, _, _, _, _) -> e) xs
(* [[a b c]] or [[a, b + 1]]: whitespace separates only single terms. *)
and vec_items p open_loc =
(* One separator per bracket: [1 2, 3] mixes them, and which elements the

View File

@ -145,6 +145,10 @@ Each item: the proposal, then the reason in one line.
the Lisp look for data and is refusable by shape. **Built** (in braces a
value may have an operator in it, `{.x a + 1, .y 2}`; the comma after it is
what is required).
- **Indices separate the same way.** `grid[r c]` and `grid[(r + 1) (c - 1)]`
are two indices each; `grid[r + 1, c]` needs its comma, and `grid[r + 1 c]`
is refused with the commas put in as the fix. `grid[i -1]` is refused as a
glued minus. **Built**; the printer writes indices with commas.
- **Struct literal:** `Vector2{.x 1, .y 2}` (brace glued to the name) reads
`(Vector2 {.x 1 .y 2})`. A bare `{.x 1}` is today's bare literal. `{:a 1}` is a
dyn map. **Built.**

View File

@ -693,8 +693,25 @@ let () =
"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]";
(* Indices separate as a vector's elements do. *)
reads "indices separated by spaces" "x = grid[row col].color-idx"
"(set x (.color-idx (at grid row col)))";
reads "grouped indices separated by spaces" "x = grid[(r + 1) (c - 1)]"
"(set x (at grid (+ r 1) (- c 1)))";
reads "indices separated by commas" "x = grid[r + 1, c]" "(set x (at grid (+ r 1) c))";
reads "calls and fields as spaced indices" "x = grid[f(r) p.y]" "(set x (at grid (f r) (.y p)))";
reads "a negative literal first" "x = grid[-1 c]" "(set x (at grid -1 c))";
reads "a spaced index assigned" "grid[r c] = 1" "(set (at grid r c) 1)";
refuses "an operator among spaced indices" "x = grid[r + 1 c]"
"indent/separate-elements" "Separate the indices with commas: grid[r + 1, c]";
refuses "an operator last among spaced indices" "x = grid[r c - 1]"
"indent/separate-elements" "grid[r, c - 1]";
refuses "mixed index separators" "x = grid[r c, d]"
"indent/mixed-separators" "Use one: grid[r, c, d] or grid[r c d]";
refuses "a glued minus among indices" "x = grid[i -1]"
"indent/glued-minus" "grid[i - 1]";
refuses "a glued negation among indices" "x = grid[i -j]"
"indent/glued-minus" "grid[i, -j]";
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"