A defn- is private to its module, and the module's own macros may call it

This commit is contained in:
Joseph Ferano 2026-09-25 08:57:20 +07:00
commit 11dc230974
33 changed files with 433 additions and 108 deletions

View File

@ -531,11 +531,22 @@ 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
both.
** TODO A package cannot mark a name private
=rl/get-color-raw= is callable from outside its package. The refusal machinery
takes a second rule in one line; the blocker is that there is no way for a package
to *say* a name is private, and adding one is a parser change the author has to
choose a spelling for. Every lane has skipped it for that reason.
** DONE A package cannot mark a name private
CLOSED: [2026-09-25]
=defn-= declares a function private to its module; it is a =defn= otherwise.
The module is the import boundary: every file of a directory package, or the
one file of a package imported by naming the file. A use from outside — a call
or the name as a value — is refused in =Check=, so it holds under C-c C-c too.
Code the package's own macro wrote counts as inside (SBCL's rule, not
Clojure's); what the importer wrote, passed through or from its own macro, does
not. Functions only. The edn and json internals are now =defn-=;
=rl/get-color-raw= no longer exists. =docs/BUILT.md= has the placement.
** WAIT A private scoped to one file of a directory package
Deferred until a need appears: Odin's =@(private="file")=, a function visible
to its own file only when the package is a directory. =defn-= covers the
package; a one-file package already gets file scope because the file is the
module.
** CANCELLED A struct version word, so a redefined layout keeps working
CLOSED: [2026-09-20]

View File

