In a .fln file a top-level let is a global and def is refused, while a let in a block or an evaluated expression stays local.
This commit is contained in:
parent
cde4f2ab1b
commit
702ebdc9c5
4
TODO.org
4
TODO.org
@ -650,10 +650,6 @@ Decided 2026-09-26: a lambda's body follows ~=>~, and ~=>~ is its only spelling
|
|||||||
takes an indented block even inside brackets, closing where the brackets close:
|
takes an indented block even inside brackets, closing where the brackets close:
|
||||||
~sort-by(xs, fn(a, b) =>~ plus a block.
|
~sort-by(xs, fn(a, b) =>~ plus a block.
|
||||||
|
|
||||||
** NEXT A top-level let is a global
|
|
||||||
Decided 2026-09-26: in .fln ~let x = v~ at column 0 reads ~(def x v)~ and replaces
|
|
||||||
~def~, which is refused with that fix; ~once~ and ~const~ stay.
|
|
||||||
|
|
||||||
** TODO Hard-coded code in messages is still paren syntax in a .fln file
|
** TODO Hard-coded code in messages is still paren syntax in a .fln file
|
||||||
Types follow the code's syntax now (=Types.spell=). Hints written into a message's
|
Types follow the code's syntax now (=Types.spell=). Hints written into a message's
|
||||||
text — =(Ptr %s)=, =(clone v)=, =(the T x)= in most of =check.ml= and =parse.ml=, the
|
text — =(Ptr %s)=, =(clone v)=, =(the T x)= in most of =check.ml= and =parse.ml=, the
|
||||||
|
|||||||
@ -120,7 +120,7 @@ fine here. Brackets and strings are still paired."
|
|||||||
'(("fn" . "defn") ("fn-" . "defn-") ("def" . "def") ("once" . "defonce")
|
'(("fn" . "defn") ("fn-" . "defn-") ("def" . "def") ("once" . "defonce")
|
||||||
("const" . "defconst") ("struct" . "defstruct") ("data" . "defdata")
|
("const" . "defconst") ("struct" . "defstruct") ("data" . "defdata")
|
||||||
("enum" . "defenum") ("union" . "defunion") ("import" . "import")
|
("enum" . "defenum") ("union" . "defunion") ("import" . "import")
|
||||||
("macro" . "defmacro") ("type" . "defalias") ("class" . "defclass")
|
("let" . "def") ("macro" . "defmacro") ("type" . "defalias") ("class" . "defclass")
|
||||||
("generic" . "defgeneric") ("multi" . "defmulti") ("method" . "defmethod"))
|
("generic" . "defgeneric") ("multi" . "defmulti") ("method" . "defmethod"))
|
||||||
"Each declaration header word, and the paren head it reads as.")
|
"Each declaration header word, and the paren head it reads as.")
|
||||||
|
|
||||||
@ -613,7 +613,7 @@ the form."
|
|||||||
(defun flan-fln--declaration-head-at (pos &optional heads)
|
(defun flan-fln--declaration-head-at (pos &optional heads)
|
||||||
"The paren head of the declaration written at POS, or nil.
|
"The paren head of the declaration written at POS, or nil.
|
||||||
The .fln twin of `flan--declaration-head-at': POS must be at column 0 and not
|
The .fln twin of `flan--declaration-head-at': POS must be at column 0 and not
|
||||||
in a string or comment, and the header word -- `fn', `def', `struct', ... --
|
in a string or comment, and the header word -- `fn', `let', `struct', ... --
|
||||||
or the fallback call's name, `defmethod(', must read as one of HEADS,
|
or the fallback call's name, `defmethod(', must read as one of HEADS,
|
||||||
`flan--declaration-heads' by default."
|
`flan--declaration-heads' by default."
|
||||||
(save-excursion
|
(save-excursion
|
||||||
@ -935,8 +935,9 @@ before point. With ARG, stop there instead, as \\[flan-eval-last-sexp] does."
|
|||||||
"Non-nil if START's statement is a `let'.
|
"Non-nil if START's statement is a `let'.
|
||||||
A let takes no block: lines under it are its value's (`= match x', a lambda
|
A let takes no block: lines under it are its value's (`= match x', a lambda
|
||||||
header), and its name lasts to the end of the block it is in."
|
header), and its name lasts to the end of the block it is in."
|
||||||
|
(and (> (flan-fln--indent-at start) 0)
|
||||||
(save-excursion (goto-char (flan-fln--first-char start))
|
(save-excursion (goto-char (flan-fln--first-char start))
|
||||||
(looking-at "let[ \t]")))
|
(looking-at "let[ \t]"))))
|
||||||
|
|
||||||
(defun flan-fln--block-rest (start)
|
(defun flan-fln--block-rest (start)
|
||||||
"START's statement and every statement after it in the same block."
|
"START's statement and every statement after it in the same block."
|
||||||
@ -1174,7 +1175,7 @@ Before it at the same level, else out to the line that owns this block."
|
|||||||
(let ((bol (line-beginning-position)))
|
(let ((bol (line-beginning-position)))
|
||||||
(or (looking-back ":" bol)
|
(or (looking-back ":" bol)
|
||||||
(looking-back "[ \t]->" bol)
|
(looking-back "[ \t]->" bol)
|
||||||
;; `let x =' and `def colors =' with the value as a block,
|
;; `let x =' and `let colors =' at the top level with the value as a block,
|
||||||
;; which the author's list leaves out and the reader reads.
|
;; which the author's list leaves out and the reader reads.
|
||||||
(looking-back "[ \t]=" bol))))))
|
(looking-back "[ \t]=" bol))))))
|
||||||
|
|
||||||
@ -1621,7 +1622,8 @@ lambda or a `Fn(...)' type, and not after a match arm's."
|
|||||||
(,(concat "^struct[ \t]+[^][ \t\n(){},;\":]+\\(?:([^)\n]*)\\)?[ \t]+:parent[ \t]+"
|
(,(concat "^struct[ \t]+[^][ \t\n(){},;\":]+\\(?:([^)\n]*)\\)?[ \t]+:parent[ \t]+"
|
||||||
flan-fln--name-re)
|
flan-fln--name-re)
|
||||||
1 font-lock-type-face)
|
1 font-lock-type-face)
|
||||||
(,(concat "^\\(?:def\\|once\\|const\\)[ \t]+" flan-fln--name-re)
|
;; A global: a let at column 0 is one.
|
||||||
|
(,(concat "^\\(?:let\\|once\\|const\\)[ \t]+" flan-fln--name-re)
|
||||||
1 font-lock-variable-name-face)
|
1 font-lock-variable-name-face)
|
||||||
;; A restart clause's name, `restart retry() "Try again"'.
|
;; A restart clause's name, `restart retry() "Try again"'.
|
||||||
(,(concat "^[ \t]*restart[ \t]+" flan-fln--name-re)
|
(,(concat "^[ \t]*restart[ \t]+" flan-fln--name-re)
|
||||||
@ -1659,7 +1661,7 @@ lambda or a `Fn(...)' type, and not after a match arm's."
|
|||||||
("Macros" ,(concat "^\\(?:macro[ \t]+\\|defmacro(\\)" flan-fln--name-re) 1)
|
("Macros" ,(concat "^\\(?:macro[ \t]+\\|defmacro(\\)" flan-fln--name-re) 1)
|
||||||
("Types" ,(concat "^\\(?:struct\\|data\\|union\\|enum\\|type\\|class\\)[ \t]+" flan-fln--name-re) 1)
|
("Types" ,(concat "^\\(?:struct\\|data\\|union\\|enum\\|type\\|class\\)[ \t]+" flan-fln--name-re) 1)
|
||||||
("Types" ,(flan-fln--fallback-re flan-fln--fallback-type-heads) 2)
|
("Types" ,(flan-fln--fallback-re flan-fln--fallback-type-heads) 2)
|
||||||
("Variables" ,(concat "^\\(?:def\\|once\\|const\\)[ \t]+" flan-fln--name-re) 1)
|
("Variables" ,(concat "^\\(?:let\\|once\\|const\\)[ \t]+" flan-fln--name-re) 1)
|
||||||
("Variables" ,(flan-fln--fallback-re flan-fln--fallback-variable-heads) 2))
|
("Variables" ,(flan-fln--fallback-re flan-fln--fallback-variable-heads) 2))
|
||||||
"Imenu index for `flan-fln-mode'.")
|
"Imenu index for `flan-fln-mode'.")
|
||||||
|
|
||||||
@ -1669,7 +1671,7 @@ lambda or a `Fn(...)' type, and not after a match arm's."
|
|||||||
(when s
|
(when s
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(goto-char s)
|
(goto-char s)
|
||||||
(and (looking-at (concat "\\(?:fn-?\\|macro\\|generic\\|multi\\|method\\|class\\|def\\|once\\|const\\|struct\\|data\\|union\\|enum\\|type\\)[ \t]+"
|
(and (looking-at (concat "\\(?:fn-?\\|macro\\|generic\\|multi\\|method\\|class\\|let\\|once\\|const\\|struct\\|data\\|union\\|enum\\|type\\)[ \t]+"
|
||||||
flan-fln--name-re))
|
flan-fln--name-re))
|
||||||
(match-string-no-properties 1))))))
|
(match-string-no-properties 1))))))
|
||||||
|
|
||||||
|
|||||||
@ -90,6 +90,10 @@ struct Oops :parent Error
|
|||||||
|
|
||||||
type Count = i64
|
type Count = i64
|
||||||
|
|
||||||
|
let speed: i64 = 3
|
||||||
|
|
||||||
|
fn speed-of() -> i64 = speed
|
||||||
|
|
||||||
struct Pair(a: i64, b: i64)
|
struct Pair(a: i64, b: i64)
|
||||||
|
|
||||||
fn pair-sum(p: Pair) -> i64 = p.a + p.b
|
fn pair-sum(p: Pair) -> i64 = p.a + p.b
|
||||||
@ -275,6 +279,15 @@ comment():
|
|||||||
(test-flan--check (funcall name (format "C-c C-c installs %s" needle))
|
(test-flan--check (funcall name (format "C-c C-c installs %s" needle))
|
||||||
(equal (funcall value call) want)))
|
(equal (funcall value call) want)))
|
||||||
|
|
||||||
|
;; A top-level let is a global, installed and re-run by C-c C-c.
|
||||||
|
(funcall goto "let speed: i64 = 3")
|
||||||
|
(end-of-line)
|
||||||
|
(delete-char -1)
|
||||||
|
(insert "9")
|
||||||
|
(flan-fln-eval-defun)
|
||||||
|
(test-flan--check (funcall name "C-c C-c on a top-level let installs the global")
|
||||||
|
(equal (funcall value "(speed-of)") "9"))
|
||||||
|
|
||||||
;; A macro changed in the buffer and installed again reaches the
|
;; A macro changed in the buffer and installed again reaches the
|
||||||
;; function installed after it.
|
;; function installed after it.
|
||||||
(funcall goto "~x + ~x")
|
(funcall goto "~x + ~x")
|
||||||
|
|||||||
@ -203,7 +203,7 @@ fn step() -> ()
|
|||||||
(test-flan-fln--is "before any form, the next one"
|
(test-flan-fln--is "before any form, the next one"
|
||||||
(test-flan-fln--thing 'flan-fln-toplevel) "fn f() -> i32 = 1"))
|
(test-flan-fln--thing 'flan-fln-toplevel) "fn f() -> i32 = 1"))
|
||||||
|
|
||||||
(test-flan-fln--in "def xs = [1 2\n3 4]\n + 1\nfn|x() -> i32 = 1\n"
|
(test-flan-fln--in "let xs = [1 2\n3 4]\n + 1\nfn|x() -> i32 = 1\n"
|
||||||
(test-flan-fln--is "column 0 inside a bracket or after a leading operator is no form start"
|
(test-flan-fln--is "column 0 inside a bracket or after a leading operator is no form start"
|
||||||
(save-excursion (beginning-of-defun)
|
(save-excursion (beginning-of-defun)
|
||||||
(buffer-substring-no-properties (point) (line-end-position)))
|
(buffer-substring-no-properties (point) (line-end-position)))
|
||||||
@ -853,7 +853,7 @@ defconst(k, 3)
|
|||||||
(test-flan-fln--is "after a trailing colon too"
|
(test-flan-fln--is "after a trailing colon too"
|
||||||
(test-flan-fln--tabs "rl/with-drawing():\n|" 1) 2)
|
(test-flan-fln--tabs "rl/with-drawing():\n|" 1) 2)
|
||||||
(test-flan-fln--is "and after let x ="
|
(test-flan-fln--is "and after let x ="
|
||||||
(test-flan-fln--tabs "def colors =\n|" 1) 2)
|
(test-flan-fln--tabs "let colors =\n|" 1) 2)
|
||||||
(test-flan-fln--is "but not after a one-line fn"
|
(test-flan-fln--is "but not after a one-line fn"
|
||||||
(test-flan-fln--tabs "fn f() -> i32 = 1\n|" 1) 0)
|
(test-flan-fln--tabs "fn f() -> i32 = 1\n|" 1) 0)
|
||||||
(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"
|
||||||
@ -903,6 +903,22 @@ defconst(k, 3)
|
|||||||
(test-flan-fln--tabs "macro repeat(i, n, & body)\n|" 1) 2)
|
(test-flan-fln--tabs "macro repeat(i, n, & body)\n|" 1) 2)
|
||||||
(test-flan-fln--is "and a struct with a parent"
|
(test-flan-fln--is "and a struct with a parent"
|
||||||
(test-flan-fln--tabs "struct DiskFull :parent IoError\n|" 1) 2)
|
(test-flan-fln--tabs "struct DiskFull :parent IoError\n|" 1) 2)
|
||||||
|
(test-flan-fln--in "let speed: i64 = 3\n\nfn f()\n let x = 1\n x\n"
|
||||||
|
(font-lock-ensure)
|
||||||
|
(test-flan-fln--is "a top-level let's name is a variable's"
|
||||||
|
(save-excursion (goto-char (point-min)) (search-forward "speed")
|
||||||
|
(get-text-property (match-beginning 0) 'face))
|
||||||
|
'font-lock-variable-name-face)
|
||||||
|
(test-flan-fln--is "and installs as a def" (flan-fln--declaration-head-at (point-min)) "def")
|
||||||
|
(test-flan--check "imenu lists it"
|
||||||
|
(assoc "speed" (cdr (assoc "Variables" (imenu--generic-function
|
||||||
|
flan-fln-imenu-generic-expression)))))
|
||||||
|
(test-flan--check "it is no local let" (not (flan-fln--let-p (point-min))))
|
||||||
|
(goto-char (point-min))
|
||||||
|
(search-forward "let x")
|
||||||
|
(test-flan--check "one in a fn is" (flan-fln--let-p (line-beginning-position)))
|
||||||
|
(test-flan-fln--is "and its name is not a global's"
|
||||||
|
(get-text-property (match-end 0) 'face) nil))
|
||||||
(test-flan-fln--is "a class with a slot per line opens a block"
|
(test-flan-fln--is "a class with a slot per line opens a block"
|
||||||
(test-flan-fln--tabs "class point\n|" 1) 2)
|
(test-flan-fln--tabs "class point\n|" 1) 2)
|
||||||
(test-flan-fln--is "not one on one line"
|
(test-flan-fln--is "not one on one line"
|
||||||
|
|||||||
@ -1014,9 +1014,12 @@ and sugar n (f : Form.t) : string list option =
|
|||||||
| Form.List ({ v = Form.Sym (("def" | "defonce" | "defconst") as d); _ }
|
| Form.List ({ v = Form.Sym (("def" | "defonce" | "defconst") as d); _ }
|
||||||
:: { v = Form.Sym name; _ } :: rest)
|
:: { v = Form.Sym name; _ } :: rest)
|
||||||
when def_name name ->
|
when def_name name ->
|
||||||
let w = match d with "def" -> "def" | "defonce" -> "once" | _ -> "const" in
|
(* A global [def] is a top-level [let]; nested, where a let is local, it
|
||||||
|
keeps the fallback. *)
|
||||||
|
let w = match d with "def" -> "let" | "defonce" -> "once" | _ -> "const" in
|
||||||
let pre = i ^ w ^ " " ^ name in
|
let pre = i ^ w ^ " " ^ name in
|
||||||
(match d, rest with
|
(match d, rest with
|
||||||
|
| "def", _ when n > 0 -> None
|
||||||
| "defconst", [ v ] -> Some (value_lines n (w ^ " " ^ name) v)
|
| "defconst", [ v ] -> Some (value_lines n (w ^ " " ^ name) v)
|
||||||
| "defconst", [ t; v ] -> Some (value_lines n (w ^ " " ^ name ^ ": " ^ ty t) v)
|
| "defconst", [ t; v ] -> Some (value_lines n (w ^ " " ^ name ^ ": " ^ ty t) v)
|
||||||
| "defconst", _ -> None
|
| "defconst", _ -> None
|
||||||
@ -1246,8 +1249,8 @@ let program ?source ?macros:m (fs : Form.t list) : string =
|
|||||||
(fun (c : Source_text.comment) ->
|
(fun (c : Source_text.comment) ->
|
||||||
f.loc.Loc.line <= c.line && c.line < f.loc.Loc.eline)
|
f.loc.Loc.line <= c.line && c.line < f.loc.Loc.eline)
|
||||||
cs);
|
cs);
|
||||||
(* A flat [let] at the top level would take in the forms after it, so one
|
(* A [let] at the top level is a global, so a local one goes in a [do:]
|
||||||
that is not last goes in a [do:] block. *)
|
block. *)
|
||||||
let top x =
|
let top x =
|
||||||
Hashtbl.reset used;
|
Hashtbl.reset used;
|
||||||
Hashtbl.reset made;
|
Hashtbl.reset made;
|
||||||
@ -1256,7 +1259,6 @@ let program ?source ?macros:m (fs : Form.t list) : string =
|
|||||||
in
|
in
|
||||||
let rec go = function
|
let rec go = function
|
||||||
| [] -> []
|
| [] -> []
|
||||||
| [ x ] -> [ (x, top x) ]
|
|
||||||
| x :: rest -> (x, top (if let_sugar x then in_do x else x)) :: go rest
|
| x :: rest -> (x, top (if let_sugar x then in_do x else x)) :: go rest
|
||||||
in
|
in
|
||||||
let text =
|
let text =
|
||||||
|
|||||||
@ -1523,6 +1523,9 @@ and header (s : st) w : Form.t =
|
|||||||
named (if w = "fn" then "defn" else "defn-")
|
named (if w = "fn" then "defn" else "defn-")
|
||||||
(name :: Form.make (Form.Vec ps) lp.loc :: ret :: (where_clause @ body))
|
(name :: Form.make (Form.Vec ps) lp.loc :: ret :: (where_clause @ body))
|
||||||
| "def" | "once" | "const" ->
|
| "def" | "once" | "const" ->
|
||||||
|
(* A top-level [let] is read here too, as [def]: [w] is then "def" and
|
||||||
|
[t] the let. *)
|
||||||
|
let shown = match t.tok with NAME "let" -> "let" | _ -> w in
|
||||||
let name = name_tok p ~what:"the name being defined" in
|
let name = name_tok p ~what:"the name being defined" in
|
||||||
let tyf =
|
let tyf =
|
||||||
match (peek p).tok with
|
match (peek p).tok with
|
||||||
@ -1533,7 +1536,7 @@ and header (s : st) w : Form.t =
|
|||||||
match (peek p).tok with
|
match (peek p).tok with
|
||||||
| NAME "=" ->
|
| NAME "=" ->
|
||||||
ignore (advance p);
|
ignore (advance p);
|
||||||
Some (value_line s ~after:(w ^ " " ^ text_of name ^ " ="))
|
Some (value_line s ~after:(shown ^ " " ^ text_of name ^ " ="))
|
||||||
| _ ->
|
| _ ->
|
||||||
expect_eol p ~after:(match tyf with Some f -> text_of f | None -> text_of name);
|
expect_eol p ~after:(match tyf with Some f -> text_of f | None -> text_of name);
|
||||||
None
|
None
|
||||||
@ -1555,8 +1558,14 @@ and header (s : st) w : Form.t =
|
|||||||
failk "def-empty" l0
|
failk "def-empty" l0
|
||||||
"%s %s names neither a type nor a value. Give it one or both: %s %s: \
|
"%s %s names neither a type nor a value. Give it one or both: %s %s: \
|
||||||
i32 = 0"
|
i32 = 0"
|
||||||
w (text_of name) w (text_of name)
|
shown (text_of name) shown (text_of name)
|
||||||
in
|
in
|
||||||
|
if shown = "def" then
|
||||||
|
failk "def-is-let" l0
|
||||||
|
"a global is written with let, at the file's top level:\n\n let %s%s%s"
|
||||||
|
(text_of name)
|
||||||
|
(match tyf with Some t -> ": " ^ text_of t | None -> "")
|
||||||
|
(match v with Some v -> " = " ^ text_of v | None -> "");
|
||||||
named head items
|
named head items
|
||||||
| "struct" | "union" ->
|
| "struct" | "union" ->
|
||||||
let name = name_tok p ~what:"the type's name" in
|
let name = name_tok p ~what:"the type's name" in
|
||||||
@ -2124,7 +2133,7 @@ and lines (s : st) (one : unit -> Form.t list) : Form.t list =
|
|||||||
|
|
||||||
(** All top-level forms in a [.fln] source string. [col] is the column the
|
(** All top-level forms in a [.fln] source string. [col] is the column the
|
||||||
text's top level starts at, 1 for a file. *)
|
text's top level starts at, 1 for a file. *)
|
||||||
let read_all ?(line = 1) ?col ?indent ~file src =
|
let read_all ?(line = 1) ?col ?indent ?(global_let = true) ~file src =
|
||||||
let snippet = col <> None in
|
let snippet = col <> None in
|
||||||
let col = Option.value col ~default:1 in
|
let col = Option.value col ~default:1 in
|
||||||
let saved = !source in
|
let saved = !source in
|
||||||
@ -2136,7 +2145,19 @@ let read_all ?(line = 1) ?col ?indent ~file src =
|
|||||||
Fun.protect ~finally:(fun () -> source := saved) (fun () ->
|
Fun.protect ~finally:(fun () -> source := saved) (fun () ->
|
||||||
let toks = layout ~snippet ~base:col ?indent (lex ~line ~col ~file src) in
|
let toks = layout ~snippet ~base:col ?indent (lex ~line ~col ~file src) in
|
||||||
let s = { p = { toks; i = 0 }; lets = [] } in
|
let s = { p = { toks; i = 0 }; lets = [] } in
|
||||||
let fs = stmts s in
|
(* At the top level, a [let] is a global, [(def x dyn v)]: a let there has
|
||||||
|
no block to be local to. Not in an expression the editor sends, where
|
||||||
|
a let is the statement it is in a body. *)
|
||||||
|
let rec top () =
|
||||||
|
match (peek s.p).tok with
|
||||||
|
| EOF -> []
|
||||||
|
| DEDENT -> ignore (advance s.p); []
|
||||||
|
| NAME "let" when header_follow s.p "let" ->
|
||||||
|
let f = header s "def" in
|
||||||
|
f :: top ()
|
||||||
|
| _ -> let f = stmt s in f :: top ()
|
||||||
|
in
|
||||||
|
let fs = if global_let then top () else stmts s in
|
||||||
(match (peek s.p).tok with
|
(match (peek s.p).tok with
|
||||||
| EOF -> ()
|
| EOF -> ()
|
||||||
| tk -> failk "unexpected-token" (where_ s.p) "unexpected %s" (show tk));
|
| tk -> failk "unexpected-token" (where_ s.p) "unexpected %s" (show tk));
|
||||||
|
|||||||
@ -82,7 +82,8 @@ let read_code ?(expr = false) ~file code =
|
|||||||
match !code_syntax with
|
match !code_syntax with
|
||||||
| Paren -> read_paren ~line ~col ~file code
|
| Paren -> read_paren ~line ~col ~file code
|
||||||
| Indented ->
|
| Indented ->
|
||||||
(match Indent_reader.read_all ~line ~col ?indent:!code_indent ~file code with
|
(match Indent_reader.read_all ~line ~col ?indent:!code_indent ~global_let:(not expr)
|
||||||
|
~file code with
|
||||||
| (first :: _ :: _ as forms) when expr ->
|
| (first :: _ :: _ as forms) when expr ->
|
||||||
let last = List.nth forms (List.length forms - 1) in
|
let last = List.nth forms (List.length forms - 1) in
|
||||||
let loc =
|
let loc =
|
||||||
|
|||||||
@ -280,9 +280,13 @@ Each item: the proposal, then the reason in one line.
|
|||||||
becomes `where ordered?($t)` after the return type. **Built**; with no
|
becomes `where ordered?($t)` after the return type. **Built**; with no
|
||||||
`-> R` the return is `_`, read off the body. Several predicates are
|
`-> R` the return is `_`, read off the body. Several predicates are
|
||||||
`where p, q`.
|
`where p, q`.
|
||||||
- `def x = v`, `def x: T = v`, `once x: T`, `once x = v`, `const n = 3`,
|
- A `let` at the top level is a global: `let x = v`, `let x: T = v`,
|
||||||
`def scratch: [4 u8] = uninit`. **Built.** `def x = v` and `once x = v` read
|
`let scratch: [4 u8] = uninit` read `(def x dyn v)`, `(def x T v)`;
|
||||||
with `dyn`; `const n = 3` reads `(defconst n 3)`, its type inferred as today.
|
`once x: T`, `once x = v`, `const n = 3`. **Built.** `let x = v` and
|
||||||
|
`once x = v` read with `dyn`; `const n = 3` reads `(defconst n 3)`, its type
|
||||||
|
inferred as today. `def` is refused with the `let` to write. A `let` in a
|
||||||
|
block, `comment:`'s included, is local, and so is one in code the editor
|
||||||
|
evaluates as an expression.
|
||||||
- `struct Cell` with a `name: Type` line per field. `data Shape` with a line per
|
- `struct Cell` with a `name: Type` line per field. `data Shape` with a line per
|
||||||
case: `Circle(r: f32)`, `Empty`. `enum K` with `lo = -1`, `mid`. `union U` like
|
case: `Circle(r: f32)`, `Empty`. `enum K` with `lo = -1`, `mid`. `union U` like
|
||||||
`struct`. **Built** (an untyped field is `dyn`; `Empty()` is `(Empty [])`).
|
`struct`. **Built** (an untyped field is `dyn`; `Empty()` is `(Empty [])`).
|
||||||
|
|||||||
@ -8,7 +8,7 @@ enum State
|
|||||||
quote-in-quoted
|
quote-in-quoted
|
||||||
|
|
||||||
once rows-seen: i32
|
once rows-seen: i32
|
||||||
def fields-seen: i32 = 0
|
let fields-seen: i32 = 0
|
||||||
const separator = \,
|
const separator = \,
|
||||||
|
|
||||||
; Frame the body's output with a title line and a closing rule.
|
; Frame the body's output with a title line and a closing rule.
|
||||||
|
|||||||
@ -8,7 +8,7 @@ struct Rule
|
|||||||
label: string
|
label: string
|
||||||
applies: CFn(stock/Item) -> bool
|
applies: CFn(stock/Item) -> bool
|
||||||
|
|
||||||
def report-width: i32 = 28
|
let report-width: i32 = 28
|
||||||
once runs: i32
|
once runs: i32
|
||||||
const reorder-below = 5
|
const reorder-below = 5
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,8 @@ const brush-size = 10
|
|||||||
fn dyn->f64(v: f64) -> f64 = v
|
fn dyn->f64(v: f64) -> f64 = v
|
||||||
fn dyn->u32(v: i64) -> u32 = u32(v)
|
fn dyn->u32(v: i64) -> u32 = u32(v)
|
||||||
|
|
||||||
def gravity = 0.05
|
let gravity = 0.05
|
||||||
def colors =
|
let colors =
|
||||||
let v = vec-new(dyn)
|
let v = vec-new(dyn)
|
||||||
push(v, 0xFFF00FFF)
|
push(v, 0xFFF00FFF)
|
||||||
push(v, 0x3B6E8CFF)
|
push(v, 0x3B6E8CFF)
|
||||||
@ -121,7 +121,7 @@ fn game-draw() -> ()
|
|||||||
rl/draw-fps(20, 20)
|
rl/draw-fps(20, 20)
|
||||||
|
|
||||||
once frame: Allocator = arena-new(262144)
|
once frame: Allocator = arena-new(262144)
|
||||||
def game-data =
|
let game-data =
|
||||||
handler-case
|
handler-case
|
||||||
edn/read-file("game-data.edn")
|
edn/read-file("game-data.edn")
|
||||||
on FileError(c)
|
on FileError(c)
|
||||||
|
|||||||
@ -411,17 +411,19 @@ let () =
|
|||||||
|
|
||||||
(* ── Lexical edge cases ────────────────────────────────────────────── *)
|
(* ── Lexical edge cases ────────────────────────────────────────────── *)
|
||||||
|
|
||||||
let read src = Indent_reader.read_all ~file:"<syntax>" src
|
(* [~global:false] reads as an expression the editor sends, where a [let] is
|
||||||
|
local; a file's top-level [let] is a global. *)
|
||||||
|
let read ?(global = true) src = Indent_reader.read_all ~global_let:global ~file:"<syntax>" src
|
||||||
|
|
||||||
let reads name src want =
|
let reads ?global name src want =
|
||||||
match read src with
|
match read ?global src with
|
||||||
| forms ->
|
| forms ->
|
||||||
let got = String.concat "\n" (List.map Form.to_string forms) in
|
let got = String.concat "\n" (List.map Form.to_string forms) in
|
||||||
if got <> want then fail "%s: read %s, wanted %s" name got want
|
if got <> want then fail "%s: read %s, wanted %s" name got want
|
||||||
| exception e -> fail "%s: refused: %s" name (diag_text e)
|
| exception e -> fail "%s: refused: %s" name (diag_text e)
|
||||||
|
|
||||||
let refuses name src kind needle =
|
let refuses ?global name src kind needle =
|
||||||
match read src with
|
match read ?global src with
|
||||||
| forms ->
|
| forms ->
|
||||||
fail "%s: read %s, wanted the refusal %s" name
|
fail "%s: read %s, wanted the refusal %s" name
|
||||||
(String.concat " " (List.map Form.to_string forms)) kind
|
(String.concat " " (List.map Form.to_string forms)) kind
|
||||||
@ -462,7 +464,7 @@ let () =
|
|||||||
reads "character literals" "x = [\\( \\, \\space \\)]" "(set x [\\( \\, \\space \\)])";
|
reads "character literals" "x = [\\( \\, \\space \\)]" "(set x [\\( \\, \\space \\)])";
|
||||||
reads "character arguments" "f(\\,, \\))" "(f \\, \\))";
|
reads "character arguments" "f(\\,, \\))" "(f \\, \\))";
|
||||||
(* Keywords and annotations. *)
|
(* Keywords and annotations. *)
|
||||||
reads "keyword" "def k = :else" "(def k dyn :else)";
|
reads "keyword" "let k = :else" "(def k dyn :else)";
|
||||||
reads "annotation" "once grid: [4 [8 u32]]" "(defonce grid [4 [8 u32]])";
|
reads "annotation" "once grid: [4 [8 u32]]" "(defonce grid [4 [8 u32]])";
|
||||||
reads "keyword argument" "rl/key-pressed?(:key-r)" "(rl/key-pressed? :key-r)";
|
reads "keyword argument" "rl/key-pressed?(:key-r)" "(rl/key-pressed? :key-r)";
|
||||||
refuses "colon inside a name" "fn f(x:i32) -> () = x" "indent/colon-in-name" "x: i32";
|
refuses "colon inside a name" "fn f(x:i32) -> () = x" "indent/colon-in-name" "x: i32";
|
||||||
@ -521,8 +523,8 @@ let () =
|
|||||||
(* Statements. *)
|
(* Statements. *)
|
||||||
reads "lets merge" "fn f() -> i32\n let a = 1\n let b = 2\n a + b"
|
reads "lets merge" "fn f() -> i32\n let a = 1\n let b = 2\n a + b"
|
||||||
"(defn f [] i32 (let [a 1 b 2] (+ a b)))";
|
"(defn f [] i32 (let [a 1 b 2] (+ a b)))";
|
||||||
refuses "let with a block" "let a = 1\n a\nb" "indent/let-block" "go at the let's column";
|
refuses ~global:false "let with a block" "let a = 1\n a\nb" "indent/let-block" "go at the let's column";
|
||||||
reads "flat let" "let a = 1\na\nb" "(let [a 1] a b)";
|
reads ~global:false "flat let" "let a = 1\na\nb" "(let [a 1] a b)";
|
||||||
reads "elif" "if a\n 1\nelif b\n 2\nelse\n 3" "(cond a 1 b 2 :else 3)";
|
reads "elif" "if a\n 1\nelif b\n 2\nelse\n 3" "(cond a 1 b 2 :else 3)";
|
||||||
reads "one-line if" "x = if a then 1 else 2" "(set x (if a 1 2))";
|
reads "one-line if" "x = if a then 1 else 2" "(set x (if a 1 2))";
|
||||||
reads "assignment ops" "a[i] += 1" "(set (at a i) (+ (at a i) 1))";
|
reads "assignment ops" "a[i] += 1" "(set (at a i) (+ (at a i) 1))";
|
||||||
@ -577,6 +579,12 @@ let () =
|
|||||||
"method g(p: point)";
|
"method g(p: point)";
|
||||||
refuses "a method with both" "method g(p: point) when :x\n 1" "indent/method-key"
|
refuses "a method with both" "method g(p: point) when :x\n 1" "indent/method-key"
|
||||||
"not both";
|
"not both";
|
||||||
|
reads "a top-level let is a global" "let g = 1\nlet h: i32 = 2\nlet s: [4 u8]\nf(g)"
|
||||||
|
"(def g dyn 1)\n(def h i32 2)\n(def s [4 u8])\n(f g)";
|
||||||
|
refuses "def" "def g: i32 = 1" "indent/def-is-let" "let g: i32 = 1";
|
||||||
|
reads "a let in a comment block stays local" "comment:\n let x = 1\n f(x)"
|
||||||
|
"(comment (let [x 1] (f x)))";
|
||||||
|
reads "and in a fn" "fn f() -> i32\n let x = 1\n x" "(defn f [] i32 (let [x 1] x))";
|
||||||
reads "a one-line struct" "struct Pt(x: i32, y)" "(defstruct Pt [x i32 y dyn])";
|
reads "a one-line struct" "struct Pt(x: i32, y)" "(defstruct Pt [x i32 y dyn])";
|
||||||
reads "a one-line union" "union U(a: i32)" "(defunion U [a i32])";
|
reads "a one-line union" "union U(a: i32)" "(defunion U [a i32])";
|
||||||
reads "a one-line struct with a parent" "struct D(free: i64) :parent Io"
|
reads "a one-line struct with a parent" "struct D(free: i64) :parent Io"
|
||||||
@ -591,12 +599,12 @@ let () =
|
|||||||
refuses "a rest parameter not last" "macro m(& a, b)\n a" "indent/macro-rest-last"
|
refuses "a rest parameter not last" "macro m(& a, b)\n a" "indent/macro-rest-last"
|
||||||
"comes last: macro m(b, & a)";
|
"comes last: macro m(b, & a)";
|
||||||
reads "loop" "loop x = a, y = b + 1\n recur(y, x)" "(loop [x a y (+ b 1)] (recur y x))";
|
reads "loop" "loop x = a, y = b + 1\n recur(y, x)" "(loop [x a y (+ b 1)] (recur y x))";
|
||||||
reads "a let-bound loop" "let r = loop i = 0\n recur(i)\nr" "(let [r (loop [i 0] (recur i))] r)";
|
reads ~global:false "a let-bound loop" "let r = loop i = 0\n recur(i)\nr" "(let [r (loop [i 0] (recur i))] r)";
|
||||||
reads "the loop call stays a call" "loop([x 1]):\n x" "(loop [x 1] x)";
|
reads "the loop call stays a call" "loop([x 1]):\n x" "(loop [x 1] x)";
|
||||||
refuses "a loop with no values" "loop\n g()" "indent/loop-bindings" "loop([]):";
|
refuses "a loop with no values" "loop\n g()" "indent/loop-bindings" "loop([]):";
|
||||||
refuses "a loop variable with no value" "loop x, y = 1\n g()" "indent/loop-bindings"
|
refuses "a loop variable with no value" "loop x, y = 1\n g()" "indent/loop-bindings"
|
||||||
"loop x = 0";
|
"loop x = 0";
|
||||||
reads "read-only pointer" "def p: Ptr(const u8) = uninit" "(def p (Ptr const u8) uninit)";
|
reads "read-only pointer" "let p: Ptr(const u8) = uninit" "(def p (Ptr const u8) uninit)";
|
||||||
(* Statements that fit on a line, in one-line slots. *)
|
(* Statements that fit on a line, in one-line slots. *)
|
||||||
reads "arm statements" "match s\n 1 -> break\n 2 -> continue :outer\n _ -> x += 1"
|
reads "arm statements" "match s\n 1 -> break\n 2 -> continue :outer\n _ -> x += 1"
|
||||||
"(match s 1 (break) 2 (continue :outer) _ (set x (+ x 1)))";
|
"(match s 1 (break) 2 (continue :outer) _ (set x (+ x 1)))";
|
||||||
@ -612,9 +620,9 @@ let () =
|
|||||||
refuses "two assignments" "if a then b = c = d" "indent/assign-in-test" "if a then b = c,";
|
refuses "two assignments" "if a then b = c = d" "indent/assign-in-test" "if a then b = c,";
|
||||||
refuses "a let-bound if with no block" "let r = if a > 1\nr" "indent/expected-block" "if a > 1 takes";
|
refuses "a let-bound if with no block" "let r = if a > 1\nr" "indent/expected-block" "if a > 1 takes";
|
||||||
refuses "two bindings on a line" "let v: i32 = a, w = b" "indent/one-binding" "a is followed by a comma";
|
refuses "two bindings on a line" "let v: i32 = a, w = b" "indent/one-binding" "a is followed by a comma";
|
||||||
reads "a let-bound match" "let r = match a\n 1 -> 2\n _ -> 3\nr" "(let [r (match a 1 2 _ 3)] r)";
|
reads ~global:false "a let-bound match" "let r = match a\n 1 -> 2\n _ -> 3\nr" "(let [r (match a 1 2 _ 3)] r)";
|
||||||
reads "a let-bound if" "let q = if a\n 1\nelse\n 2\nq" "(let [q (if a 1 2)] q)";
|
reads ~global:false "a let-bound if" "let q = if a\n 1\nelse\n 2\nq" "(let [q (if a 1 2)] q)";
|
||||||
reads "a let-bound call with a block" "let v = foo(a):\n x\nv" "(let [v (foo a x)] v)";
|
reads ~global:false "a let-bound call with a block" "let v = foo(a):\n x\nv" "(let [v (foo a x)] v)";
|
||||||
refuses "colon after if" "if c:\n y" "indent/header-colon" "no colon";
|
refuses "colon after if" "if c:\n y" "indent/header-colon" "no colon";
|
||||||
refuses "colon after a return type" "fn f() -> i32:\n 0" "indent/header-colon" "no colon";
|
refuses "colon after a return type" "fn f() -> i32:\n 0" "indent/header-colon" "no colon";
|
||||||
refuses "colon after a number" "while x < 3:\n y" "indent/header-colon" "no colon";
|
refuses "colon after a number" "while x < 3:\n y" "indent/header-colon" "no colon";
|
||||||
@ -626,7 +634,7 @@ let () =
|
|||||||
refuses "mixed separators" "x = [1 2, 3]" "indent/mixed-separators" "[1, 2, 3]";
|
refuses "mixed separators" "x = [1 2, 3]" "indent/mixed-separators" "[1, 2, 3]";
|
||||||
reads "one-line quote" "defmacro(m, [x]):\n quote ~x + 1"
|
reads "one-line quote" "defmacro(m, [x]):\n quote ~x + 1"
|
||||||
"(defmacro m [x] (quasiquote (+ (unquote x) 1)))";
|
"(defmacro m [x] (quasiquote (+ (unquote x) 1)))";
|
||||||
reads "typed let" "let x: i32 = 5\nx" "(let [x (the i32 5)] x)";
|
reads ~global:false "typed let" "let x: i32 = 5\nx" "(let [x (the i32 5)] x)";
|
||||||
refuses "a let takes no block" "fn f() -> ()\n let x = 1\n g(x)\n h(x)"
|
refuses "a let takes no block" "fn f() -> ()\n let x = 1\n g(x)\n h(x)"
|
||||||
"indent/let-block" "go at the let's column";
|
"indent/let-block" "go at the let's column";
|
||||||
(* Mistakes carried over from other languages, answered in this one. *)
|
(* Mistakes carried over from other languages, answered in this one. *)
|
||||||
@ -655,7 +663,7 @@ let () =
|
|||||||
"indent/orphan-else" "goes at the if's column";
|
"indent/orphan-else" "goes at the if's column";
|
||||||
reads "a typed lambda" "f = fn(a: C, b) -> bool = a.n < b"
|
reads "a typed lambda" "f = fn(a: C, b) -> bool = a.n < b"
|
||||||
"(set f (the (Fn [C dyn] bool) (fn [a b] (< (.n a) b))))";
|
"(set f (the (Fn [C dyn] bool) (fn [a b] (< (.n a) b))))";
|
||||||
reads "a typed lambda with a block" "let f = fn(x: i32) -> i32\n let y = x + 1\n y\ng(f)"
|
reads ~global:false "a typed lambda with a block" "let f = fn(x: i32) -> i32\n let y = x + 1\n y\ng(f)"
|
||||||
"(let [f (the (Fn [i32] i32) (fn [x] (let [y (+ x 1)] y)))] (g f))";
|
"(let [f (the (Fn [i32] i32) (fn [x] (let [y (+ x 1)] y)))] (g f))";
|
||||||
refuses "a typed lambda states its return type" "f = fn(a: C) = a"
|
refuses "a typed lambda states its return type" "f = fn(a: C) = a"
|
||||||
"indent/lambda-return" "fn(a: C) -> R = value";
|
"indent/lambda-return" "fn(a: C) -> R = value";
|
||||||
@ -839,9 +847,9 @@ let () =
|
|||||||
"restart retry() \"Try again\"\n 7";
|
"restart retry() \"Try again\"\n 7";
|
||||||
prints "adjacent one-line globals stay adjacent"
|
prints "adjacent one-line globals stay adjacent"
|
||||||
"(defonce a i32)\n(def b i32 2)\n\n(defconst c 3)\n"
|
"(defonce a i32)\n(def b i32 2)\n\n(defconst c 3)\n"
|
||||||
"once a: i32\ndef b: i32 = 2\n\nconst c = 3";
|
"once a: i32\nlet b: i32 = 2\n\nconst c = 3";
|
||||||
back "adjacent one-line globals stay adjacent in parens"
|
back "adjacent one-line globals stay adjacent in parens"
|
||||||
"once a: i32\ndef b: i32 = 2\n\nconst c = 3\n"
|
"once a: i32\nlet b: i32 = 2\n\nconst c = 3\n"
|
||||||
"(defonce a i32)\n(def b i32 2)\n\n(defconst c 3)";
|
"(defonce a i32)\n(def b i32 2)\n\n(defconst c 3)";
|
||||||
prints "a field of a field chains" "(defn f [] () (g (.count (.x w))))" "g(w.x.count)";
|
prints "a field of a field chains" "(defn f [] () (g (.count (.x w))))" "g(w.x.count)";
|
||||||
prints "an else-if chain on one line"
|
prints "an else-if chain on one line"
|
||||||
@ -863,6 +871,10 @@ let () =
|
|||||||
prints "a method on a class" "(defmethod area point [p] (g p) (h p))"
|
prints "a method on a class" "(defmethod area point [p] (g p) (h p))"
|
||||||
"method area(p: point)\n g(p)\n h(p)";
|
"method area(p: point)\n g(p)\n h(p)";
|
||||||
prints "a method on a value" "(defmethod kind :int [v] \"n\")" "method kind(v) when :int = \"n\"";
|
prints "a method on a value" "(defmethod kind :int [v] \"n\")" "method kind(v) when :int = \"n\"";
|
||||||
|
prints "a global is a top-level let" "(def g dyn 1)\n(def h i32 2)" "let g = 1\nlet h: i32 = 2";
|
||||||
|
prints "a def inside a form keeps the fallback" "(comment (def g i32 1))" "comment:\n def(g, i32, 1)";
|
||||||
|
prints "a local let at the top level goes in a do block" "(let [x 1] (f x))"
|
||||||
|
"do:\n let x = 1\n f(x)";
|
||||||
prints "a type alias" "(defalias Row (Vec i32))" "type Row = Vec(i32)";
|
prints "a type alias" "(defalias Row (Vec i32))" "type Row = Vec(i32)";
|
||||||
prints "a struct with a parent" "(defstruct D :parent Io [free i64])"
|
prints "a struct with a parent" "(defstruct D :parent Io [free i64])"
|
||||||
"struct D(free: i64) :parent Io";
|
"struct D(free: i64) :parent Io";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user