Merge master into the arity versions lane.

This commit is contained in:
Joseph Ferano 2026-09-26 17:18:24 +07:00
commit 3f59c18257
17 changed files with 2900 additions and 2501 deletions

View File

@ -15,6 +15,11 @@ Found porting the prelude (2026-09-26): =declare= has no statement form; a quasi
in the middle of an expression needs =quasiquote(...)=; =if not x= plus a block reads as
=when=, so a template wanting =(if c (do …))= keeps call syntax; a spliced let binding
list needs =let([…]):=. Each needs a .fln spelling now that .fln is the only syntax.
** DONE x |> f(a) is f(x, a)
CLOSED: [2026-09-26]
Decided (137): the pipe feeds the first argument, as Elixir's does, since Flan puts the
thing operated on first. Rules out F#'s last-argument pipe. Below =or=; the right side is
a name or a call to one, and the reader writes the plain call.
** CANCELLED rand-int returns i64
Decided 2026-09-26 (134): rand-int stays u64; write rand-int-range(lo, hi) for a signed
range.
@ -33,6 +38,7 @@ prelude and vendor rewritten in .fln; tests and examples converted with =flan co
then =flan convert=, the .flan source path and =flan-mode= removed. Nothing tracks what
.flan can no longer say. Step 1 is done: the name rule with the =is-=/=has-= renames
through the prelude, vendor and raylib, and =T?=, =??=, =x!=, =if let g = x= and =a?.b=.
Step 2's prelude is done: =lib/prelude.fln=, read by the indented reader.
** DONE x? tests presence, and narrows
CLOSED: [2026-09-26]
Decided (133): =x?= is a bool; =if x?=, =elif x?=, =while x?= and the rest of an =and=

View File

@ -7527,7 +7527,7 @@ prints freed memory.
## String is a prelude struct, and the checker is its wall
`String` is `(defstruct String [bytes (Vec u8)])` in `lib/prelude.ml`, so its
`String` is `struct String(bytes: Vec(u8))` in `lib/prelude.fln`, so its
allocator, free, retry on exhaustion and dev-registry notes are the Vec's and
neither backend has a String of its own. What makes it always valid UTF-8 is
`lib/check.ml`'s String section: outside the prelude the field and the

View File

