A function declared with defn- is callable only from the files of its own package

This commit is contained in:
Joseph Ferano 2026-09-25 07:42:40 +07:00
parent 5557594f31
commit 6fd456c0a2
27 changed files with 283 additions and 108 deletions

View File

@ -522,11 +522,15 @@ routes must arrive under one set of names or the checker sees every declaration
twice. The price is that the same directory under two aliases is refused, naming twice. The price is that the same directory under two aliases is refused, naming
both. both.
** TODO A package cannot mark a name private ** DONE A package cannot mark a name private
=rl/get-color-raw= is callable from outside its package. The refusal machinery CLOSED: [2026-09-25]
takes a second rule in one line; the blocker is that there is no way for a package =defn-= declares a function private to its package; it is a =defn= otherwise.
to *say* a name is private, and adding one is a parser change the author has to A use from a file outside the package's directory — a call or the name as a
choose a spelling for. Every lane has skipped it for that reason. value, from the importer or from another package — is refused in =Check=, so it
holds under C-c C-c too. Functions only. A single-file package's siblings share
its directory and can call its =defn-=s, and a package macro expanding to a
=defn-= call is refused at the expansion. The edn and json internals are now
=defn-=; =rl/get-color-raw= no longer exists. =docs/BUILT.md= has the placement.
** CANCELLED A struct version word, so a redefined layout keeps working ** CANCELLED A struct version word, so a redefined layout keeps working
CLOSED: [2026-09-20] CLOSED: [2026-09-20]

View File

@ -794,10 +794,18 @@ takes a macro-stamped location — and `Loc.from_macro` sets a name and leaves f
the frame the break loop reports is still the line the reader is looking at. `temps` is not reset on this path, unlike `decl`'s — a declaration is the frame the break loop reports is still the line the reader is looking at. `temps` is not reset on this path, unlike `decl`'s — a declaration is
a fresh top level, an expression is evaluated into a session that has been handing out temporaries all along. a fresh top level, an expression is evaluated into a session that has been handing out temporaries all along.
Still missing: a package-private marker for anything other than `main`, which is why `rl/get-color-raw` is callable. A function can be private to its package: `defn-`, Clojure's spelling, is a `defn` whose uses outside the package's
The gap is surface syntax and not `load.ml` — `exported` is one predicate and the refusal machinery that points at the directory are refused. It is not done in `load.ml`, although `main`'s refusal is. `Load.refuse_hidden` walks every
line which tried already exists, so a second rule is a line. What does not exist is any way for a package to *mark* a declaration after the rename, the package's own included, and by then the package's calls to its helper read
name private, and inventing one is a reader and parser change. `alias/helper` like anyone else's; and a C-c C-c goes through `Load.program` with one form and no imports in sight. So
the flag rides on `Ast.fn`, `Check` records every `defn-` in `privates`, and `Check.private_ref` compares the directory
of the file the use is written in with the directory of the file the definition is — the same shape as
`shadows_builtin`'s file test. Being in `Check` is what makes it hold in the session too: every evaluation re-checks
the whole declaration list. Only a qualified name is asked about, because an unqualified one is the program's own.
Two consequences follow from the rule being about files. A single-file package shares its directory with its
siblings, and they can call its `defn-`s. And a package macro that expands to a call to one of its `defn-`s is
refused at the expansion, because the expansion is located at the call site in the importer's file; the edn and json
readers' generated code calls `need-int` and its neighbours, which is why those stay `defn`.
## The link follows the program ## The link follows the program

View File

