Merge master into the arity versions lane.
This commit is contained in:
commit
3f59c18257
6
TODO.org
6
TODO.org
@ -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
|
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
|
=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.
|
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
|
** CANCELLED rand-int returns i64
|
||||||
Decided 2026-09-26 (134): rand-int stays u64; write rand-int-range(lo, hi) for a signed
|
Decided 2026-09-26 (134): rand-int stays u64; write rand-int-range(lo, hi) for a signed
|
||||||
range.
|
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
|
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
|
.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=.
|
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
|
** DONE x? tests presence, and narrows
|
||||||
CLOSED: [2026-09-26]
|
CLOSED: [2026-09-26]
|
||||||
Decided (133): =x?= is a bool; =if x?=, =elif x?=, =while x?= and the rest of an =and=
|
Decided (133): =x?= is a bool; =if x?=, =elif x?=, =while x?= and the rest of an =and=
|
||||||
|
|||||||
@ -7527,7 +7527,7 @@ prints freed memory.
|
|||||||
|
|
||||||
## String is a prelude struct, and the checker is its wall
|
## 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
|
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
|
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
|
`lib/check.ml`'s String section: outside the prelude the field and the
|
||||||
|
|||||||
@ -166,7 +166,7 @@ often `pause' itself, and `P' shows them.")
|
|||||||
|
|
||||||
(defconst flan-cnr-breakpoint "Pause"
|
(defconst flan-cnr-breakpoint "Pause"
|
||||||
"The condition `(pause)' signals.
|
"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
|
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
|
first place that can tell the difference. Named here rather than spelled at
|
||||||
its use, because it is a fact about the prelude.")
|
its use, because it is a fact about the prelude.")
|
||||||
|
|||||||
@ -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
|
;; that starts with one, or follows a line that ends with one, continues the
|
||||||
;; line above.
|
;; line above.
|
||||||
(defconst flan-fln--binops
|
(defconst flan-fln--binops
|
||||||
'("or" "and" "==" "!=" "??" "<" "<=" ">" ">=" "||" "^^" "&&" "<<" ">>" "+" "-"
|
'("|>" "or" "and" "==" "!=" "??" "<" "<=" ">" ">=" "||" "^^" "&&" "<<" ">>" "+" "-"
|
||||||
"*" "/" "%"))
|
"*" "/" "%"))
|
||||||
|
|
||||||
(defconst flan-fln--binop-re (regexp-opt flan-fln--binops))
|
(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))
|
((save-excursion (back-to-indentation) (looking-at flan-fln--clause-re))
|
||||||
(or (flan-fln--clause-columns (match-string-no-properties 1) (point))
|
(or (flan-fln--clause-columns (match-string-no-properties 1) (point))
|
||||||
(flan-fln--block-levels (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))
|
((or (flan-fln--starts-with-op-p (point))
|
||||||
(flan-fln--ends-in-op-p prev))
|
(flan-fln--ends-in-op-p prev))
|
||||||
(list (+ (flan-fln--indent-at (flan-fln--logical-start prev))
|
(list (+ (flan-fln--indent-at (flan-fln--logical-start prev))
|
||||||
flan-fln-indent-offset)))
|
flan-fln-indent-offset)))
|
||||||
(t (flan-fln--block-levels (point))))))))))
|
(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 ()
|
(defun flan-fln-indent-line ()
|
||||||
"Indent the line to a block column.
|
"Indent the line to a block column.
|
||||||
A line with text at a valid column stays there: its column is its meaning,
|
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
|
;; 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.
|
;; chain, `a?.b' and `a?[i]'. A type's `T?' stays the type's.
|
||||||
("[ \t]\\(\\?\\?\\)[ \t]" 1 font-lock-keyword-face)
|
("[ \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)
|
||||||
("[])[:alnum:]_]\\(\\?\\)[.[]" 1 font-lock-keyword-face)
|
("[])[:alnum:]_]\\(\\?\\)[.[]" 1 font-lock-keyword-face)
|
||||||
(,(concat "\\_<" (regexp-opt flan--constants t) "\\_>")
|
(,(concat "\\_<" (regexp-opt flan--constants t) "\\_>")
|
||||||
|
|||||||
@ -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"
|
(user-error "flan: the daemon gave %s an unreadable location: %s"
|
||||||
subject loc))
|
subject loc))
|
||||||
((string-match-p "\\`<.*>\\'" (nth 0 parts))
|
((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
|
;; names itself <prelude>; anything in angle brackets is a placeholder
|
||||||
;; the frontend made up, not a path.
|
;; the frontend made up, not a path.
|
||||||
(user-error "flan: %s is defined in %s, which is not a file on disk"
|
(user-error "flan: %s is defined in %s, which is not a file on disk"
|
||||||
|
|||||||
@ -950,6 +950,31 @@ defconst(k, 3)
|
|||||||
(forward-line 2)
|
(forward-line 2)
|
||||||
(test-flan--check "a line starting with ?? continues"
|
(test-flan--check "a line starting with ?? continues"
|
||||||
(flan-fln--continuation-p (point))))
|
(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--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--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"
|
(test-flan-fln--is "and a second TAB to the outer if's"
|
||||||
|
|||||||
@ -179,7 +179,7 @@ let add (t : t) ~qualify forms =
|
|||||||
adds nothing. *)
|
adds nothing. *)
|
||||||
let table ?file (forms : Form.t list) : t =
|
let table ?file (forms : Form.t list) : t =
|
||||||
let t = create () in
|
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;
|
add t ~qualify:Fun.id prelude;
|
||||||
(match file with
|
(match file with
|
||||||
| None -> ()
|
| None -> ()
|
||||||
|
|||||||
14
lib/dune
14
lib/dune
@ -64,3 +64,17 @@
|
|||||||
(echo "|c}\n\nlet agent_source = {c|\n")
|
(echo "|c}\n\nlet agent_source = {c|\n")
|
||||||
(cat %{workspace_root}/vendor/agent/flan_agent.c)
|
(cat %{workspace_root}/vendor/agent/flan_agent.c)
|
||||||
(echo "|c}\n")))))
|
(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")))))
|
||||||
|
|||||||
@ -479,7 +479,7 @@ and list f h args =
|
|||||||
((if t <> "" && t.[0] = '~' then "~(" ^ t ^ ")" else "~" ^ t), 13)
|
((if t <> "" && t.[0] = '~' then "~(" ^ t ^ ")" else "~" ^ t), 13)
|
||||||
| Form.Sym "unquote-splicing", [ x ] -> ("~@" ^ at 13 x, 13)
|
| Form.Sym "unquote-splicing", [ x ] -> ("~@" ^ at 13 x, 13)
|
||||||
| Form.Sym s, _ :: _ :: _
|
| 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) ->
|
&& not (s = "!=" && List.length args > 2) ->
|
||||||
let op = infix_op s in
|
let op = infix_op s in
|
||||||
let lvl = Option.get (R.binop_level op) in
|
let lvl = Option.get (R.binop_level op) in
|
||||||
|
|||||||
@ -57,10 +57,21 @@ let binops =
|
|||||||
("==", 4); ("!=", 4); ("<", 4); ("<=", 4); (">", 4); (">=", 4);
|
("==", 4); ("!=", 4); ("<", 4); ("<=", 4); (">", 4); (">=", 4);
|
||||||
("||", 5); ("^^", 6); ("&&", 7);
|
("||", 5); ("^^", 6); ("&&", 7);
|
||||||
("<<", 8); (">>", 8); ("+", 9); ("-", 9); ("*", 10); ("/", 10); ("%", 10);
|
("<<", 8); (">>", 8); ("+", 9); ("-", 9); ("*", 10); ("/", 10); ("%", 10);
|
||||||
(* [??] is here for the line rules — a line ending in it, or one starting
|
(* [??] and [|>] are here for the line rules — a line ending in one, or
|
||||||
with it, continues — and is read by [operand] between 4 and 5, so no
|
starting with one, continues. [operand] reads [??] between 4 and 5,
|
||||||
level of [binary]'s is its own. *)
|
and [binary] reads [|>] below [or]; no level of [binary_]'s climb is
|
||||||
("??", 0) ]
|
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 binop_level s = List.assoc_opt s binops
|
||||||
let is_binop s = binop_level s <> None
|
let is_binop s = binop_level s <> None
|
||||||
@ -1066,7 +1077,107 @@ let and_rewrite (xs : Form.t list) ops =
|
|||||||
separated only by whitespace. *)
|
separated only by whitespace. *)
|
||||||
let rec expr p : Form.t * int = binary p 1
|
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 =
|
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
|
if lvl = 3 then not_ p
|
||||||
else if lvl > 10 then unary p
|
else if lvl > 10 then unary p
|
||||||
else
|
else
|
||||||
@ -1447,9 +1558,27 @@ and as_head p (c : Form.t) =
|
|||||||
in
|
in
|
||||||
Some (Form.make (Form.Vec [ g; e ]) c.loc)
|
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
|
failk "as-test" at.loc
|
||||||
"as names what a test found, and %s is not one. Write %s? as name"
|
"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
|
| _ -> None
|
||||||
|
|
||||||
(* The if an [if let] head was read into, rewritten to (if-let [P v] then
|
(* The if an [if let] head was read into, rewritten to (if-let [P v] then
|
||||||
|
|||||||
2539
lib/prelude.fln
Normal file
2539
lib/prelude.fln
Normal file
File diff suppressed because it is too large
Load Diff
2503
lib/prelude.ml
2503
lib/prelude.ml
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,8 @@
|
|||||||
([Reader]). Both give the same [Form.t], so nothing past this point knows
|
([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.
|
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
|
the registry's spellings are paren text the compiler writes itself, and
|
||||||
read it with [Reader] directly. *)
|
read it with [Reader] directly. *)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
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.*
|
line starts at, and the block ends where the enclosing bracket closes.*
|
||||||
- **Continuation outside brackets:** a line that starts with a spaced infix
|
- **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,
|
after one that ends in a spaced infix operator. (F# `LexFilter.fs` 360-380,
|
||||||
1850-1870, 2345-2360.) No `\` continuation. **Built** (`=` does not
|
1850-1870, 2345-2360.) No `\` continuation. **Built** (`=` does not
|
||||||
continue: `let x =` plus a block is a block value). A continuation line must
|
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
|
### 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
|
prefix `-` and `~~` < postfix (call, index, field, `!`, `?`, `?.`). **Built.** An operator
|
||||||
glued to `(` is always a call. The bit operators sit where Python and Rust
|
glued to `(` is always a call. The bit operators sit where Python and Rust
|
||||||
put them, so `x && mask == 0` is `(x && mask) == 0`.
|
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
|
- **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
|
`(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
|
integers; `and`, `or` and `not` are the logical ones. `~~` is one token, so a
|
||||||
|
|||||||
42
test/programs/pipe.fln
Normal file
42
test/programs/pipe.fln
Normal 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)))
|
||||||
@ -2284,6 +2284,12 @@ let () =
|
|||||||
[ ("optionals.fln", optionals_out); ("optionals-dyn.fln", optionals_dyn_out);
|
[ ("optionals.fln", optionals_out); ("optionals-dyn.fln", optionals_dyn_out);
|
||||||
(* x? tests and narrows, e? as g names what it found (decision 133). *)
|
(* 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") ];
|
("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. *)
|
(* x! over nothing traps at its site and names the expression. *)
|
||||||
List.iter
|
List.iter
|
||||||
(fun (x86, arg, want) ->
|
(fun (x86, arg, want) ->
|
||||||
|
|||||||
@ -1417,6 +1417,61 @@ let () =
|
|||||||
reads "while e? as g" "while pop(s)? as x\n f(x)"
|
reads "while e? as g" "while pop(s)? as x\n f(x)"
|
||||||
"(while true (if-let [x (pop s)] (do (f x)) (break)))";
|
"(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";
|
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"
|
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";
|
[ "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" ];
|
"a yes-or-no name starts with is- or has-, as in is-x" ];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user