@ -166,7 +166,7 @@ often `pause' itself, and `P' shows them.")
(defconst flan-cnr-breakpoint "Pause"
"The condition `(pause)' signals.
`lib/prelude.ml' writes it as an ordinary struct under a `restart-case', so
`lib/prelude.fln' writes it as an ordinary struct under a `restart-case', so
nothing in the compiler knows a breakpoint from an error and this buffer is the
first place that can tell the difference. Named here rather than spelled at
its use, because it is a fact about the prelude.")

View File

@ -77,7 +77,7 @@ fine here. Brackets and strings are still paired."
;; that starts with one, or follows a line that ends with one, continues the
;; line above.
(defconst flan-fln--binops
'("or" "and" "==" "!=" "??" "<" "<=" ">" ">=" "||" "^^" "&&" "<<" ">>" "+" "-"
'("|>" "or" "and" "==" "!=" "??" "<" "<=" ">" ">=" "||" "^^" "&&" "<<" ">>" "+" "-"
"*" "/" "%"))
(defconst flan-fln--binop-re (regexp-opt flan-fln--binops))
@ -1492,12 +1492,36 @@ lambda's block: it ends that block, and goes to the opening line's column."
((save-excursion (back-to-indentation) (looking-at flan-fln--clause-re))
(or (flan-fln--clause-columns (match-string-no-properties 1) (point))
(flan-fln--block-levels (point))))
((flan-fln--starts-with-pipe-p (point))
(flan-fln--pipe-columns prev))
((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--block-levels (point))))))))))
(defun flan-fln--starts-with-pipe-p (pos)
"Non-nil if POS's line starts with the pipe `|>'."
(save-excursion
(goto-char pos)
(back-to-indentation)
(looking-at "|>\\(?:[ \t]\\|$\\)")))
(defun flan-fln--pipe-columns (prev)
"Columns for a line starting with `|>' whose code line above is PREV.
Under the pipe above it when PREV starts with one. Else under the value
PREV's statement binds -- `let t = x', then `|> f()' under the `x' -- and
one level in from the statement."
(if (flan-fln--starts-with-pipe-p prev)
(list (flan-fln--indent-at prev))
(let* ((start (flan-fln--logical-start prev))
(in (+ (flan-fln--indent-at start) flan-fln-indent-offset))
(v (flan-fln--value-start start))
(vcol (and v
(< v (save-excursion (goto-char start) (line-end-position)))
(save-excursion (goto-char v) (current-column)))))
(if (and vcol (> vcol in)) (list vcol in) (list in)))))
(defun flan-fln-indent-line ()
"Indent the line to a block column.
A line with text at a valid column stays there: its column is its meaning,
@ -1891,6 +1915,8 @@ lambda or a `Fn(...)' type, and not after a match arm's."
;; The optional marks: `x ?? d', the unwrap of `x!' and the `?' of a
;; chain, `a?.b' and `a?[i]'. A type's `T?' stays the type's.
("[ \t]\\(\\?\\?\\)[ \t]" 1 font-lock-keyword-face)
;; The pipe, `x |> f(a)', inside a line or starting one.
("\\(?:^[ \t]*\\|[ \t]\\)\\(|>\\)\\(?:[ \t]\\|$\\)" 1 font-lock-keyword-face)
("[])[:alnum:]_]\\(!\\)\\(?:[^=]\\|$\\)" 1 font-lock-keyword-face)
("[])[:alnum:]_]\\(\\?\\)[.[]" 1 font-lock-keyword-face)
(,(concat "\\_<" (regexp-opt flan--constants t) "\\_>")

View File

@ -1528,7 +1528,7 @@ definition, and RET on a frame or on the stop in the break buffer."
(user-error "flan: the daemon gave %s an unreadable location: %s"
subject loc))
((string-match-p "\\`<.*>\\'" (nth 0 parts))
;; The prelude is a string inside the compiler (lib/prelude.ml) and
;; The prelude is a string inside the compiler (lib/prelude.fln) and
;; names itself <prelude>; anything in angle brackets is a placeholder
;; the frontend made up, not a path.
(user-error "flan: %s is defined in %s, which is not a file on disk"

View File

@ -950,6 +950,31 @@ defconst(k, 3)
(forward-line 2)
(test-flan--check "a line starting with ?? continues"
(flan-fln--continuation-p (point))))
;; The pipe: a line starting with `|>' goes under the value the statement
;; binds, then under the pipe above it. The first `|' of each text is point.
(test-flan-fln--is "a first |> goes under the let's value"
(test-flan-fln--tabs "let total = get(g)\n||> f()" 1) 12)
(test-flan-fln--is "and a second TAB one level in from the let"
(test-flan-fln--tabs "let total = get(g)\n||> f()" 2) 2)
(with-temp-buffer
(insert "let total = get(g)\n |> f()\n|> g()")
(flan-fln-mode)
(flan-fln-indent-line)
(test-flan-fln--is "a |> under a |> lines up with it" (current-indentation) 12))
(test-flan-fln--is "a |> under a bare statement is one level in"
(test-flan-fln--tabs "fn f() -> ()\n get(g)\n||> f()" 1) 4)
(test-flan-fln--in "|let y = x |> f()\nz = a\n |> g\nw = b |>\n h\n"
(font-lock-ensure)
(test-flan-fln--is "a |> inside a line is marked"
(save-excursion (search-forward "x |>") (get-text-property (- (point) 2) 'face))
'font-lock-keyword-face)
(test-flan-fln--is "a |> starting a line is marked"
(save-excursion (search-forward " |>") (get-text-property (- (point) 2) 'face))
'font-lock-keyword-face)
(forward-line 2)
(test-flan--check "a line starting with |> continues" (flan-fln--continuation-p (point)))
(forward-line 2)
(test-flan--check "a line after a trailing |> continues it" (flan-fln--continuation-p (point))))
(test-flan-fln--is "else goes to its if's column, whatever the depth"
(test-flan-fln--tabs "if a\n if b\n c\n |else" 1) 2)
(test-flan-fln--is "and a second TAB to the outer if's"

View File

@ -179,7 +179,7 @@ let add (t : t) ~qualify forms =
adds nothing. *)
let table ?file (forms : Form.t list) : t =
let t = create () in
let prelude = try Reader.read_all ~file:Prelude.file Prelude.source with _ -> [] in
let prelude = try Prelude.read () with _ -> [] in
add t ~qualify:Fun.id prelude;
(match file with
| None -> ()

View File

@ -64,3 +64,17 @@
(echo "|c}\n\nlet agent_source = {c|\n")
(cat %{workspace_root}/vendor/agent/flan_agent.c)
(echo "|c}\n")))))
; The prelude, carried the same way: lib/prelude.fln is the source, read by
; the indented reader at every build (lib/prelude.ml). No newline after the
; opening quote, so a location's line is the file's line.
(rule
(target prelude_src.ml)
(deps prelude.fln)
(action
(with-stdout-to
prelude_src.ml
(progn
(echo "let source = {fln|")
(cat prelude.fln)
(echo "|fln}\n")))))

View File

@ -479,7 +479,7 @@ and list f h args =
((if t <> "" && t.[0] = '~' then "~(" ^ t ^ ")" else "~" ^ t), 13)
| Form.Sym "unquote-splicing", [ x ] -> ("~@" ^ at 13 x, 13)
| Form.Sym s, _ :: _ :: _
when R.is_binop (infix_op s) && s <> "==" && s <> "??"
when R.is_binop (infix_op s) && s <> "==" && s <> "??" && s <> "|>"
&& not (s = "!=" && List.length args > 2) ->
let op = infix_op s in
let lvl = Option.get (R.binop_level op) in

View File

@ -57,10 +57,21 @@ let binops =
("==", 4); ("!=", 4); ("<", 4); ("<=", 4); (">", 4); (">=", 4);
("||", 5); ("^^", 6); ("&&", 7);
("<<", 8); (">>", 8); ("+", 9); ("-", 9); ("*", 10); ("/", 10); ("%", 10);
(* [??] is here for the line rules — a line ending in it, or one starting
with it, continues — and is read by [operand] between 4 and 5, so no
level of [binary]'s is its own. *)
("??", 0) ]
(* [??] and [|>] are here for the line rules — a line ending in one, or
starting with one, continues. [operand] reads [??] between 4 and 5,
and [binary] reads [|>] below [or]; no level of [binary_]'s climb is
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
@ -1066,7 +1077,107 @@ let and_rewrite (xs : Form.t list) ops =
separated only by whitespace. *)
let rec expr p : Form.t * int = binary p 1
(* [x |> f(a)] is [f(x, a)] and [x |> f] is [f(x)] (decision 137): the value
is the first argument, as Flan puts the thing operated on first. It is
below [or], so each side is a whole level-1 expression, and it reads to
the plain call, so nothing after the reader knows a pipe was written.
The right side is a name or a call to one and nothing else: those are the
only shapes the first-argument rewrite has one meaning for. *)
and binary p lvl : Form.t * int =
if lvl <> 1 then binary_ p lvl
else
let l0 = (peek p).loc in
let ((first, _) as fp) = binary_ p 1 in
let rec more (x : Form.t) =
match (peek p).tok with
| NAME "|>" when not ((peek_at p 1).tok = LP && not (peek_at p 1).sp) ->
let ot = advance p in
if not (ot.sp && (peek p).sp) then
failk "unspaced-operator" ot.loc
"|> is an operator here, and a binary operator has a space on each \
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
in
let glued_paren () =
i0 + 1 < Array.length p.toks
&& p.toks.(i0 + 1).tok = LP && not p.toks.(i0 + 1).sp
in
let call =
match t0.tok, rhs.v with
| NAME _, Form.Sym _ when rl = 13 && p.i = i0 + 1 -> Some (rhs, [])
| NAME _, Form.List (({ v = Form.Sym _; _ } as h) :: args)
when rl = 12 && head_at h && (last p).tok = RP && glued_paren () ->
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 \
neither. The value on the left becomes the first argument:\n\n\
\ x |> f(a, b) is f(x, a, b)\n\
\ x |> f is f(x)"
(if t0.tok = LP && rl = 13 then "(" ^ text_of rhs ^ ")"
else text_of rhs))
| _ -> x
in
let r = more first in
if r == first then fp else (r, 1)
and binary_ p lvl : Form.t * int =
if lvl = 3 then not_ p
else if lvl > 10 then unary p
else
@ -1447,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

2539
lib/prelude.fln Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,8 @@
([Reader]). Both give the same [Form.t], so nothing past this point knows
which one a file was written in, and a program may mix them freely.
Only program sources come through here. The prelude, the wire protocol and
Only program sources come through here. The prelude is indented text
embedded in the compiler and read by [Prelude.read]; the wire protocol and
the registry's spellings are paren text the compiler writes itself, and
read it with [Reader] directly. *)

View File

@ -122,7 +122,7 @@ Each item: the proposal, then the reason in one line.
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
operator (`+`, `and`, `==`, `|>`, …) continues the previous line; so does a line
after one that ends in a spaced infix operator. (F# `LexFilter.fs` 360-380,
1850-1870, 2345-2360.) No `\` continuation. **Built** (`=` does not
continue: `let x =` plus a block is a block value). A continuation line must
@ -163,11 +163,38 @@ Each item: the proposal, then the reason in one line.
### Expressions
- **Precedence**, low to high: `or` < `and` < `not` < comparisons
- **Precedence**, low to high: `|>` < `or` < `and` < `not` < comparisons
(`== != < <= > >=`) < `??` < `||` < `^^` < `&&` < `<< >>` < `+ -` < `* / %` <
prefix `-` and `~~` < postfix (call, index, field, `!`, `?`, `?.`). **Built.** An operator
glued to `(` is always a call. The bit operators sit where Python and Rust
put them, so `x && mask == 0` is `(x && mask) == 0`.
- **The pipe** (decision 137): `x |> f(a, b)` reads `(f x a b)`; `x |> f()`
and `x |> f` read `(f x)`. The value on the left is the first argument. A
chain runs left to right: `get(grid, r, c) |> or-else(empty) |> is-empty-cell()`
reads `(is-empty-cell (or-else (get grid r c) empty))`. The name may be
qualified, `x |> m/f(a)`, and the call may take a block, `xs |> each():`.
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)`. 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)`, 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)
|> or-else(empty)
|> is-empty-cell()
```
The reader writes the plain call, so nothing after it knows a pipe was
written. `|>(a, b)`, glued, is a call to a function named `|>`. **Built.**
- **The bit operators** are `a && b`, `a || b`, `a ^^ b` and `~~a`, reading
`(bit-and a b)`, `(bit-or a b)`, `(bit-xor a b)` and `(bit-not a)`. They take
integers; `and`, `or` and `not` are the logical ones. `~~` is one token, so a

42
test/programs/pipe.fln Normal file
View File

@ -0,0 +1,42 @@
;; The pipe x |> f(a) is f(x, a): the value on the left is the first argument,
;; evaluated before the others. Typed, dyn, chained, multi-line and qualified.
import shape "pkgs/shape"
import area "pkgs/area"
fn add(a: i32, b: i32) -> i32 = a + b
fn scale(a: i32, k: i32) -> i32 = a * k
fn twice(a: i32) -> i32 = a * 2
fn sub3(a: i32, b: i32, c: i32) -> i32 = a - b - c
;; Prints its argument, to show the order the arguments run in.
fn note(n: i32) -> i32
println(n)
n
fn held-or(o: i32?, d: i32) -> i32 = o ?? d
fn bump(x) = x + 1
fn main()
println(3 |> add(4))
println(3 |> twice)
println(3 |> twice())
println(1 |> add(2) |> scale(10) |> twice)
;; Below or and the arithmetic: the whole left side is piped.
println(2 + 3 |> scale(2))
let n: i32? = None
println(n ?? 4 |> twice)
println(1 == 1 |> not)
let total = 10
|> add(5)
|> scale(2)
println(total)
let other = 10 |>
sub3(1, 2)
println(other)
println(Some(7) |> held-or(0), n |> held-or(-1))
println(shape/box(3, 2) |> area/of)
println(5 |> bump |> bump)
;; The left side runs first, then the other arguments in order: 1 2 3.
println(note(1) |> sub3(note(2), note(3)))

View File

@ -2284,6 +2284,12 @@ let () =
[ ("optionals.fln", optionals_out); ("optionals-dyn.fln", optionals_dyn_out);
(* x? tests and narrows, e? as g names what it found (decision 133). *)
("presence.fln", "true false true\n6\n-1\n3\n101 209 0\n11\n42\n2\nabsent\n6\nfalse true\n3\n6\n15\n"); ("presence-dyn.fln", "true false\n103 209 0\nno pet\nann\n3 2\n") ];
(* The pipe (decision 137): chains, multi-line, qualified, dyn, and the
left side run before the other arguments (the last 1 2 3). *)
(let want = "7\n6\n6\n60\n10\n8\nfalse\n30\n7\n7 -1\n6\n7\n1\n2\n3\n-4\n" in
outputs "pipe.fln" "programs/pipe.fln" want;
outputs ~opt:"-O0" "pipe.fln, -O0" "programs/pipe.fln" want;
outputs ~x86:true "pipe.fln, --x86" "programs/pipe.fln" want);
(* x! over nothing traps at its site and names the expression. *)
List.iter
(fun (x86, arg, want) ->

View File

@ -1417,6 +1417,61 @@ let () =
reads "while e? as g" "while pop(s)? as x\n f(x)"
"(while true (if-let [x (pop s)] (do (f x)) (break)))";
refuses "as after no test" "if x as y\n y" "indent/as-test" "Write x? as name";
(* Decision 137: x |> f(a) is f(x, a), below or. *)
reads "|> into a call" "y = x |> f(1, 2)" "(set y (f x 1 2))";
reads "|> into an empty call" "y = x |> f()" "(set y (f x))";
reads "|> into a name" "y = x |> f" "(set y (f x))";
reads "|> into a case" "y = x |> Some" "(set y (Some x))";
reads "|> chains left to right"
"y = get(grid, r, c) |> or-else(empty) |> is-empty-cell()"
"(set y (is-empty-cell (or-else (get grid r c) empty)))";
reads "|> into a qualified name" "y = x |> m/f(a) |> m/g" "(set y (m/g (m/f x a)))";
reads "|> is below arithmetic" "y = a + 1 |> f()" "(set y (f (+ a 1)))";
reads "|> is below ??" "y = a ?? b |> f()" "(set y (f (?? a b)))";
reads "|> is below comparisons" "y = a == b |> f()" "(set y (f (= a b)))";
reads "|> is below not" "y = not x |> f()" "(set y (f (not x)))";
reads "|> is below or" "y = a or b and c |> f()" "(set y (f (or a (and b c))))";
reads "|> in an argument" "g(x |> f, 2)" "(g (f x) 2)";
reads "|> in a condition" "if x |> is-empty() then 1 else 2" "(if (is-empty x) 1 2)";
reads "|> starting lines" "let y = get(g, r)\n |> or-else(0)\n |> f()\ny"
"(def y dyn (f (or-else (get g r) 0)))\ny";
reads "|> ending a line" "let y = get(g, r) |>\n or-else(0)\ny"
"(def y dyn (or-else (get g r) 0))\ny";
reads "|> under a statement" "x\n |> f(1)\n |> g" "(g (f x 1))";
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" "\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-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 |>";
refuses "|> in a list separated by spaces" "y = [x |> f() 2]" "indent/separate-elements" "commas";
(match read "y = |>(a, b)" with
| forms ->
let got = Indent_printer.program ~source:"y = |>(a, b)" forms in
if not (Test_support.contains got "|>(a, b)") then
fail "a call to |> prints as a call: %S" got
| exception e -> fail "a call to |> prints as a call: %s" (diag_text e));
refused "present-i32.fln" "fn main()\n let x = 5\n println(x?)\n"
[ "x is i32, which always holds a value, so x? has nothing to test";
"a yes-or-no name starts with is- or has-, as in is-x" ];