@ -119,7 +119,7 @@
;; patched only when somebody notices a gap is the list that is always a ;; patched only when somebody notices a gap is the list that is always a
;; release behind the parser. ;; release behind the parser.
(defconst flan--definers (defconst flan--definers
'("defn" "defmacro" "def" "defonce" "defconst" "defstruct" "defdata" "defunion" '("defn" "defn-" "defmacro" "def" "defonce" "defconst" "defstruct" "defdata" "defunion"
"defenum" "defalias" "defenum" "defalias"
;; The object and dispatch heads (lib/parse.ml:1277-1334). ;; The object and dispatch heads (lib/parse.ml:1277-1334).
"defclass" "defgeneric" "defmulti" "defmethod" "defclass" "defgeneric" "defmulti" "defmethod"
@ -277,7 +277,7 @@ below — and not again here.")
;; it implements, which is the name in the same place, so a file of several ;; it implements, which is the name in the same place, so a file of several
;; methods shows that name several times — the honest answer, and better ;; methods shows that name several times — the honest answer, and better
;; than listing none of them as it did. ;; than listing none of them as it did.
`(("Functions" ,(concat "^(def\\(?:n\\|generic\\|multi\\|method\\)\\s-+" `(("Functions" ,(concat "^(def\\(?:n-?\\|generic\\|multi\\|method\\)\\s-+"
flan--name-re) flan--name-re)
1) 1)
;; Its own heading rather than a second `Functions' entry: a macro runs at ;; Its own heading rather than a second `Functions' entry: a macro runs at
@ -574,6 +574,7 @@ For `syntax-propertize-function'."
;; the parameters and the body is optional; a count would have to know ;; the parameters and the body is optional; a count would have to know
;; whether one is there, and `:defn' does not care. ;; whether one is there, and `:defn' does not care.
("defn" . :defn) ("defn" . :defn)
("defn-" . :defn)
;; `(declare-c NAME [params] RET "CSymbol")'. The name is the one special ;; `(declare-c NAME [params] RET "CSymbol")'. The name is the one special
;; argument; everything after it is written down the page in one column. ;; argument; everything after it is written down the page in one column.
("declare" . 1) ("declare" . 1)

View File

@ -2529,7 +2529,7 @@ daemon reads that as stopping on entry instead."
;; and `package' is a single named exception rather than the edge of a subtler ;; and `package' is a single named exception rather than the edge of a subtler
;; rule that was never quite true. ;; rule that was never quite true.
(defconst flan--declaration-heads (defconst flan--declaration-heads
'("defmacro" "defn" "def" "defonce" "defconst" '("defmacro" "defn" "defn-" "def" "defonce" "defconst"
"defstruct" "defdata" "defunion" "defenum" "defalias" "defstruct" "defdata" "defunion" "defenum" "defalias"
;; The object and dispatch heads (lib/parse.ml:1277-1334), which are ;; The object and dispatch heads (lib/parse.ml:1277-1334), which are
;; declarations in exactly the way `defn' is: each introduces a top-level ;; declarations in exactly the way `defn' is: each introduces a top-level

View File

@ -388,6 +388,12 @@
"and the name it introduces") "and the name it introduces")
("(defonce seed i64 1)" "defonce" font-lock-keyword-face ("(defonce seed i64 1)" "defonce" font-lock-keyword-face
"defonce") "defonce")
;; A private defn: the head as a whole, not `defn' and a
;; stray `-', and the name after it.
("(defn- mix [a i32] i32 a)" "defn-" font-lock-keyword-face
"defn-'s head")
("(defn- mix [a i32] i32 a)" "mix"
font-lock-function-name-face "and the private function's name")
("(defclass point [x y])" "defclass" font-lock-keyword-face ("(defclass point [x y])" "defclass" font-lock-keyword-face
"defclass") "defclass")
("(defgeneric area [self] dyn)" "defgeneric" ("(defgeneric area [self] dyn)" "defgeneric"

View File

@ -730,7 +730,7 @@ already rely on it — so nothing here is a stand-in for the real thing."
;; back. Read off `Parse.decl', so the check is the derivation. ;; back. Read off `Parse.decl', so the check is the derivation.
(with-temp-buffer (with-temp-buffer
(flan-mode) (flan-mode)
(dolist (head '("defmacro" "defn" "def" "defonce" "defconst" "defstruct" (dolist (head '("defmacro" "defn" "defn-" "def" "defonce" "defconst" "defstruct"
"defdata" "defunion" "defenum" "defalias" "defdata" "defunion" "defenum" "defalias"
"defclass" "defgeneric" "defmulti" "defmethod" "defclass" "defgeneric" "defmulti" "defmethod"
"import" "declare" "declare-c")) "import" "declare" "declare-c"))

View File

@ -225,6 +225,9 @@ type fn = {
fwhere : pred list; fwhere : pred list;
fbody : expr list; fbody : expr list;
nloc : Loc.t; nloc : Loc.t;
(* Written [defn-]: callable only from the files of the package that
declares it. [Check.private_ref] is the whole of what it means. *)
fprivate : bool;
} }
type decl = { d : decl_kind; dloc : Loc.t } type decl = { d : decl_kind; dloc : Loc.t }

View File

@ -114,6 +114,8 @@ type env = {
function can show it. Kept apart from [fparams] because a foreign function can show it. Kept apart from [fparams] because a foreign
[declare] has a location and no parameter vector worth showing. *) [declare] has a location and no parameter vector worth showing. *)
fn_locs : (string, Loc.t) Hashtbl.t; fn_locs : (string, Loc.t) Hashtbl.t;
(* Every [defn-], by name, with where it was written. See [private_ref]. *)
privates : (string, Loc.t) Hashtbl.t;
globals : (string, Types.t * bool) Hashtbl.t; (* type, is a constant *) globals : (string, Types.t * bool) Hashtbl.t; (* type, is a constant *)
(* Where each global was declared, so a refusal about one can show it. A (* Where each global was declared, so a refusal about one can show it. A
second table rather than a third field, because every other reader of second table rather than a third field, because every other reader of
@ -194,6 +196,7 @@ let new_env () = {
fns = Hashtbl.create 32; fns = Hashtbl.create 32;
fparams = Hashtbl.create 32; fparams = Hashtbl.create 32;
fn_locs = Hashtbl.create 32; fn_locs = Hashtbl.create 32;
privates = Hashtbl.create 8;
globals = Hashtbl.create 16; globals = Hashtbl.create 16;
global_locs = Hashtbl.create 16; global_locs = Hashtbl.create 16;
lifted = []; lifted = [];
@ -3863,6 +3866,7 @@ and var ctx ?(qualified = false) loc ~want name =
a question this language does not ask. *) a question this language does not ask. *)
(match Hashtbl.find_opt ctx.env.fns name with (match Hashtbl.find_opt ctx.env.fns name with
| Some (params, ret) -> | Some (params, ret) ->
private_ref ctx loc name;
(* A foreign function is in [fns] too, and its emitted signature (* A foreign function is in [fns] too, and its emitted signature
is C's: no transfer channel, and an aggregate flattened by the is C's: no transfer channel, and an aggregate flattened by the
shim. Nothing could call the resulting pointer correctly, so it shim. Nothing could call the resulting pointer correctly, so it
@ -8836,11 +8840,13 @@ and ordinary_call ctx ~want loc name args =
| Some b -> call_value ctx ~want loc (mk loc b.bty (Tast.Local b.slot)) args | Some b -> call_value ctx ~want loc (mk loc b.bty (Tast.Local b.slot)) args
| None -> assert false) | None -> assert false)
| _ when Hashtbl.mem ctx.env.gsigs name -> | _ when Hashtbl.mem ctx.env.gsigs name ->
private_ref ctx loc name;
let vars, params, ret = Hashtbl.find ctx.env.gsigs name in let vars, params, ret = Hashtbl.find ctx.env.gsigs name in
generic_call ctx ~want loc name vars params ret args generic_call ctx ~want loc name vars params ret args
| _ -> | _ ->
match Hashtbl.find_opt ctx.env.fns name with match Hashtbl.find_opt ctx.env.fns name with
| Some (params, ret) -> | Some (params, ret) ->
private_ref ctx loc name;
if List.length args <> List.length params then if List.length args <> List.length params then
fail loc "%s takes %d argument%s, given %d" name fail loc "%s takes %d argument%s, given %d" name
(List.length params) (List.length params)
@ -9002,6 +9008,34 @@ and ordinary_call ctx ~want loc name args =
is not the file the defn was written in, so it reaches the builtin. That is not the file the defn was written in, so it reaches the builtin. That
is the conservative direction, and C-c C-c — which sends the buffer's own is the conservative direction, and C-c C-c — which sends the buffer's own
path — is not affected. *) path — is not affected. *)
(* A [defn-] is its package's own. A use of one — a call, or the name taken
as a value — is refused unless it is written in a file of the package that
declares it, and a package is its directory, so the test is the directory of
the two files. Realpath'd, because one side is usually the path the importer
was given on the command line and the other the one [Load] resolved.
Only a qualified name is asked about. An unqualified one belongs to the
program being built, and nothing outside a program can name it — the REPL,
which evaluates with no file behind it, included.
A package that is a single file named outright, rather than a directory,
shares its directory with whatever sits beside it, so a sibling file that
imports it can call its [defn-]s. *)
and private_ref ctx loc name =
match Hashtbl.find_opt ctx.env.privates name with
| Some at when String.contains name '/' ->
let dir file =
Filename.dirname
(try Unix.realpath file with Unix.Unix_error _ -> file)
in
if not (String.equal (dir at.Loc.file) (dir loc.Loc.file)) then
Loc.failk "check/private" loc
~notes:[ Loc.note at (Printf.sprintf "%s is declared here" name) ]
"%s is private to its package: it is declared with defn-, and a \
defn- can be used only from a file in %s"
name (dir at.Loc.file)
| _ -> ()
and shadows_builtin ctx loc name = and shadows_builtin ctx loc name =
(* Where the definition was written, if this name has one. A generic is in (* Where the definition was written, if this name has one. A generic is in
[generics] and nowhere near [fn_locs], so both tables are asked. *) [generics] and nowhere near [fn_locs], so both tables are asked. *)
@ -10438,6 +10472,8 @@ let collect env (decls : Ast.decl list) =
in in
env.tyvars <- []; env.tyvars <- [];
env.tvpreds <- []; env.tvpreds <- [];
if fn.Ast.fprivate then
Hashtbl.replace env.privates fn.Ast.name fn.Ast.nloc;
if vars = [] then begin if vars = [] then begin
Hashtbl.replace env.fns fn.Ast.name (params, ret); Hashtbl.replace env.fns fn.Ast.name (params, ret);
Hashtbl.replace env.fparams fn.Ast.name fn.Ast.params; Hashtbl.replace env.fparams fn.Ast.name fn.Ast.params;

View File

@ -838,7 +838,7 @@ let of_dump ~env ~taken ~bound_syms ~config (d : dump) : imported =
decls := decls :=
{ Ast.d = { Ast.d =
Ast.DeclareC Ast.DeclareC
({ Ast.name = flan; params; praw = None; ret; fwhere = []; fbody = []; nloc = f.cloc }, ({ Ast.name = flan; params; praw = None; ret; fwhere = []; fbody = []; nloc = f.cloc; fprivate = false },
f.csym); f.csym);
dloc = f.cloc } dloc = f.cloc }
:: !decls) :: !decls)

View File

@ -175,7 +175,7 @@ let constructor n slots loc : Ast.decl =
Ast.Defn Ast.Defn
{ Ast.name = n; params; praw = None; ret = Some (dyn_at loc); { Ast.name = n; params; praw = None; ret = Some (dyn_at loc);
fwhere = []; fbody = [ ex loc (Ast.MapLit (Some n, pairs)) ]; fwhere = []; fbody = [ ex loc (Ast.MapLit (Some n, pairs)) ];
nloc = loc }; nloc = loc; fprivate = false };
dloc = loc } dloc = loc }
(* The dispatch value a method answers for, as an expression to compare (* The dispatch value a method answers for, as an expression to compare

View File

@ -22,12 +22,13 @@
of one directory load it once, keyed by its real path; the same directory of one directory load it once, keyed by its real path; the same directory
under two different aliases is refused, and so is a cycle. under two different aliases is refused, and so is a cycle.
Visibility is one rule so far: [main] is not exported. A package carrying Visibility is two rules. [main] is not exported: a package carrying one
one would collide with the importer's, and worse, would keep everything it would collide with the importer's, and worse, would keep everything it
calls reachable (see [Reach]) — which for a raylib front-end is the whole calls reachable (see [Reach]) — which for a raylib front-end is the whole
library, on the target that cannot link it. Package-private markers for library, on the target that cannot link it. And a [defn-] is exported like
anything else are still missing, which is why [rl/get-color-raw] is any other name but refused at a use outside its package's directory, which
callable. is [Check.private_ref]'s and not this module's: the rename has to qualify
the package's own calls to it exactly as it qualifies everything else.
A package may also carry the C it binds to. Every [.c] file in the A package may also carry the C it binds to. Every [.c] file in the
directory is compiled into the build, and a file named [link] lists extra directory is compiled into the build, and a file named [link] lists extra
@ -725,9 +726,8 @@ let imports_of (forms : Form.t list) =
exists to prevent. exists to prevent.
So the package's [main] is dropped rather than qualified, and [alias/main] So the package's [main] is dropped rather than qualified, and [alias/main]
is not a name. Everything else is still exported; package-private markers is not a name. Everything else is exported, a [defn-] included — see
are a separate gap (TODO.org, "A package cannot mark a name private" — [Check.private_ref] for where that one is refused. *)
[rl/get-color-raw] should not be callable either). *)
let exported n = not (String.equal n "main") let exported n = not (String.equal n "main")
(* Where a name is *used*, which is what a refusal has to point at. A rename (* Where a name is *used*, which is what a refusal has to point at. A rename

View File

@ -684,7 +684,7 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
a head, which is the same property that makes a quasiquoted macro call a head, which is the same property that makes a quasiquoted macro call
output rather than a dependency. Building a declaration as a value is what output rather than a dependency. Building a declaration as a value is what
a macro is for. *) a macro is for. *)
| Sym ("defmacro" | "defn" | "def" | "defonce" | "defconst" | "defstruct" | Sym ("defmacro" | "defn" | "defn-" | "def" | "defonce" | "defconst" | "defstruct"
| "defdata" | "defunion" | "defclass" | "defgeneric" | "defmulti" | "defdata" | "defunion" | "defclass" | "defgeneric" | "defmulti"
| "defmethod" | "defenum" | "defalias" | "import" as name) -> | "defmethod" | "defenum" | "defalias" | "import" as name) ->
fail f fail f
@ -1374,7 +1374,10 @@ let rec decl (f : Form.t) : Ast.decl =
rule's and not this file's, and [Session.compatible] is where it is felt -- rule's and not this file's, and [Session.compatible] is where it is felt --
a redefinition that changes a signature is refused there, and this is a a redefinition that changes a signature is refused there, and this is a
way for a signature to change with nothing redefined. *) way for a signature to change with nothing redefined. *)
| List ({ v = Sym "defn"; _ } :: args) -> (* [defn-] is a [defn] in every respect but one: [Check.private_ref] refuses
a use of it from outside the package that declares it. *)
| List ({ v = Sym ("defn" | "defn-" as head); _ } :: args) ->
let fprivate = String.equal head "defn-" in
(match args with (match args with
| n :: { v = Vec ps; _ } :: ret :: body -> | n :: { v = Vec ps; _ } :: ret :: body ->
(* The slot's own failure, because the thing found there is almost (* The slot's own failure, because the thing found there is almost
@ -1408,11 +1411,12 @@ let rec decl (f : Form.t) : Ast.decl =
let fwhere, body = constraints body in let fwhere, body = constraints body in
mk (Ast.Defn { Ast.name = sym n; params = []; praw = Some (pitems ps); mk (Ast.Defn { Ast.name = sym n; params = []; praw = Some (pitems ps);
ret = Some rty; fwhere; fbody = body_of body; ret = Some rty; fwhere; fbody = body_of body;
nloc = n.loc }) nloc = n.loc; fprivate })
| _ -> | _ ->
fail f fail f
"defn is (defn name [param Type ...] ReturnType body ...). The return \ "%s is (%s name [param Type ...] ReturnType body ...). The return \
type is not optional; a function that returns nothing writes ()") type is not optional; a function that returns nothing writes ()"
head head)
(* ── The dyn side's classes and generic functions ────────────────── (* ── The dyn side's classes and generic functions ──────────────────
Four forms, all of them shorthand: nothing below [Classes.expand] knows Four forms, all of them shorthand: nothing below [Classes.expand] knows
@ -1463,7 +1467,7 @@ let rec decl (f : Form.t) : Ast.decl =
else fun fn -> Ast.Defmulti fn) else fun fn -> Ast.Defmulti fn)
{ Ast.name = sym n; params = dyn_params which ps; praw = None; { Ast.name = sym n; params = dyn_params which ps; praw = None;
ret = Some (texpr ret); fwhere = []; fbody = body_of body; ret = Some (texpr ret); fwhere = []; fbody = body_of body;
nloc = n.loc }) nloc = n.loc; fprivate = false })
| _ -> fail f "%s" usage) | _ -> fail f "%s" usage)
| List ({ v = Sym "defmethod"; _ } :: args) -> | List ({ v = Sym "defmethod"; _ } :: args) ->
@ -1477,7 +1481,7 @@ let rec decl (f : Form.t) : Ast.decl =
mfn = { Ast.name = gen ^ "@" ^ Ast.dispatch_text k; mfn = { Ast.name = gen ^ "@" ^ Ast.dispatch_text k;
params = dyn_params "defmethod" ps; praw = None; params = dyn_params "defmethod" ps; praw = None;
ret = None; fwhere = []; fbody = body_of body; ret = None; fwhere = []; fbody = body_of body;
nloc = n.loc } }) nloc = n.loc; fprivate = false } })
| _ -> | _ ->
fail f fail f
"defmethod is (defmethod generic dispatch [param ...] body ...). \ "defmethod is (defmethod generic dispatch [param ...] body ...). \
@ -1508,11 +1512,12 @@ let rec decl (f : Form.t) : Ast.decl =
(match List.rev rest with (match List.rev rest with
| [ n; { v = Form.Vec ps; _ } ] -> | [ n; { v = Form.Vec ps; _ } ] ->
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None; mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
ret = None; fwhere = []; fbody = []; nloc = n.loc } csym) ret = None; fwhere = []; fbody = []; nloc = n.loc;
fprivate = false } csym)
| [ n; { v = Form.Vec ps; _ }; r ] -> | [ n; { v = Form.Vec ps; _ }; r ] ->
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None; mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
ret = Some (texpr r); fwhere = []; fbody = []; ret = Some (texpr r); fwhere = []; fbody = [];
nloc = n.loc } csym) nloc = n.loc; fprivate = false } csym)
| _ -> fail f "%s" usage) | _ -> fail f "%s" usage)
| _ -> fail f "%s" usage) | _ -> fail f "%s" usage)
@ -1744,7 +1749,7 @@ let rec decl (f : Form.t) : Ast.decl =
leave off. *) leave off. *)
praw = None; praw = None;
ret = Some form_t; fwhere = []; fbody = macro_body sg body; ret = Some form_t; fwhere = []; fbody = macro_body sg body;
nloc = n.loc }) nloc = n.loc; fprivate = false })
| _ -> | _ ->
fail f "defmacro is (defmacro name [param ...] body ...)") fail f "defmacro is (defmacro name [param ...] body ...)")