@ -794,10 +794,29 @@ 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
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.
The gap is surface syntax and not `load.ml` — `exported` is one predicate and the refusal machinery that points at the
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
name private, and inventing one is a reader and parser change.
A function can be private to its module: `defn-`, Clojure's spelling, is a `defn` whose uses outside the module that
declares it are refused. It is not done in `load.ml`, although `main`'s refusal is. `Load.refuse_hidden` walks every
declaration after the rename, the package's own included, and by then the package's calls to its helper read
`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 file the
use is written in with 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.
The module is what `Load` imports as one unit. For a directory package that is every `.flan` file in the directory, so
the test is the two files' directories. For a package imported by naming a single file it is that file, and its
siblings in the directory are not part of it; the parser cannot tell which kind it is reading, so `Load.file_scoped`
narrows the flag to `Private_to_file` at import, and `Session.eval` does the same for a C-c C-c in such a file. A
file-only private inside a directory package, Odin's `@(private="file")`, is not provided.
Code a package's own macro wrote counts as inside the package wherever it is expanded, which is SBCL's rule: an
expansion there refers to the package's internal symbols freely. Clojure refuses the same thing. `Expand.unmarshal`
already separates the two kinds of node an expansion holds. A node the macro invented carries the call site's
location, tagged with the macro's name and no separate call site; a form the author wrote and the macro passed
through keeps its own position, with the call recorded in `msite`. So an invented node whose macro has the function's
qualifier is accepted, and an author's form is judged by the file it sits in. The macro name is the outermost one,
because the tag is outermost-wins: a call written by a package macro that was itself expanded out of an importer's
macro is judged as the importer's.
## 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
;; release behind the parser.
(defconst flan--definers
'("defn" "defmacro" "def" "defonce" "defconst" "defstruct" "defdata" "defunion"
'("defn" "defn-" "defmacro" "def" "defonce" "defconst" "defstruct" "defdata" "defunion"
"defenum" "defalias"
;; The object and dispatch heads (lib/parse.ml:1277-1334).
"defclass" "defgeneric" "defmulti" "defmethod"
@ -278,7 +278,7 @@ below — and not again here.")
;; 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
;; 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)
1)
;; Its own heading rather than a second `Functions' entry: a macro runs at
@ -575,6 +575,7 @@ For `syntax-propertize-function'."
;; the parameters and the body is optional; a count would have to know
;; whether one is there, and `:defn' does not care.
("defn" . :defn)
("defn-" . :defn)
;; `(declare-c NAME [params] RET "CSymbol")'. The name is the one special
;; argument; everything after it is written down the page in one column.
("declare" . 1)

View File

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

View File

@ -388,6 +388,12 @@
"and the name it introduces")
("(defonce seed i64 1)" "defonce" font-lock-keyword-face
"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")
("(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.
(with-temp-buffer
(flan-mode)
(dolist (head '("defmacro" "defn" "def" "defonce" "defconst" "defstruct"
(dolist (head '("defmacro" "defn" "defn-" "def" "defonce" "defconst" "defstruct"
"defdata" "defunion" "defenum" "defalias"
"defclass" "defgeneric" "defmulti" "defmethod"
"import" "declare" "declare-c"))

View File

@ -215,6 +215,12 @@ and pattern =
makes each instantiation check the concrete type answers yes. *)
type pred = { pname : string; pvar : string; ploc : Loc.t }
(* Who may use a function. [defn-] parses as [Private_to_package]; [Load]
narrows it to [Private_to_file] when the package is a single file named
outright, because that file is the whole module and its siblings in the
directory are not part of it. [Check.private_ref] is what enforces both. *)
type privacy = Exported | Private_to_package | Private_to_file
type fn = {
name : string;
params : field list;
@ -232,6 +238,7 @@ type fn = {
fwhere : pred list;
fbody : expr list;
nloc : Loc.t;
fprivate : privacy;
}
type decl = { d : decl_kind; dloc : Loc.t }

View File

@ -114,6 +114,9 @@ type env = {
function can show it. Kept apart from [fparams] because a foreign
[declare] has a location and no parameter vector worth showing. *)
fn_locs : (string, Loc.t) Hashtbl.t;
(* Every [defn-], by name, with where it was written and how far it is
visible. See [private_ref]. *)
privates : (string, Loc.t * Ast.privacy) Hashtbl.t;
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
second table rather than a third field, because every other reader of
@ -194,6 +197,7 @@ let new_env () = {
fns = Hashtbl.create 32;
fparams = Hashtbl.create 32;
fn_locs = Hashtbl.create 32;
privates = Hashtbl.create 8;
globals = Hashtbl.create 16;
global_locs = Hashtbl.create 16;
lifted = [];
@ -3912,6 +3916,7 @@ and var ctx ?(qualified = false) loc ~want name =
a question this language does not ask. *)
(match Hashtbl.find_opt ctx.env.fns name with
| Some (params, ret) ->
private_ref ctx loc name;
(* A foreign function is in [fns] too, and its emitted signature
is C's: no transfer channel, and an aggregate flattened by the
shim. Nothing could call the resulting pointer correctly, so it
@ -9004,11 +9009,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
| None -> assert false)
| _ when Hashtbl.mem ctx.env.gsigs name ->
private_ref ctx loc name;
let vars, params, ret = Hashtbl.find ctx.env.gsigs name in
generic_call ctx ~want loc name vars params ret args
| _ ->
match Hashtbl.find_opt ctx.env.fns name with
| Some (params, ret) ->
private_ref ctx loc name;
if List.length args <> List.length params then
fail loc "%s takes %d argument%s, given %d" name
(List.length params)
@ -9170,6 +9177,63 @@ and ordinary_call ctx ~want loc name args =
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
path — is not affected. *)
(* A [defn-] is its module's own. A use of one — a call, or the name taken
as a value — is refused unless it is written inside the module that
declares it. A directory package is every file in its directory, so the
test there is the two files' directories; a single file imported outright
is that file alone, which [Load] records by narrowing the flag to
[Private_to_file]. Realpath'd, because one side is usually the path the
importer was given on the command line and the other the one [Load]
resolved.
Code the package's own macro wrote counts as inside, wherever it was
expanded: SBCL's rule, where a macro's expansion refers to its package's
internal symbols freely. What the importer wrote itself does not, even when
it is an argument the macro passed through. [Expand.unmarshal] tells the two
apart already: a node the macro invented carries the call site's location
with the macro's name on it and no separate call site, and a form the author
wrote keeps its own position with the call recorded beside it. The macro's
package is its qualifier, which is the function's when both come from the
same module, because an import qualifies every name a directory declares
under the one alias it is read as.
Only a qualified name is asked about. An unqualified one belongs to the
program being built, and nothing outside a program can name it. *)
and private_ref ctx loc name =
let qualifier n =
match String.rindex_opt n '/' with
| Some i -> Some (String.sub n 0 i)
| None -> None
in
let written_by_own_macro () =
match loc.Loc.macro, loc.Loc.msite with
| Some m, None -> qualifier m <> None && qualifier m = qualifier name
| _ -> false
in
match Hashtbl.find_opt ctx.env.privates name with
| Some (at, scope) when String.contains name '/' ->
let real file = try Unix.realpath file with Unix.Unix_error _ -> file in
let inside =
match scope with
| Ast.Private_to_file -> String.equal (real at.Loc.file) (real loc.Loc.file)
| _ ->
String.equal (Filename.dirname (real at.Loc.file))
(Filename.dirname (real loc.Loc.file))
in
if not inside && not (written_by_own_macro ()) then begin
let where =
match scope with
| Ast.Private_to_file -> real at.Loc.file
| _ -> "the files in " ^ Filename.dirname (real at.Loc.file)
in
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-, so only %s \
can use it. Declaring it with defn instead makes it usable from here"
name where
end
| _ -> ()
and shadows_builtin ctx loc name =
(* 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. *)
@ -10611,6 +10675,9 @@ let collect env (decls : Ast.decl list) =
in
env.tyvars <- [];
env.tvpreds <- [];
if fn.Ast.fprivate <> Ast.Exported then
Hashtbl.replace env.privates fn.Ast.name
(fn.Ast.nloc, fn.Ast.fprivate);
if vars = [] then begin
Hashtbl.replace env.fns fn.Ast.name (params, ret);
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 :=
{ Ast.d =
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 = Ast.Exported },
f.csym);
dloc = f.cloc }
:: !decls)

View File

@ -175,7 +175,7 @@ let constructor n slots loc : Ast.decl =
Ast.Defn
{ Ast.name = n; params; praw = None; ret = Some (dyn_at loc);
fwhere = []; fbody = [ ex loc (Ast.MapLit (Some n, pairs)) ];
nloc = loc };
nloc = loc; fprivate = Ast.Exported };
dloc = loc }
(* 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
under two different aliases is refused, and so is a cycle.
Visibility is one rule so far: [main] is not exported. A package carrying
one would collide with the importer's, and worse, would keep everything it
Visibility is two rules. [main] is not exported: a package carrying one
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
library, on the target that cannot link it. Package-private markers for
anything else are still missing, which is why [rl/get-color-raw] is
callable.
library, on the target that cannot link it. And a [defn-] is exported like
any other name but refused at a use outside its package, which
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
directory is compiled into the build, and a file named [link] lists extra
@ -726,9 +727,8 @@ let imports_of (forms : Form.t list) =
exists to prevent.
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
are a separate gap (TODO.org, "A package cannot mark a name private" —
[rl/get-color-raw] should not be callable either). *)
is not a name. Everything else is exported, a [defn-] included — see
[Check.private_ref] for where that one is refused. *)
let exported n = not (String.equal n "main")
(* Where a name is *used*, which is what a refusal has to point at. A rename
@ -905,6 +905,20 @@ let refuse_hidden hidden ds =
(* Every top-level name the package declares — types and values alike, since a
use site is rewritten by name and the two never collide in one namespace. *)
(* A [defn-] in a package that is one file named outright is private to that
file: the file is the whole module, and whatever else sits in its directory
is not part of it. The parser cannot know which kind of package it is
reading, so the flag is narrowed here and in [Session.eval], the two places
that do. *)
let file_scoped (ds : Ast.decl list) =
List.map
(fun (d : Ast.decl) ->
match d.Ast.d with
| Ast.Defn ({ Ast.fprivate = Ast.Private_to_package; _ } as fn) ->
{ d with Ast.d = Ast.Defn { fn with Ast.fprivate = Ast.Private_to_file } }
| _ -> d)
ds
let owned_names (ds : Ast.decl list) =
List.filter_map Ast.declared_name ds
@ -1415,6 +1429,7 @@ let rec import ~seen ~open_ ~loc alias dir =
in
let owned = List.filter exported (owned_names ds) in
let decls = List.map (qualify_decl owned alias) own in
let decls = if one_file then file_scoped decls else decls in
let lflags = if one_file then [] else link_flags dir in
let csrcs = if one_file then [] else entries dir ".c" in
let phidden =

View File

@ -168,7 +168,7 @@ let reduce (forms : Form.t list) : Form.t list =
List.iter
(fun f ->
match head_name f with
| Some (("defn" | "defmacro"), n) when not (List.mem n !out) ->
| Some (("defn" | "defn-" | "defmacro"), n) when not (List.mem n !out) ->
if names_macro !out f then begin out := n :: !out; changed := true end
| _ -> ())
forms
@ -188,7 +188,7 @@ let reduce (forms : Form.t list) : Form.t list =
List.filter
(fun f ->
match head_name f with
| Some ("defn", n) -> not (List.mem n !out)
| Some (("defn" | "defn-"), n) -> not (List.mem n !out)
| _ -> true)
forms

View File

@ -710,7 +710,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
output rather than a dependency. Building a declaration as a value is what
a macro is for. *)
| Sym ("defmacro" | "defn" | "def" | "defonce" | "defconst" | "defstruct"
| Sym ("defmacro" | "defn" | "defn-" | "def" | "defonce" | "defconst" | "defstruct"
| "defdata" | "defunion" | "defclass" | "defgeneric" | "defmulti"
| "defmethod" | "defenum" | "defalias" | "import" as name) ->
fail f
@ -1400,7 +1400,12 @@ let rec decl (f : Form.t) : Ast.decl =
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
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 =
if String.equal head "defn-" then Ast.Private_to_package else Ast.Exported
in
(match args with
| n :: { v = Vec ps; _ } :: ret :: body ->
(* The slot's own failure, because the thing found there is almost
@ -1434,11 +1439,12 @@ let rec decl (f : Form.t) : Ast.decl =
let fwhere, body = constraints body in
mk (Ast.Defn { Ast.name = sym n; params = []; praw = Some (pitems ps);
ret = Some rty; fwhere; fbody = body_of body;
nloc = n.loc })
nloc = n.loc; fprivate })
| _ ->
fail f
"defn is (defn name [param Type ...] ReturnType body ...). The return \
type is not optional; a function that returns nothing writes ()")
"%s is (%s name [param Type ...] ReturnType body ...). The return \
type is not optional; a function that returns nothing writes ()"
head head)
(* ── The dyn side's classes and generic functions ──────────────────
Four forms, all of them shorthand: nothing below [Classes.expand] knows
@ -1489,7 +1495,7 @@ let rec decl (f : Form.t) : Ast.decl =
else fun fn -> Ast.Defmulti fn)
{ Ast.name = sym n; params = dyn_params which ps; praw = None;
ret = Some (texpr ret); fwhere = []; fbody = body_of body;
nloc = n.loc })
nloc = n.loc; fprivate = Ast.Exported })
| _ -> fail f "%s" usage)
| List ({ v = Sym "defmethod"; _ } :: args) ->
@ -1503,7 +1509,7 @@ let rec decl (f : Form.t) : Ast.decl =
mfn = { Ast.name = gen ^ "@" ^ Ast.dispatch_text k;
params = dyn_params "defmethod" ps; praw = None;
ret = None; fwhere = []; fbody = body_of body;
nloc = n.loc } })
nloc = n.loc; fprivate = Ast.Exported } })
| _ ->
fail f
"defmethod is (defmethod generic dispatch [param ...] body ...). \
@ -1534,11 +1540,12 @@ let rec decl (f : Form.t) : Ast.decl =
(match List.rev rest with
| [ n; { v = Form.Vec ps; _ } ] ->
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 = Ast.Exported } csym)
| [ n; { v = Form.Vec ps; _ }; r ] ->
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
ret = Some (texpr r); fwhere = []; fbody = [];
nloc = n.loc } csym)
nloc = n.loc; fprivate = Ast.Exported } csym)
| _ -> fail f "%s" usage)
| _ -> fail f "%s" usage)
@ -1770,7 +1777,7 @@ let rec decl (f : Form.t) : Ast.decl =
leave off. *)
praw = None;
ret = Some form_t; fwhere = []; fbody = macro_body sg body;
nloc = n.loc })
nloc = n.loc; fprivate = Ast.Exported })
| _ ->
fail f "defmacro is (defmacro name [param ...] body ...)")

View File

@ -644,7 +644,8 @@ let eval ?(origin = "<eval>") ?pause t src : change =
p.Load.owns
@ List.filter_map Ast.declared_name ds
in
List.map (Load.qualify_decl owns p.Load.alias) ds
let ds = List.map (Load.qualify_decl owns p.Load.alias) ds in
if Load.is_package_file p.Load.dir then Load.file_scoped ds else ds
in
let loc =
match incoming with d :: _ -> d.Ast.dloc | [] -> Loc.unknown

View File

@ -0,0 +1,7 @@
;;;; A package that is one file, imported by naming the file. Its defn- is
;;;; private to this file: the files beside it in the directory are not part
;;;; of it.
(defn- inner [a i32] i32 (* a 3))
(defn outer [a i32] i32 (inner a))

View File

@ -0,0 +1,8 @@
;;;; Sits beside lib.flan and imports it by name. lib's defn- is private to
;;;; lib.flan, so the shared directory does not make it callable here.
(import lib "lib.flan")
(defn main [] i32
(print (lib/inner 5))
0)

View File

@ -0,0 +1,7 @@
;;;; Imports lib.flan by name and calls its public function.
(import lib "lib.flan")
(defn main [] i32
(print (lib/outer 5)) (println "")
0)

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,10 @@
;;;; A call to a package's private function written by a macro of this file,
;;;; not of the package.
(import secret "pkgs/secret")
(defmacro sneak [& args] `(do (secret/mix 1 2)))
(defn main [] i32
(print (sneak))
0)

