A pipe into a language word is refused, and a pipe the next operator or an as swallows is refused with the bracketed form as the fix.
This commit is contained in:
parent
5a3d480edf
commit
0c7a53f526
@ -63,6 +63,16 @@ let binops =
|
||||
their own. *)
|
||||
("??", 0); ("|>", 0) ]
|
||||
|
||||
(* The words a statement, clause or declaration starts with, which name no
|
||||
function and so are refused on the right of [|>]. *)
|
||||
let pipe_words =
|
||||
[ "fn"; "fn-"; "def"; "once"; "const"; "struct"; "union"; "data"; "enum";
|
||||
"import"; "if"; "when"; "while"; "until"; "match"; "let"; "for"; "macro";
|
||||
"class"; "generic"; "multi"; "method"; "type"; "return"; "break";
|
||||
"continue"; "defer"; "handler-case"; "handler-bind"; "restart-case";
|
||||
"quote"; "then"; "else"; "elif"; "as"; "in"; "where"; "and"; "or"; "on";
|
||||
"restart" ]
|
||||
|
||||
let binop_level s = List.assoc_opt s binops
|
||||
let is_binop s = binop_level s <> None
|
||||
|
||||
@ -1088,6 +1098,14 @@ and binary p lvl : Form.t * int =
|
||||
side: x |> f(a)";
|
||||
let i0 = p.i in
|
||||
let t0 = p.toks.(i0) in
|
||||
(match t0.tok with
|
||||
| NAME w when List.mem w pipe_words ->
|
||||
failk "pipe-keyword" t0.loc
|
||||
"%s is a word of the language, not a function, so it cannot be \
|
||||
the right side of |>. The right side is a function name or a \
|
||||
call: x |> f(a)"
|
||||
w
|
||||
| _ -> ());
|
||||
let rhs, rl = binary_ p 1 in
|
||||
let head_at (h : Form.t) =
|
||||
h.loc.Loc.line = t0.loc.Loc.line && h.loc.Loc.col = t0.loc.Loc.col
|
||||
@ -1104,8 +1122,48 @@ and binary p lvl : Form.t * int =
|
||||
Some (h, args)
|
||||
| _ -> None
|
||||
in
|
||||
(* [a |> f ?? d] reads [a |> (f ?? d)]: the pipe is loosest. When the
|
||||
leftmost operand of the right side is a target, the likely meaning
|
||||
is the pipe's result under the operator, so the refusal says how
|
||||
to write that. *)
|
||||
(* An operator's form takes its first operand's location, so the
|
||||
head's name is matched against the text too. *)
|
||||
let target_at (f : Form.t) =
|
||||
let starts s pre =
|
||||
String.length s >= String.length pre
|
||||
&& String.sub s 0 (String.length pre) = pre
|
||||
in
|
||||
match f.v, t0.tok with
|
||||
| Form.Sym s, NAME n -> head_at f && s = n && text_of f = n
|
||||
| Form.List (({ v = Form.Sym s; _ } as h) :: _), NAME n ->
|
||||
head_at h && s = n && starts (text_of f) (n ^ "(")
|
||||
| _ -> false
|
||||
in
|
||||
let rec leftmost (f : Form.t) =
|
||||
if target_at f then Some f
|
||||
else match f.v with
|
||||
| Form.List (_ :: a :: _) -> leftmost a
|
||||
| _ -> None
|
||||
in
|
||||
let bracketed () =
|
||||
match leftmost rhs with
|
||||
| None -> None
|
||||
| Some f ->
|
||||
let rt = text_of rhs and ft = text_of f in
|
||||
let n = String.length ft in
|
||||
if String.length rt > n && String.sub rt 0 n = ft then
|
||||
Some (Printf.sprintf "(%s |> %s)%s" (text_of x) ft
|
||||
(String.sub rt n (String.length rt - n)))
|
||||
else None
|
||||
in
|
||||
(match call with
|
||||
| Some (h, args) -> more (mk p l0 (Form.List (h :: x :: args)))
|
||||
| None when bracketed () <> None ->
|
||||
failk "pipe-target" rhs.loc
|
||||
"|> takes the whole expression on its right, so this is %s |> (%s), \
|
||||
and %s is not a function name or a call. To use the pipe's \
|
||||
result there, put the pipe in parentheses:\n\n %s"
|
||||
(text_of x) (text_of rhs) (text_of rhs) (Option.get (bracketed ()))
|
||||
| None ->
|
||||
failk "pipe-target" rhs.loc
|
||||
"the right side of |> is a function name or a call, and %s is \
|
||||
@ -1500,9 +1558,27 @@ and as_head p (c : Form.t) =
|
||||
in
|
||||
Some (Form.make (Form.Vec [ g; e ]) c.loc)
|
||||
| _ ->
|
||||
(* [a |> f? as g] would test [f] alone: a compound test is bracketed. *)
|
||||
let t = text_of c in
|
||||
let compound =
|
||||
let depth = ref 0 and quoted = ref false and hit = ref false in
|
||||
String.iteri
|
||||
(fun i ch ->
|
||||
if !quoted then
|
||||
(if ch = '"' && (i = 0 || t.[i - 1] <> '\\') then quoted := false)
|
||||
else
|
||||
match ch with
|
||||
| '"' -> quoted := true
|
||||
| '(' | '[' | '{' -> incr depth
|
||||
| ')' | ']' | '}' -> decr depth
|
||||
| ' ' when !depth = 0 -> hit := true
|
||||
| _ -> ())
|
||||
t;
|
||||
!hit
|
||||
in
|
||||
failk "as-test" at.loc
|
||||
"as names what a test found, and %s is not one. Write %s? as name"
|
||||
(text_of c) (text_of c))
|
||||
t (if compound then "(" ^ t ^ ")" else t))
|
||||
| _ -> None
|
||||
|
||||
(* The if an [if let] head was read into, rewritten to (if-let [P v] then
|
||||
|
||||
@ -176,11 +176,16 @@ Each item: the proposal, then the reason in one line.
|
||||
It is the loosest operator, so each side is a whole expression:
|
||||
`a + 1 |> f()` is `f(a + 1)`, `a ?? b |> f()` is `f(a ?? b)`,
|
||||
`a == b |> f()` is `f(a == b)`, `not x |> f()` is `f(not x)` and
|
||||
`a or b |> f()` is `f(a or b)`. The left side is evaluated once, before
|
||||
the call's other arguments, as the first argument of any call is. The
|
||||
`a or b |> f()` is `f(a or b)`. When the right side names a function, the
|
||||
left side is evaluated once, before the call's other arguments. A macro
|
||||
receives the left side unevaluated as its first argument, as it would in
|
||||
the written call: `x |> set(5)` is `set(x, 5)`, which assigns to `x`. The
|
||||
right side is a name or a call to one; anything else — `x |> 3`,
|
||||
`x |> a + b`, `x |> a.b`, `x |> f(a).x`, `x |> f(a)(b)` — is refused. A
|
||||
pipe line under a statement is indented past it:
|
||||
`x |> a + b`, `x |> a.b`, `x |> f(a).x`, `x |> f(a)(b)`, a word such as
|
||||
`if`, `let` or `fn` — is refused. Since the pipe binds loosest,
|
||||
`a |> f ?? d` is `a |> (f ?? d)` and is refused with the bracketed form
|
||||
`(a |> f) ?? d` as the fix; likewise a test, `(a |> f)? as v`. A pipe line
|
||||
under a statement is indented past it:
|
||||
|
||||
```
|
||||
let cell = get(grid, r, c)
|
||||
|
||||
@ -1441,12 +1441,27 @@ let () =
|
||||
reads "|> into a call with a block" "xs |> each(1):\n print(2)" "(each xs 1 (print 2))";
|
||||
reads "|>( is a call" "y = |>(a, b)" "(set y (|> a b))";
|
||||
refuses "|> into a number" "y = x |> 3" "indent/pipe-target" "3 is neither";
|
||||
refuses "|> into arithmetic" "y = x |> a + b" "indent/pipe-target" "a + b is neither";
|
||||
refuses "|> into a field" "y = x |> a.b" "indent/pipe-target" "a.b is neither";
|
||||
refuses "|> into arithmetic" "y = x |> a + b" "indent/pipe-target" "\n (x |> a) + b";
|
||||
refuses "|> into a field" "y = x |> a.b" "indent/pipe-target" "(x |> a).b";
|
||||
refuses "|> into a field of a call" "y = x |> f(a).x" "indent/pipe-target" "f(a).x";
|
||||
refuses "|> into a call's result" "y = x |> f(a)(b)" "indent/pipe-target" "f(a)(b)";
|
||||
refuses "|> into an index" "y = x |> a[1]" "indent/pipe-target" "a[1]";
|
||||
refuses "|> into a lambda" "y = x |> fn(a) => g(a)" "indent/pipe-target" "is neither";
|
||||
refuses "|> into a lambda" "y = x |> fn(a) => g(a)" "indent/pipe-keyword" "fn is a word";
|
||||
refuses "|> into if" "y = 1 |> if" "indent/pipe-keyword" "if is a word";
|
||||
refuses "|> into let" "y = 1 |> let" "indent/pipe-keyword" "let is a word";
|
||||
refuses "|> into fn" "y = 1 |> fn" "indent/pipe-keyword" "fn is a word";
|
||||
reads "|> into not" "y = x |> not" "(set y (not x))";
|
||||
refuses "|> before ??" "y = a |> f ?? d" "indent/pipe-target" "\n (a |> f) ?? d";
|
||||
refuses "|> before or" "y = a |> f or c" "indent/pipe-target" "\n (a |> f) or c";
|
||||
refuses "|> before a test" "y = a |> f?" "indent/pipe-target" "\n (a |> f)?";
|
||||
refuses "|> chained before ==" "y = a |> b |> f(1) == 2" "indent/pipe-target"
|
||||
"\n (a |> b |> f(1)) == 2";
|
||||
refuses "|> before as" "if a |> f(1) as v\n v" "indent/as-test" "Write (a |> f(1))? as name";
|
||||
refuses "|> before as over a pattern" "if a |> f as Some(v)\n v" "indent/as-test"
|
||||
"Write (a |> f)? as name";
|
||||
refuses "a call before as is not bracketed" "if f(x) as v\n v" "indent/as-test"
|
||||
"Write f(x)? as name";
|
||||
reads "|> into a macro passes the place" "y = x |> set(5)" "(set y (set x 5))";
|
||||
refuses "|> into parentheses" "y = x |> (f)" "indent/pipe-target" "(f) is neither";
|
||||
refuses "|> unspaced on the right" "y = x |>[f]" "indent/unspaced-operator" "x |> f(a)";
|
||||
refuses "|> at a statement's column" "f(x)\n|> g()" "indent/continuation" "starts with the operator |>";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user