View File

@ -64,6 +64,10 @@
; pkg-generic-reject.flan import: the body has to be present where the copy ; pkg-generic-reject.flan import: the body has to be present where the copy
; is made, so the directory comes whole like every other package. ; is made, so the directory comes whole like every other package.
(glob_files programs/pkgs/gen/*) (glob_files programs/pkgs/gen/*)
; The package with a defn-, and the package that reaches into it:
; pkg-private*.flan.
(glob_files programs/pkgs/secret/*)
(glob_files programs/pkgs/nosy/*)
; The synthetic C header the importer's table reads. Committed rather than ; The synthetic C header the importer's table reads. Committed rather than
; reached for on the machine: the raylib case needs raylib installed, at the ; reached for on the machine: the raylib case needs raylib installed, at the
; right version, with a variable set, so it skips everywhere and covers ; right version, with a variable set, so it skips everywhere and covers
@ -235,7 +239,9 @@
(glob_files programs/pkgs/macspin/*) (glob_files programs/pkgs/macspin/*)
; And the package shadow-builtin.flan imports. ; And the package shadow-builtin.flan imports.
(glob_files programs/pkgs/shadowed/*) (glob_files programs/pkgs/shadowed/*)
(glob_files programs/pkgs/gen/*)) (glob_files programs/pkgs/gen/*)
(glob_files programs/pkgs/secret/*)
(glob_files programs/pkgs/nosy/*))
(action (run ./test_valgrind.exe))) (action (run ./test_valgrind.exe)))
; The corpus a fourth time, through the hand-written x86-64 backend, compared ; The corpus a fourth time, through the hand-written x86-64 backend, compared
@ -295,7 +301,9 @@
(glob_files programs/pkgs/macspin/*) (glob_files programs/pkgs/macspin/*)
; And the package shadow-builtin.flan imports. ; And the package shadow-builtin.flan imports.
(glob_files programs/pkgs/shadowed/*) (glob_files programs/pkgs/shadowed/*)
(glob_files programs/pkgs/gen/*)) (glob_files programs/pkgs/gen/*)
(glob_files programs/pkgs/secret/*)
(glob_files programs/pkgs/nosy/*))
(action (action
(setenv SURVEY_STRICT 1 (setenv SURVEY_STRICT 1
(setenv SURVEY_QUIET 1 (setenv SURVEY_QUIET 1
@ -472,7 +480,9 @@
(glob_files programs/pkgs/macspin/*) (glob_files programs/pkgs/macspin/*)
; And the package shadow-builtin.flan imports. ; And the package shadow-builtin.flan imports.
(glob_files programs/pkgs/shadowed/*) (glob_files programs/pkgs/shadowed/*)
(glob_files programs/pkgs/gen/*)) (glob_files programs/pkgs/gen/*)
(glob_files programs/pkgs/secret/*)
(glob_files programs/pkgs/nosy/*))
(action (action
(setenv SURVEY_STRICT 1 (setenv SURVEY_STRICT 1
(setenv SURVEY_QUIET 1 (setenv SURVEY_QUIET 1

View File

@ -0,0 +1,7 @@
;;;; A call to a package's private function, from outside the package.
(import secret "pkgs/secret")
(defn main [] i32
(print (secret/mix 1 2))
0)

View File

@ -0,0 +1,8 @@
;;;; A package that calls another package's private function. The refusal is
;;;; at nosy's call, in nosy's file.
(import nosy "pkgs/nosy")
(defn main [] i32
(print (nosy/peek 1))
0)

View File

@ -0,0 +1,7 @@
;;;; A package's private function, taken as a value from outside the package.
(import secret "pkgs/secret")
(defn main [] i32
(print (secret/apply2 secret/mix 1 2))
0)

View File

@ -0,0 +1,10 @@
;;;; A package's private function, used from inside the package: from the
;;;; file that declares it, from the package's other file, and as a value.
(import secret "pkgs/secret")
(defn main [] i32
(print (secret/combine 1 2)) (println "")
(print (secret/twice 3)) (println "")
(print (secret/via-value 4 5)) (println "")
0)

View File

@ -0,0 +1,6 @@
;;;; A package that imports secret and reaches for its private function under
;;;; the qualified name it arrives as.
(import secret "../secret")
(defn peek [a i32] i32 (secret/mix a 1))

View File

@ -0,0 +1,8 @@
;;;; The package's second file. It is in the same directory, so mix is its
;;;; own to call.
(defn twice [a i32] i32 (mix a a))
(defn apply2 [f (Fn [i32 i32] i32) a i32 b i32] i32 (f a b))
(defn via-value [a i32 b i32] i32 (apply2 mix a b))

View File

@ -0,0 +1,7 @@
;;;; A package with a private function. mix is declared with defn-, so only
;;;; the files in this directory may use it: combine calls it here, and the
;;;; package's other file calls it and hands it out as a value.
(defn- mix [a i32 b i32] i32 (+ (* a 10) b))
(defn combine [a i32 b i32] i32 (mix a b))

View File

@ -2778,6 +2778,11 @@ let () =
shape/Box and not area/shape/Box. *) shape/Box and not area/shape/Box. *)
outputs "a diamond, with a type crossing it" "programs/pkg-diamond.flan" outputs "a diamond, with a type crossing it" "programs/pkg-diamond.flan"
"3\n6\n20\n"; "3\n6\n20\n";
(* A [defn-] is callable from anywhere in its own package: the file that
declares it, the package's other file, and as a value handed out from
there. The refusals are with the others, further down. *)
outputs "a defn- used inside its package" "programs/pkg-private.flan"
"12\n33\n45\n";
(* A defn named after a builtin, and the boundary the shadow stops at. (* A defn named after a builtin, and the boundary the shadow stops at.
The numbers are the whole claim and none of them could be printed by The numbers are the whole claim and none of them could be printed by
the other reading: 7 is the program's own one-argument (get p), which the other reading: 7 is the program's own one-argument (get p), which
@ -3053,6 +3058,19 @@ let () =
if sand_checks then if sand_checks then
refuses "a package's main is not visible" "programs/pkg-hidden-main.flan" refuses "a package's main is not visible" "programs/pkg-hidden-main.flan"
"sand/main is not a name"; "sand/main is not a name";
(* [defn-]: a package's own function, refused at every use from outside
its directory — a call, the name taken as a value, and a call from a
second package that imports it, where the name arrives qualified under
the alias that package chose. The working half is
[outputs "a defn- used inside its package"]. *)
refuses "a defn- called from outside its package"
"programs/pkg-private-call.flan" "secret/mix is private to its package";
refuses "a defn- taken as a value from outside its package"
"programs/pkg-private-value.flan" "secret/mix is private to its package";
refuses "a defn- called from another package"
"programs/pkg-private-nested.flan" "secret/mix is private to its package";
refuses "and the refusal names the package's directory"
"programs/pkg-private-call.flan" "pkgs/secret";
refuses "one directory under two aliases" "programs/pkg-two-aliases.flan" refuses "one directory under two aliases" "programs/pkg-two-aliases.flan"
"one directory takes one alias"; "one directory takes one alias";
(* A ring is refused and the ring is named. The needle is the chain, not (* A ring is refused and the ring is named. The needle is the chain, not

View File

@ -590,6 +590,32 @@ let () =
(fun d -> try Unix.rmdir d with Unix.Unix_error _ -> ()) (fun d -> try Unix.rmdir d with Unix.Unix_error _ -> ())
[ pkg; tmp ]; [ pkg; tmp ];
(* ── A defn- through the dev loop ──────────────────────────────────
C-c C-c on a [defn-] in its package's own file redefines it under the
qualified name and the session keeps it private: the package's callers
still reach the new body, and a form evaluated in the importer's buffer
is still refused. A [defn-] in the program's own file is the program's
and is callable from the rest of it. *)
let tp, _ = Session.create ~file:"programs/pkg-private.flan" () in
(match Session.eval ~origin:"programs/pkgs/secret/secret.flan" tp
"(defn- mix [a i32 b i32] i32 (+ (* a 100) b))" with
| c ->
if not (has c.Session.ir "mul i32") then
fail "a defn- redefined in its package's file installed no new body"
| exception Loc.Error { Loc.dmsg = m; _ } ->
fail "redefining a defn- in its package's file: %s" m);
(match Session.eval ~origin:"programs/pkg-private.flan" tp
"(defn peek [] i32 (secret/mix 1 2))" with
| _ -> fail "a redefined defn- became callable from outside its package"
| exception Loc.Error { Loc.dmsg = m; _ } ->
if not (has m "secret/mix is private to its package") then
fail "a call to a redefined defn- was refused for another reason: %s" m);
(match Session.eval ~origin:"programs/pkg-private.flan" tp
"(defn- helper [] i32 7)\n(defn use-helper [] i32 (helper))" with
| _ -> ()
| exception Loc.Error { Loc.dmsg = m; _ } ->
fail "a defn- in the program's own file: %s" m);
(* ── C-x C-e expands, which it never used to ──────────────────────── (* ── C-x C-e expands, which it never used to ────────────────────────
[Parse.expr] did not call the expander at all, so an expression typed at [Parse.expr] did not call the expander at all, so an expression typed at
the REPL saw no macros — not a package's and not the prelude's, which is the REPL saw no macros — not a package's and not the prelude's, which is

28
vendor/edn/edn.flan vendored
View File

@ -228,18 +228,18 @@
;; A comma is whitespace in EDN, which is the rule most hand-written readers ;; A comma is whitespace in EDN, which is the rule most hand-written readers
;; get wrong: {:a 1, :b 2} is one map and the comma is not a token. ;; get wrong: {:a 1, :b 2} is one map and the comma is not a token.
(defn ws? [b u8] bool (defn- ws? [b u8] bool
(or (space? b) (= b \,))) (or (space? b) (= b \,)))
;; Everything that ends an unquoted token. Note `;` is here: `[1;c` has the ;; Everything that ends an unquoted token. Note `;` is here: `[1;c` has the
;; comment start immediately after the 1, with no space, and a scanner that ;; comment start immediately after the 1, with no space, and a scanner that
;; only stopped on whitespace and brackets would read "1;c" as one number. ;; only stopped on whitespace and brackets would read "1;c" as one number.
(defn delim? [b u8] bool (defn- delim? [b u8] bool
(or (ws? b) (or (ws? b)
(= b \() (= b \)) (= b \[) (= b \]) (= b \{) (= b \}) (= b \() (= b \)) (= b \[) (= b \]) (= b \{) (= b \})
(= b \") (= b \;))) (= b \") (= b \;)))
(defn alpha? [b u8] bool (defn- alpha? [b u8] bool
(or (and (>= b \a) (<= b \z)) (or (and (>= b \a) (<= b \z))
(and (>= b \A) (<= b \Z)))) (and (>= b \A) (<= b \Z))))
@ -247,7 +247,7 @@
;; "anything that is not a delimiter": without it every stray byte becomes a ;; "anything that is not a delimiter": without it every stray byte becomes a
;; one-character symbol, and `@` or a backtick — a Clojure reader macro, not ;; one-character symbol, and `@` or a backtick — a Clojure reader macro, not
;; EDN — reads as a name instead of being reported at the byte it is on. ;; EDN — reads as a name instead of being reported at the byte it is on.
(defn sym-start? [b u8] bool (defn- sym-start? [b u8] bool
(or (alpha? b) (or (alpha? b)
(= b \.) (= b \*) (= b \+) (= b \!) (= b \-) (= b \_) (= b \.) (= b \*) (= b \+) (= b \!) (= b \-) (= b \_)
(= b \?) (= b \$) (= b \%) (= b \&) (= b \=) (= b \<) (= b \>) (= b \?) (= b \$) (= b \%) (= b \&) (= b \=) (= b \<) (= b \>)
@ -259,26 +259,26 @@
;; yet, so edn/scan-atom and edn/push-open are as callable as edn/next is. ;; yet, so edn/scan-atom and edn/push-open are as callable as edn/next is.
;; Nothing below is part of the API and none of it will keep its shape. ;; Nothing below is part of the API and none of it will keep its shape.
(defn at-end? [c (Ptr Cursor)] bool (defn- at-end? [c (Ptr Cursor)] bool
(>= (.pos c) (length (.src c)))) (>= (.pos c) (length (.src c))))
;; An empty slice of src, positioned at p. Used for the tokens that have no ;; An empty slice of src, positioned at p. Used for the tokens that have no
;; text of their own — eof, error, and every delimiter. It is still a slice of ;; text of their own — eof, error, and every delimiter. It is still a slice of
;; the input rather than a slice of nothing, so `text` has one meaning for all ;; the input rather than a slice of nothing, so `text` has one meaning for all
;; token kinds. ;; token kinds.
(defn empty-at [c (Ptr Cursor) p i32] [u8] (defn- empty-at [c (Ptr Cursor) p i32] [u8]
(slice (.src c) p p)) (slice (.src c) p p))
(defn token [c (Ptr Cursor) kind i32 lo i32 hi i32 p i32] Token (defn- token [c (Ptr Cursor) kind i32 lo i32 hi i32 p i32] Token
(Token {.kind kind .text (slice (.src c) lo hi) .pos p})) (Token {.kind kind .text (slice (.src c) lo hi) .pos p}))
(defn error-token [c (Ptr Cursor)] Token (defn- error-token [c (Ptr Cursor)] Token
(Token {.kind tok-error .text (empty-at c (.err-pos c)) .pos (.err-pos c)})) (Token {.kind tok-error .text (empty-at c (.err-pos c)) .pos (.err-pos c)}))
;; Whitespace, commas, and `;` comments, which run to the newline or to the end ;; Whitespace, commas, and `;` comments, which run to the newline or to the end
;; of input — a comment on the last line of a file with no trailing newline is ;; of input — a comment on the last line of a file with no trailing newline is
;; the case that decides whether the loop tests the length before the byte. ;; the case that decides whether the loop tests the length before the byte.
(defn skip-trivia [c (Ptr Cursor)] () (defn- skip-trivia [c (Ptr Cursor)] ()
(while (not (at-end? c)) (while (not (at-end? c))
(let [b (at (.src c) (.pos c))] (let [b (at (.src c) (.pos c))]
(cond (cond
@ -313,7 +313,7 @@
(set (.depth c) (+ (.depth c) 1)) (set (.depth c) (+ (.depth c) 1))
true) true)
(defn pop-close [c (Ptr Cursor) closer i32 p i32] bool (defn- pop-close [c (Ptr Cursor) closer i32 p i32] bool
(when (or (= (.depth c) 0) (when (or (= (.depth c) 0)
(!= (at (.open c) (- (.depth c) 1)) closer)) (!= (at (.open c) (- (.depth c) 1)) closer))
(fail c err-unbalanced p) (fail c err-unbalanced p)
@ -325,7 +325,7 @@
;; A token starting with a digit, or with a sign or a dot followed by one. ;; A token starting with a digit, or with a sign or a dot followed by one.
;; `-` alone is a symbol in EDN and stays one here. ;; `-` alone is a symbol in EDN and stays one here.
(defn number-start? [c (Ptr Cursor) i i32] bool (defn- number-start? [c (Ptr Cursor) i i32] bool
(let [s (.src c)] (let [s (.src c)]
(when (>= i (length s)) (when (>= i (length s))
(return false)) (return false))
@ -335,7 +335,7 @@
(< (+ i 1) (length s)) (< (+ i 1) (length s))
(digit? (at s (+ i 1)))))) (digit? (at s (+ i 1))))))
(defn read-number [c (Ptr Cursor) lo i32] Token (defn- read-number [c (Ptr Cursor) lo i32] Token
(let [hi (scan-atom c lo)] (let [hi (scan-atom c lo)]
(set (.pos c) hi) (set (.pos c) hi)
(let [text (slice (.src c) lo hi)] (let [text (slice (.src c) lo hi)]
@ -362,7 +362,7 @@
;; A backslash anywhere inside is the refusal, reported at the backslash ;; A backslash anywhere inside is the refusal, reported at the backslash
;; rather than at the start of the string, because the backslash is what has ;; rather than at the start of the string, because the backslash is what has
;; to be removed. ;; to be removed.
(defn read-string [c (Ptr Cursor) lo i32] Token (defn- read-string [c (Ptr Cursor) lo i32] Token
(let [i (+ lo 1) (let [i (+ lo 1)
s (.src c)] s (.src c)]
(while (< i (length s)) (while (< i (length s))
@ -536,7 +536,7 @@
(Some (bytes=? (.text t) (bytes-view "true"))) (Some (bytes=? (.text t) (bytes-view "true")))
None)) None))
(defn text=? [t Token s string] bool (defn- text=? [t Token s string] bool
(bytes=? (.text t) (bytes-view s))) (bytes=? (.text t) (bytes-view s)))
;; A keyword whose name is s. The leading colon is not part of `text`, so this ;; A keyword whose name is s. The leading colon is not part of `text`, so this

View File

@ -57,13 +57,13 @@
;; ── Small string work, for the refusals and the names ─────────────── ;; ── Small string work, for the refusals and the names ───────────────
(defn joined [a string b string] string (defn- joined [a string b string] string
(let [v (vec-new u8)] (let [v (vec-new u8)]
(append (addr v) (bytes-view a)) (append (addr v) (bytes-view a))
(append (addr v) (bytes-view b)) (append (addr v) (bytes-view b))
(string (slice v)))) (string (slice v))))
(defn joined3 [a string b string c string] string (defn- joined3 [a string b string c string] string
(joined a (joined b c))) (joined a (joined b c)))
;; Copied out, and not `(string (i64->bytes n))`. The prelude's note over ;; Copied out, and not `(string (i64->bytes n))`. The prelude's note over
@ -71,7 +71,7 @@
;; in the runtime, so two of its results cannot be held at once — and `where` ;; in the runtime, so two of its results cannot be held at once — and `where`
;; below holds a line and a column at the same time, which read as the same ;; below holds a line and a column at the same time, which read as the same
;; number until this copied. ;; number until this copied.
(defn i64->string [n i64] string (defn- i64->string [n i64] string
(let [v (vec-new u8)] (let [v (vec-new u8)]
(append-i64 (addr v) n) (append-i64 (addr v) n)
(string (slice v)))) (string (slice v))))
@ -80,7 +80,7 @@
;; buffer costs nothing to produce. A person reading a refusal wants a line and ;; buffer costs nothing to produce. A person reading a refusal wants a line and
;; a column, so the newlines before the offset are counted here — once per ;; a column, so the newlines before the offset are counted here — once per
;; refusal, which is as often as this is ever called. ;; refusal, which is as often as this is ever called.
(defn where [src [u8] pos i32] string (defn- where [src [u8] pos i32] string
(let [line (i64 1) (let [line (i64 1)
col (i64 1) col (i64 1)
i (i32 0)] i (i32 0)]
@ -110,13 +110,13 @@
reader Form reader Form
bad string]) bad string])
(defn derived-bad [msg string] Derived (defn- derived-bad [msg string] Derived
(Derived {.ty `i64 .decls (form-nil) .reader `0 .bad msg})) (Derived {.ty `i64 .decls (form-nil) .reader `0 .bad msg}))
(defn ok-derived [ty Form decls [Form] reader Form] Derived (defn- ok-derived [ty Form decls [Form] reader Form] Derived
(Derived {.ty ty .decls decls .reader reader .bad ""})) (Derived {.ty ty .decls decls .reader reader .bad ""}))
(defn bad? [d Derived] bool (defn- bad? [d Derived] bool
(> (length (bytes-view (.bad d))) 0)) (> (length (bytes-view (.bad d))) 0))
;; ── The scalars a generated reader calls ──────────────────────────── ;; ── The scalars a generated reader calls ────────────────────────────
@ -212,7 +212,7 @@
;; One value, from the cursor's current position, consumed. `name` is what a ;; One value, from the cursor's current position, consumed. `name` is what a
;; struct here would be called; `src` is the whole buffer, for the positions a ;; struct here would be called; `src` is the whole buffer, for the positions a
;; refusal names. ;; refusal names.
(defn derive [c (Ptr Cursor) name string src [u8]] Derived (defn- derive [c (Ptr Cursor) name string src [u8]] Derived
(let [t (next c)] (let [t (next c)]
(when (not (ok? c)) (when (not (ok? c))
(return (derived-bad (return (derived-bad
@ -242,7 +242,7 @@
;; decides; every one after it is compared against that decision and both ;; decides; every one after it is compared against that decision and both
;; positions are named when they disagree, because "heterogeneous" without ;; positions are named when they disagree, because "heterogeneous" without
;; saying where sends someone to read the whole file. ;; saying where sends someone to read the whole file.
(defn derive-vec [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived (defn- derive-vec [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived
(when (at-byte? c \]) (when (at-byte? c \])
(return (derived-bad (return (derived-bad
(joined3 "the empty vector at " (where src at-pos) (joined3 "the empty vector at " (where src at-pos)
@ -286,12 +286,12 @@
;; a signature, and the bare call in the body gets it from `want`. It is also ;; a signature, and the bare call in the body gets it from `want`. It is also
;; the more readable expansion: the reader says `(cells-new a)` where it would ;; the more readable expansion: the reader says `(cells-new a)` where it would
;; otherwise carry a type nobody wrote. ;; otherwise carry a type nobody wrote.
(defn with-decl [decls [Form] d Form] [Form] (defn- with-decl [decls [Form] d Form] [Form]
(form-append decls (form-cons d (form-nil)))) (form-append decls (form-cons d (form-nil))))
;; A set becomes `(Map T bool)`, so its elements are map keys. `derive-key` is ;; A set becomes `(Map T bool)`, so its elements are map keys. `derive-key` is
;; where that constraint is enforced and said. ;; where that constraint is enforced and said.
(defn derive-set [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived (defn- derive-set [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived
(when (at-byte? c \}) (when (at-byte? c \})
(return (derived-bad (return (derived-bad
(joined3 "the empty set at " (where src at-pos) (joined3 "the empty set at " (where src at-pos)
@ -326,7 +326,7 @@
;; fixed array, which is one where a Vec is not; anything else is refused here ;; fixed array, which is one where a Vec is not; anything else is refused here
;; rather than at the `(Map ...)` the caller would build out of it, because a ;; rather than at the `(Map ...)` the caller would build out of it, because a
;; map-key refusal names a type nobody wrote. ;; map-key refusal names a type nobody wrote.
(defn derive-key [c (Ptr Cursor) name string src [u8]] Derived (defn- derive-key [c (Ptr Cursor) name string src [u8]] Derived
(when (at-byte? c \[) (when (at-byte? c \[)
(return (derive-array c name src))) (return (derive-array c name src)))
(let [d (derive c name src)] (let [d (derive c name src)]
@ -342,7 +342,7 @@
;; of the set has to be the same length as well as the same shape — which falls ;; of the set has to be the same length as well as the same shape — which falls
;; out of the type comparison the caller already makes, since the length is in ;; out of the type comparison the caller already makes, since the length is in
;; the type it compares. ;; the type it compares.
(defn derive-array [c (Ptr Cursor) name string src [u8]] Derived (defn- derive-array [c (Ptr Cursor) name string src [u8]] Derived
(let [open (next c)] (let [open (next c)]
(when (at-byte? c \]) (when (at-byte? c \])
(return (derived-bad (return (derived-bad
@ -379,7 +379,7 @@
(expect c tok-vec-close) (expect c tok-vec-close)
arr))))))) arr)))))))
(defn disagreement [what string src [u8] at-pos i32 n i64 (defn- disagreement [what string src [u8] at-pos i32 n i64
first Form second Form] string first Form second Form] string
(joined3 (joined3 "the " what " at ") (joined3 (joined3 "the " what " at ")
(where src at-pos) (where src at-pos)
@ -400,7 +400,7 @@
;; differently is the two arms a hand-written reader had no reason to have — a ;; differently is the two arms a hand-written reader had no reason to have — a
;; key that is not a field of the struct, and a field the file did not have. ;; key that is not a field of the struct, and a field the file did not have.
;; Both signal SchemaDrift. See the note over that type. ;; Both signal SchemaDrift. See the note over that type.
(defn derive-map [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived (defn- derive-map [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived
(when (at-byte? c \}) (when (at-byte? c \})
(return (derived-bad (return (derived-bad
(joined3 "the empty map at " (where src at-pos) (joined3 "the empty map at " (where src at-pos)
@ -485,7 +485,7 @@
;; ── Comparing and rendering a type form ───────────────────────────── ;; ── Comparing and rendering a type form ─────────────────────────────
(defn same-type? [a Form b Form] bool (defn- same-type? [a Form b Form] bool
(bytes=? (bytes-view (render a)) (bytes-view (render b)))) (bytes=? (bytes-view (render a)) (bytes-view (render b))))
;; A type form as text, for the refusals. Only the shapes this file builds — a ;; A type form as text, for the refusals. Only the shapes this file builds — a
@ -499,7 +499,7 @@
(Form.Vec xs) (joined3 "[" (render-items xs) "]") (Form.Vec xs) (joined3 "[" (render-items xs) "]")
_ "?")) _ "?"))
(defn render-items [xs [Form]] string (defn- render-items [xs [Form]] string
(let [out ""] (let [out ""]
(dotimes [i (length xs)] (dotimes [i (length xs)]
(set out (if (= i 0) (set out (if (= i 0)
@ -510,7 +510,7 @@
;; What check.ml takes as a map key, narrowed to what this file can produce. ;; What check.ml takes as a map key, narrowed to what this file can produce.
;; A float is deliberately absent and the checker says why: NaN is not equal to ;; A float is deliberately absent and the checker says why: NaN is not equal to
;; itself, so there is no equality for a map to hash. ;; itself, so there is no equality for a map to hash.
(defn key-type? [t Form] bool (defn- key-type? [t Form] bool
(let [s (bytes-view (render t))] (let [s (bytes-view (render t))]
(or (bytes=? s (bytes-view "i64")) (or (bytes=? s (bytes-view "i64"))
(or (bytes=? s (bytes-view "bool")) (or (bytes=? s (bytes-view "bool"))
@ -543,7 +543,7 @@
_ (refuse "defedn's first argument is the name of the struct to declare, written as a name")) _ (refuse "defedn's first argument is the name of the struct to declare, written as a name"))
_ (refuse "defedn's second argument is the path to the data file, written as a string literal — the file is read while this is being compiled, so there is nothing here to compute a path from")))) _ (refuse "defedn's second argument is the path to the data file, written as a string literal — the file is read while this is being compiled, so there is nothing here to compute a path from"))))
(defn provide [name string path string src [u8]] Form (defn- provide [name string path string src [u8]] Form
(let [cur (cursor src) (let [cur (cursor src)
d (derive (addr cur) name src)] d (derive (addr cur) name src)]
(if (bad? d) (if (bad? d)
@ -580,5 +580,5 @@
;; already put the report on the `defedn` the author wrote. The name is a ;; already put the report on the `defedn` the author wrote. The name is a
;; gensym, so two refusals in one file are two reports rather than a name ;; gensym, so two refusals in one file are two reports rather than a name
;; defined twice. ;; defined twice.
(defn refuse [msg string] Form (defn- refuse [msg string] Form
`(defn ~(gensym) [] () (compile-error ~(Form.Str {.s msg})))) `(defn ~(gensym) [] () (compile-error ~(Form.Str {.s msg}))))

32
vendor/json/json.flan vendored
View File

@ -281,21 +281,21 @@
;; start against the byte before it — `1//x` — and a scanner that stopped only ;; start against the byte before it — `1//x` — and a scanner that stopped only
;; on whitespace and brackets would read `1//x` as one atom and then report a ;; on whitespace and brackets would read `1//x` as one atom and then report a
;; bad number instead of a comment. ;; bad number instead of a comment.
(defn delim? [b u8] bool (defn- delim? [b u8] bool
(or (space? b) (or (space? b)
(= b \() (= b \)) (= b \[) (= b \]) (= b \{) (= b \}) (= b \() (= b \)) (= b \[) (= b \]) (= b \{) (= b \})
(= b \") (= b \') (= b \,) (= b \:) (= b \/))) (= b \") (= b \') (= b \,) (= b \:) (= b \/)))
(defn alpha? [b u8] bool (defn- alpha? [b u8] bool
(or (and (>= b \a) (<= b \z)) (or (and (>= b \a) (<= b \z))
(and (>= b \A) (<= b \Z)))) (and (>= b \A) (<= b \Z))))
(defn hex? [b u8] bool (defn- hex? [b u8] bool
(or (digit? b) (or (digit? b)
(and (>= b \a) (<= b \f)) (and (>= b \a) (<= b \f))
(and (>= b \A) (<= b \F)))) (and (>= b \A) (<= b \F))))
(defn hex-val [b u8] i32 (defn- hex-val [b u8] i32
(cond (cond
(digit? b) (- (i32 b) (i32 \0)) (digit? b) (- (i32 b) (i32 \0))
(and (>= b \a) (<= b \f)) (+ 10 (- (i32 b) (i32 \a))) (and (>= b \a) (<= b \f)) (+ 10 (- (i32 b) (i32 \a)))
@ -307,24 +307,24 @@
;; yet, so json/scan-atom and json/push-open are as callable as json/next is. ;; yet, so json/scan-atom and json/push-open are as callable as json/next is.
;; Nothing below is part of the API and none of it will keep its shape. ;; Nothing below is part of the API and none of it will keep its shape.
(defn at-end? [c (Ptr Cursor)] bool (defn- at-end? [c (Ptr Cursor)] bool
(>= (.pos c) (length (.src c)))) (>= (.pos c) (length (.src c))))
;; An empty slice of src, positioned at p. Used for the tokens that have no ;; An empty slice of src, positioned at p. Used for the tokens that have no
;; text of their own — eof, error, and every delimiter — so that `text` has one ;; text of their own — eof, error, and every delimiter — so that `text` has one
;; meaning for every token kind and not two. ;; meaning for every token kind and not two.
(defn empty-at [c (Ptr Cursor) p i32] [u8] (defn- empty-at [c (Ptr Cursor) p i32] [u8]
(slice (.src c) p p)) (slice (.src c) p p))
(defn token [c (Ptr Cursor) kind i32 lo i32 hi i32 p i32] Token (defn- token [c (Ptr Cursor) kind i32 lo i32 hi i32 p i32] Token
(Token {.kind kind .text (slice (.src c) lo hi) .pos p})) (Token {.kind kind .text (slice (.src c) lo hi) .pos p}))
(defn error-token [c (Ptr Cursor)] Token (defn- error-token [c (Ptr Cursor)] Token
(Token {.kind tok-error .text (empty-at c (.err-pos c)) .pos (.err-pos c)})) (Token {.kind tok-error .text (empty-at c (.err-pos c)) .pos (.err-pos c)}))
;; Whitespace only. There is no comment case here and there is deliberately no ;; Whitespace only. There is no comment case here and there is deliberately no
;; comment case anywhere: a `/` reaches the dispatch and is refused by name. ;; comment case anywhere: a `/` reaches the dispatch and is refused by name.
(defn skip-trivia [c (Ptr Cursor)] () (defn- skip-trivia [c (Ptr Cursor)] ()
(while (and (not (at-end? c)) (space? (at (.src c) (.pos c)))) (while (and (not (at-end? c)) (space? (at (.src c) (.pos c))))
(set (.pos c) (+ (.pos c) 1)))) (set (.pos c) (+ (.pos c) 1))))
@ -344,7 +344,7 @@
(set (.depth c) (+ (.depth c) 1)) (set (.depth c) (+ (.depth c) 1))
true) true)
(defn pop-close [c (Ptr Cursor) closer i32 p i32] bool (defn- pop-close [c (Ptr Cursor) closer i32 p i32] bool
(when (or (= (.depth c) 0) (when (or (= (.depth c) 0)
(!= (at (.open c) (- (.depth c) 1)) closer)) (!= (at (.open c) (- (.depth c) 1)) closer))
(fail c err-unbalanced p) (fail c err-unbalanced p)
@ -358,7 +358,7 @@
;; are here so that they reach the scanner and get the refusal that names them, ;; are here so that they reach the scanner and get the refusal that names them,
;; instead of falling through to "unexpected byte" — which would be true and ;; instead of falling through to "unexpected byte" — which would be true and
;; would not tell anyone that the fix is to write 0.5. ;; would not tell anyone that the fix is to write 0.5.
(defn number-start? [b u8] bool (defn- number-start? [b u8] bool
(or (digit? b) (= b \-) (= b \+) (= b \.))) (or (digit? b) (= b \-) (= b \+) (= b \.)))
;; JSON's number grammar, written out rather than left to parse-i64: -?(0 | ;; JSON's number grammar, written out rather than left to parse-i64: -?(0 |
@ -371,7 +371,7 @@
;; A number with neither a fraction nor an exponent is tok-int and anything ;; A number with neither a fraction nor an exponent is tok-int and anything
;; else is tok-float, so 1e3 is a float even though its value is whole. That is ;; else is tok-float, so 1e3 is a float even though its value is whole. That is
;; the grammar's own split and not a guess about the caller's field. ;; the grammar's own split and not a guess about the caller's field.
(defn read-number [c (Ptr Cursor) lo i32] Token (defn- read-number [c (Ptr Cursor) lo i32] Token
(let [s (.src c) (let [s (.src c)
i lo i lo
float? false] float? false]
@ -447,7 +447,7 @@
;; Four hex digits starting at i, as a code point, or -1. Used twice — for an ;; Four hex digits starting at i, as a code point, or -1. Used twice — for an
;; escape and for the low half of a surrogate pair — which is the whole reason ;; escape and for the low half of a surrogate pair — which is the whole reason
;; it is a function. ;; it is a function.
(defn hex4 [c (Ptr Cursor) i i32] i32 (defn- hex4 [c (Ptr Cursor) i i32] i32
(let [s (.src c)] (let [s (.src c)]
(when (> (+ i 4) (length s)) (when (> (+ i 4) (length s))
(return -1)) (return -1))
@ -459,8 +459,8 @@
(set v (+ (* v 16) (hex-val b))))) (set v (+ (* v 16) (hex-val b)))))
v))) v)))
(defn high-surrogate? [r i32] bool (and (>= r 0xd800) (<= r 0xdbff))) (defn- high-surrogate? [r i32] bool (and (>= r 0xd800) (<= r 0xdbff)))
(defn low-surrogate? [r i32] bool (and (>= r 0xdc00) (<= r 0xdfff))) (defn- low-surrogate? [r i32] bool (and (>= r 0xdc00) (<= r 0xdfff)))
;; The whole reason this is not three lines. `text` is the interior, between ;; The whole reason this is not three lines. `text` is the interior, between
;; the quotes and RAW; `pos` is the opening quote, so an editor underlines the ;; the quotes and RAW; `pos` is the opening quote, so an editor underlines the
@ -472,7 +472,7 @@
;; token rather than at the backslash. Surrogate PAIRING is checked here too, ;; token rather than at the backslash. Surrogate PAIRING is checked here too,
;; and not only escape syntax, so that string-of's encode-rune can never be ;; and not only escape syntax, so that string-of's encode-rune can never be
;; handed a code point the prelude refuses. ;; handed a code point the prelude refuses.
(defn read-string [c (Ptr Cursor) lo i32] Token (defn- read-string [c (Ptr Cursor) lo i32] Token
(let [s (.src c) (let [s (.src c)
i (+ lo 1)] i (+ lo 1)]
(while (< i (length s)) (while (< i (length s))

View File

@ -42,20 +42,20 @@
;; ── Small string work ─────────────────────────────────────────────── ;; ── Small string work ───────────────────────────────────────────────
(defn joined [a string b string] string (defn- joined [a string b string] string
(let [v (vec-new u8)] (let [v (vec-new u8)]
(append (addr v) (bytes-view a)) (append (addr v) (bytes-view a))
(append (addr v) (bytes-view b)) (append (addr v) (bytes-view b))
(string (slice v)))) (string (slice v))))
(defn joined3 [a string b string c string] string (defn- joined3 [a string b string c string] string
(joined a (joined b c))) (joined a (joined b c)))
;; Copied out, and not `(string (i64->bytes n))`: the prelude's note over ;; Copied out, and not `(string (i64->bytes n))`: the prelude's note over
;; append-i64 is the reason — i64->bytes renders into one shared static buffer ;; append-i64 is the reason — i64->bytes renders into one shared static buffer
;; in the runtime, so two of its results cannot be held at once, and `where` ;; in the runtime, so two of its results cannot be held at once, and `where`
;; holds a line and a column at the same time. ;; holds a line and a column at the same time.
(defn i64->string [n i64] string (defn- i64->string [n i64] string
(let [v (vec-new u8)] (let [v (vec-new u8)]
(append-i64 (addr v) n) (append-i64 (addr v) n)
(string (slice v)))) (string (slice v))))
@ -63,7 +63,7 @@
;; The tokenizer answers byte offsets. A person reading a refusal wants a line ;; The tokenizer answers byte offsets. A person reading a refusal wants a line
;; and a column, so the newlines before the offset are counted here — once per ;; and a column, so the newlines before the offset are counted here — once per
;; refusal, which is as often as this is ever called. ;; refusal, which is as often as this is ever called.
(defn where [src [u8] pos i32] string (defn- where [src [u8] pos i32] string
(let [line (i64 1) (let [line (i64 1)
col (i64 1) col (i64 1)
i (i32 0)] i (i32 0)]
@ -87,16 +87,16 @@
reader Form reader Form
bad string]) bad string])
(defn derived-bad [msg string] Derived (defn- derived-bad [msg string] Derived
(Derived {.ty `i64 .decls (form-nil) .reader `0 .bad msg})) (Derived {.ty `i64 .decls (form-nil) .reader `0 .bad msg}))
(defn ok-derived [ty Form decls [Form] reader Form] Derived (defn- ok-derived [ty Form decls [Form] reader Form] Derived
(Derived {.ty ty .decls decls .reader reader .bad ""})) (Derived {.ty ty .decls decls .reader reader .bad ""}))
(defn bad? [d Derived] bool (defn- bad? [d Derived] bool
(> (length (bytes-view (.bad d))) 0)) (> (length (bytes-view (.bad d))) 0))
(defn with-decl [decls [Form] d Form] [Form] (defn- with-decl [decls [Form] d Form] [Form]
(form-append decls (form-cons d (form-nil)))) (form-append decls (form-cons d (form-nil))))
;; ── The scalars a generated reader calls ──────────────────────────── ;; ── The scalars a generated reader calls ────────────────────────────
@ -173,7 +173,7 @@
;; ── Deriving ──────────────────────────────────────────────────────── ;; ── Deriving ────────────────────────────────────────────────────────
(defn derive [c (Ptr Cursor) name string src [u8]] Derived (defn- derive [c (Ptr Cursor) name string src [u8]] Derived
(let [t (next c)] (let [t (next c)]
(when (not (ok? c)) (when (not (ok? c))
(return (derived-bad (return (derived-bad
@ -202,7 +202,7 @@
;; every one after it is compared against that, and both positions are named ;; every one after it is compared against that, and both positions are named
;; when they disagree — "heterogeneous" on its own sends someone to read the ;; when they disagree — "heterogeneous" on its own sends someone to read the
;; whole file. ;; whole file.
(defn derive-array [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived (defn- derive-array [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived
(when (at-byte? c \]) (when (at-byte? c \])
(return (derived-bad (return (derived-bad
(joined3 "the empty array at " (where src at-pos) (joined3 "the empty array at " (where src at-pos)
@ -248,7 +248,7 @@
;; ── An object, which is a struct ──────────────────────────────────── ;; ── An object, which is a struct ────────────────────────────────────
(defn derive-object [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived (defn- derive-object [c (Ptr Cursor) name string at-pos i32 src [u8]] Derived
(when (at-byte? c \}) (when (at-byte? c \})
(return (derived-bad (return (derived-bad
(joined3 "the empty object at " (where src at-pos) (joined3 "the empty object at " (where src at-pos)
@ -349,7 +349,7 @@
(defn key=? [t Token s string] bool (defn key=? [t Token s string] bool
(bytes=? (.text t) (bytes-view s))) (bytes=? (.text t) (bytes-view s)))
(defn has-escape? [s [u8]] bool (defn- has-escape? [s [u8]] bool
(dotimes [i (length s)] (dotimes [i (length s)]
(when (= (at s i) \\) (when (= (at s i) \\)
(return true))) (return true)))
@ -358,7 +358,7 @@
;; What a field name may be made of. Deliberately narrower than what the reader ;; What a field name may be made of. Deliberately narrower than what the reader
;; would accept: this is the set a *person* would recognise as a name, and a ;; would accept: this is the set a *person* would recognise as a name, and a
;; member called "a b" or "x.y" has no field it could become. ;; member called "a b" or "x.y" has no field it could become.
(defn name-like? [s [u8]] bool (defn- name-like? [s [u8]] bool
(when (= (length s) 0) (when (= (length s) 0)
(return false)) (return false))
(dotimes [i (length s)] (dotimes [i (length s)]
@ -372,14 +372,14 @@
;; A copy of a token's raw text as a string. The Vec header is dropped here on ;; A copy of a token's raw text as a string. The Vec header is dropped here on
;; purpose: this runs inside the compiler, where an expansion is bounded by the ;; purpose: this runs inside the compiler, where an expansion is bounded by the
;; size of the program being compiled. ;; size of the program being compiled.
(defn copy-of [s [u8]] string (defn- copy-of [s [u8]] string
(let [b (vec-new u8)] (let [b (vec-new u8)]
(append (addr b) s) (append (addr b) s)
(string (slice b)))) (string (slice b))))
;; ── Comparing and rendering a type form ───────────────────────────── ;; ── Comparing and rendering a type form ─────────────────────────────
(defn same-type? [a Form b Form] bool (defn- same-type? [a Form b Form] bool
(bytes=? (bytes-view (render a)) (bytes-view (render b)))) (bytes=? (bytes-view (render a)) (bytes-view (render b))))
;; A type form as text, for the refusals. Only the shapes this file builds — a ;; A type form as text, for the refusals. Only the shapes this file builds — a
@ -392,7 +392,7 @@
(Form.Vec xs) (joined3 "[" (render-items xs) "]") (Form.Vec xs) (joined3 "[" (render-items xs) "]")
_ "?")) _ "?"))
(defn render-items [xs [Form]] string (defn- render-items [xs [Form]] string
(let [out ""] (let [out ""]
(dotimes [i (length xs)] (dotimes [i (length xs)]
(set out (if (= i 0) (set out (if (= i 0)
@ -424,7 +424,7 @@
_ (refuse "defjson's first argument is the name of the struct to declare, written as a name")) _ (refuse "defjson's first argument is the name of the struct to declare, written as a name"))
_ (refuse "defjson's second argument is the path to the data file, written as a string literal — the file is read while this is being compiled, so there is nothing here to compute a path from")))) _ (refuse "defjson's second argument is the path to the data file, written as a string literal — the file is read while this is being compiled, so there is nothing here to compute a path from"))))
(defn provide [name string path string src [u8]] Form (defn- provide [name string path string src [u8]] Form
(let [cur (cursor src) (let [cur (cursor src)
d (derive (addr cur) name src)] d (derive (addr cur) name src)]
(if (bad? d) (if (bad? d)
@ -460,5 +460,5 @@
;; calls: the checker walks it, the arm fires, and `Loc.from_macro` has already ;; calls: the checker walks it, the arm fires, and `Loc.from_macro` has already
;; put the report on the `defjson` the author wrote. The name is a gensym, so ;; put the report on the `defjson` the author wrote. The name is a gensym, so
;; two refusals in one file are two reports rather than a name defined twice. ;; two refusals in one file are two reports rather than a name defined twice.
(defn refuse [msg string] Form (defn- refuse [msg string] Form
`(defn ~(gensym) [] () (compile-error ~(Form.Str {.s msg})))) `(defn ~(gensym) [] () (compile-error ~(Form.Str {.s msg}))))

View File

@ -1523,8 +1523,13 @@ existed.</p>
refused at the line that wrote it.</li> refused at the line that wrote it.</li>
</ul> </ul>
<p>Visibility is that one rule and no more: there is no package-private marker for <p><strong>A function declared with <code>defn-</code> is private to its package.</strong>
anything other than <code>main</code> yet.</p> It is a <code>defn</code> in every other respect, and every file in the package's
directory can call it. A use from anywhere else — a call, or the name passed as a
value — is refused at compile time, and the message names the function and the
directory it belongs to. Only functions have a private form. A single-file package
shares its directory with the files beside it, so those files can call its
<code>defn-</code>s too.</p>
<p>A package may carry the C it binds to. Every <code>.c</code> file in the directory is <p>A package may carry the C it binds to. Every <code>.c</code> file in the directory is
compiled into the build, and a file named <code>link</code> lists extra linker compiled into the build, and a file named <code>link</code> lists extra linker
@ -2379,7 +2384,7 @@ refused inside a loop or a branch (a <code>let</code> is fine — it has the fun
extent); <code>find-restart</code> and <code>compute-restarts</code> are blocked on a extent); <code>find-restart</code> and <code>compute-restarts</code> are blocked on a
<code>Restart</code> type rather than on effort; a restart with parameters cannot be <code>Restart</code> type rather than on effort; a restart with parameters cannot be
taken from the break loop, which aims at a frame by position and has nothing to fill them taken from the break loop, which aims at a frame by position and has nothing to fill them
with; there is no package-private marker other than <code>main</code> not being exported; with;
and there are no threads in the language. The class facility plan.org describes is and there are no threads in the language. The class facility plan.org describes is
built — see <a href="#dyn">dyn</a> — and what is not built of it is the named-slot built — see <a href="#dyn">dyn</a> — and what is not built of it is the named-slot
constructor spelling and the user-written migration hook.</p> constructor spelling and the user-written migration hook.</p>
@ -2482,7 +2487,7 @@ disagree with the first.</p>
<script> <script>
// A small hand-written highlighter for the Flan blocks. One pass, no library. // A small hand-written highlighter for the Flan blocks. One pass, no library.
(function () { (function () {
var FORMS = new Set(("defn defstruct defenum defdata defunion defconst defonce def defalias " + var FORMS = new Set(("defn defn- defstruct defenum defdata defunion defconst defonce def defalias " +
"declare declare-c import package let if when unless cond do and or not while " + "declare declare-c import package let if when unless cond do and or not while " +
"until dotimes match set return some try defer signal error handler-bind " + "until dotimes match set return some try defer signal error handler-bind " +
"restart-case invoke-restart fn quote defmacro gensym " + "restart-case invoke-restart fn quote defmacro gensym " +