View File

@ -0,0 +1,9 @@
;;;; A call to a package's private function written here and handed to the
;;;; package's own macro. The macro passes it through; the call is still this
;;;; file's.
(import secret "pkgs/secret")
(defn main [] i32
(print (secret/pass (secret/mix 1 2)))
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,13 @@
;;;; A package's private function, used from inside the package: from the
;;;; file that declares it, from the package's other file, as a value, and
;;;; through code the package's own macros write here.
(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 "")
(print (secret/mixed 6 7)) (println "")
(print (secret/mixed-in-do 8 9)) (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,18 @@
;;;; 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))
;; Code these macros write is the package's own, wherever it is expanded: the
;; call to mix they answer with is accepted in the importer's file, on its own
;; and nested inside a do.
(defmacro mixed [& args] `(mix ~(at args 0) ~(at args 1)))
(defmacro mixed-in-do [& args] `(do (mix ~(at args 0) ~(at args 1))))
;; What the importer writes and this macro only passes through is still the
;; importer's.
(defmacro pass [& args] (at args 0))

View File

@ -2830,6 +2830,16 @@ let () =
shape/Box and not area/shape/Box. *)
outputs "a diamond, with a type crossing it" "programs/pkg-diamond.flan"
"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\n67\n89\n";
(* A single-file package's defn- is callable from that file, and the file
beside it that imports it reaches the public function. The refusal for
the same sibling calling the defn- directly is with the others. *)
outputs "a single-file package calls its own defn-"
"programs/loose/use.flan" "15\n";
(* 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 other reading: 7 is the program's own one-argument (get p), which
@ -3105,6 +3115,33 @@ let () =
if sand_checks then
refuses "a package's main is not visible" "programs/pkg-hidden-main.flan"
"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 "and names the fix"
"programs/pkg-private-call.flan" "Declaring it with defn instead";
(* The package's own macro may write a call to its defn- (that half is in
pkg-private.flan); what the importer writes is the importer's, whether
the package's macro passes it through or the importer's macro wrote it. *)
refuses "a defn- call the importer wrote, passed through the package's macro"
"programs/pkg-private-passed.flan" "secret/mix is private to its package";
refuses "a defn- call written by the importer's own macro"
"programs/pkg-private-own-macro.flan"
"secret/mix is private to its package";
(* A single file named outright is the whole module, so its defn- is not
visible to the file beside it in the directory. *)
refuses "a single-file package's defn-, from the file beside it"
"programs/loose/peek.flan" "lib/inner is private to its package";
refuses "one directory under two aliases" "programs/pkg-two-aliases.flan"
"one directory takes one alias";
(* A ring is refused and the ring is named. The needle is the chain, not

View File

@ -590,6 +590,46 @@ let () =
(fun d -> try Unix.rmdir d with Unix.Unix_error _ -> ())
[ 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);
(* And a single-file package: redefined from its own file, it stays private
to that file, so the file beside it that imports it is still refused. *)
let tl, _ = Session.create ~file:"programs/loose/use.flan" () in
(match Session.eval ~origin:"programs/loose/lib.flan" tl
"(defn- inner [a i32] i32 (* a 4))" with
| _ -> ()
| exception Loc.Error { Loc.dmsg = m; _ } ->
fail "redefining a single-file package's defn-: %s" m);
(match Session.eval ~origin:"programs/loose/use.flan" tl
"(defn peek [] i32 (lib/inner 1))" with
| _ -> fail "a redefined single-file defn- became callable beside its file"
| exception Loc.Error { Loc.dmsg = m; _ } ->
if not (has m "lib/inner is private to its package") then
fail "a call to a single-file defn- was refused for another reason: %s" m);
(* ── C-x C-e expands, which it never used to ────────────────────────
[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

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
;; 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 \,)))
;; 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
;; 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)
(= b \() (= b \)) (= b \[) (= b \]) (= b \{) (= b \})
(= b \") (= b \;)))
(defn alpha? [b u8] bool
(defn- alpha? [b u8] bool
(or (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
;; 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.
(defn sym-start? [b u8] bool
(defn- sym-start? [b u8] bool
(or (alpha? 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.
;; 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))))
;; 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
;; the input rather than a slice of nothing, so `text` has one meaning for all
;; 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))
(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}))
(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)}))
;; 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
;; 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))
(let [b (at (.src c) (.pos c))]
(cond
@ -313,7 +313,7 @@
(set (.depth c) (+ (.depth c) 1))
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)
(!= (at (.open c) (- (.depth c) 1)) closer))
(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.
;; `-` 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)]
(when (>= i (length s))
(return false))
@ -335,7 +335,7 @@
(< (+ i 1) (length s))
(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)]
(set (.pos c) hi)
(let [text (slice (.src c) lo hi)]
@ -362,7 +362,7 @@
;; 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
;; 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)
s (.src c)]
(while (< i (length s))
@ -536,7 +536,7 @@
(Some (bytes=? (.text t) (bytes-view "true")))
None))
(defn text=? [t Token s string] bool
(defn- text=? [t Token s string] bool
(bytes=? (.text t) (bytes-view s)))
;; 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 ───────────────
(defn joined [a string b string] string
(defn- joined [a string b string] string
(let [v (vec-new u8)]
(append (addr v) (bytes-view a))
(append (addr v) (bytes-view b))
(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)))
;; 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`
;; below holds a line and a column at the same time, which read as the same
;; number until this copied.
(defn i64->string [n i64] string
(defn- i64->string [n i64] string
(let [v (vec-new u8)]
(append-i64 (addr v) n)
(string (slice v))))
@ -80,7 +80,7 @@
;; 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
;; 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)
col (i64 1)
i (i32 0)]
@ -110,13 +110,13 @@
reader Form
bad string])
(defn derived-bad [msg string] Derived
(defn- derived-bad [msg string] Derived
(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 ""}))
(defn bad? [d Derived] bool
(defn- bad? [d Derived] bool
(> (length (bytes-view (.bad d))) 0))
;; ── The scalars a generated reader calls ────────────────────────────
@ -212,7 +212,7 @@
;; 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
;; 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)]
(when (not (ok? c))
(return (derived-bad
@ -242,7 +242,7 @@
;; decides; every one after it is compared against that decision and both
;; positions are named when they disagree, because "heterogeneous" without
;; 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 \])
(return (derived-bad
(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
;; the more readable expansion: the reader says `(cells-new a)` where it would
;; 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))))
;; A set becomes `(Map T bool)`, so its elements are map keys. `derive-key` is
;; 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 \})
(return (derived-bad
(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
;; rather than at the `(Map ...)` the caller would build out of it, because a
;; 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 \[)
(return (derive-array 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
;; out of the type comparison the caller already makes, since the length is in
;; 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)]
(when (at-byte? c \])
(return (derived-bad
@ -379,7 +379,7 @@
(expect c tok-vec-close)
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
(joined3 (joined3 "the " what " at ")
(where src at-pos)
@ -400,7 +400,7 @@
;; 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.
;; 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 \})
(return (derived-bad
(joined3 "the empty map at " (where src at-pos)
@ -485,7 +485,7 @@
;; ── 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))))
;; 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) "]")
_ "?"))
(defn render-items [xs [Form]] string
(defn- render-items [xs [Form]] string
(let [out ""]
(dotimes [i (length xs)]
(set out (if (= i 0)
@ -510,7 +510,7 @@
;; 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
;; 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))]
(or (bytes=? s (bytes-view "i64"))
(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 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)
d (derive (addr cur) name src)]
(if (bad? d)
@ -580,5 +580,5 @@
;; 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
;; defined twice.
(defn refuse [msg string] Form
(defn- refuse [msg string] Form
`(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
;; on whitespace and brackets would read `1//x` as one atom and then report a
;; bad number instead of a comment.
(defn delim? [b u8] bool
(defn- delim? [b u8] bool
(or (space? 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))
(and (>= b \A) (<= b \Z))))
(defn hex? [b u8] bool
(defn- hex? [b u8] bool
(or (digit? b)
(and (>= b \a) (<= b \f))
(and (>= b \A) (<= b \F))))
(defn hex-val [b u8] i32
(defn- hex-val [b u8] i32
(cond
(digit? b) (- (i32 b) (i32 \0))
(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.
;; 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))))
;; 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
;; 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))
(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}))
(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)}))
;; 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.
(defn skip-trivia [c (Ptr Cursor)] ()
(defn- skip-trivia [c (Ptr Cursor)] ()
(while (and (not (at-end? c)) (space? (at (.src c) (.pos c))))
(set (.pos c) (+ (.pos c) 1))))
@ -344,7 +344,7 @@
(set (.depth c) (+ (.depth c) 1))
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)
(!= (at (.open c) (- (.depth c) 1)) closer))
(fail c err-unbalanced p)
@ -358,7 +358,7 @@
;; 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
;; 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 \.)))
;; 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
;; 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.
(defn read-number [c (Ptr Cursor) lo i32] Token
(defn- read-number [c (Ptr Cursor) lo i32] Token
(let [s (.src c)
i lo
float? false]
@ -447,7 +447,7 @@
;; 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
;; it is a function.
(defn hex4 [c (Ptr Cursor) i i32] i32
(defn- hex4 [c (Ptr Cursor) i i32] i32
(let [s (.src c)]
(when (> (+ i 4) (length s))
(return -1))
@ -459,8 +459,8 @@
(set v (+ (* v 16) (hex-val b)))))
v)))
(defn high-surrogate? [r i32] bool (and (>= r 0xd800) (<= r 0xdbff)))
(defn low-surrogate? [r i32] bool (and (>= r 0xdc00) (<= r 0xdfff)))
(defn- high-surrogate? [r i32] bool (and (>= r 0xd800) (<= r 0xdbff)))
(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 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,
;; and not only escape syntax, so that string-of's encode-rune can never be
;; 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)
i (+ lo 1)]
(while (< i (length s))

View File

@ -42,20 +42,20 @@
;; ── Small string work ───────────────────────────────────────────────
(defn joined [a string b string] string
(defn- joined [a string b string] string
(let [v (vec-new u8)]
(append (addr v) (bytes-view a))
(append (addr v) (bytes-view b))
(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)))
;; 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
;; 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.
(defn i64->string [n i64] string
(defn- i64->string [n i64] string
(let [v (vec-new u8)]
(append-i64 (addr v) n)
(string (slice v))))
@ -63,7 +63,7 @@
;; 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
;; 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)
col (i64 1)
i (i32 0)]
@ -87,16 +87,16 @@
reader Form
bad string])
(defn derived-bad [msg string] Derived
(defn- derived-bad [msg string] Derived
(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 ""}))
(defn bad? [d Derived] bool
(defn- bad? [d Derived] bool
(> (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))))
;; ── The scalars a generated reader calls ────────────────────────────
@ -173,7 +173,7 @@
;; ── 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)]
(when (not (ok? c))
(return (derived-bad
@ -202,7 +202,7 @@
;; 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
;; 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 \])
(return (derived-bad
(joined3 "the empty array at " (where src at-pos)
@ -248,7 +248,7 @@
;; ── 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 \})
(return (derived-bad
(joined3 "the empty object at " (where src at-pos)
@ -349,7 +349,7 @@
(defn key=? [t Token s string] bool
(bytes=? (.text t) (bytes-view s)))
(defn has-escape? [s [u8]] bool
(defn- has-escape? [s [u8]] bool
(dotimes [i (length s)]
(when (= (at s i) \\)
(return true)))
@ -358,7 +358,7 @@
;; 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
;; 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)
(return false))
(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
;; purpose: this runs inside the compiler, where an expansion is bounded by the
;; size of the program being compiled.
(defn copy-of [s [u8]] string
(defn- copy-of [s [u8]] string
(let [b (vec-new u8)]
(append (addr b) s)
(string (slice b))))
;; ── 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))))
;; 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) "]")
_ "?"))
(defn render-items [xs [Form]] string
(defn- render-items [xs [Form]] string
(let [out ""]
(dotimes [i (length xs)]
(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 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)
d (derive (addr cur) name src)]
(if (bad? d)
@ -460,5 +460,5 @@
;; 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
;; 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}))))

View File

@ -1526,8 +1526,14 @@ existed.</p>
refused at the line that wrote it.</li>
</ul>
<p>Visibility is that one rule and no more: there is no package-private marker for
anything other than <code>main</code> yet.</p>
<p><strong>A function declared with <code>defn-</code> is private to its package.</strong>
It is a <code>defn</code> in every other respect, and every file in the package's
directory can call it. For a package imported by naming a single file, the package is
that file alone, and the other files in its directory cannot 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 where it may be used. Code written by one of
the package's own macros counts as the package's, wherever the macro is called. Only
functions have a private form.</p>
<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
@ -2382,7 +2388,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
<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
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
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>
@ -2485,7 +2491,7 @@ disagree with the first.</p>
<script>
// A small hand-written highlighter for the Flan blocks. One pass, no library.
(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 " +
"until dotimes match set return some try defer signal error handler-bind " +
"restart-case invoke-restart fn quote defmacro gensym " +