flan/lib/prelude.ml

77 lines
4.4 KiB
OCaml

(** The milestone-2 prelude, written in Flan.
Printing is deliberately *not* a primitive (plan.org, Milestone-2
primitives): [write-stdout] is the one output primitive and everything
above it is Flan. That is what keeps a second backend cheap — a primitive
is the only thing implemented twice.
Its source is [lib/prelude.fln], in the indented syntax, embedded as a
string at build time (the rule in lib/dune) and read with [Indent_reader];
every program gets it prepended by [Check.build_program] whether it asked
for one or not. That is a real limit and it is stated as one: the prelude
cannot be extended or replaced without rebuilding the compiler. There *is* a package
loader now — [lib/load.ml], and [vendor/raylib] is a package that uses it —
so the obstacle is no longer the mechanism. What is missing is the decision
about what a [core:] package would mean for a program that imports nothing,
and until that is made this docstring does not promise one. (It used to say
the prelude became a [core:] package "at milestone 3". Milestone 3 came and
went; the package did not.) The acceptance programs may call anything
defined here.
No printing function is here at all any more. [print] and [println] are
the whole printing surface, and neither is a function: both are
compiler-provided and structural, a walk over each argument's concrete
type at the call site (check.ml, and the walk itself in render.ml). Both
are variadic with Clojure's spacing -- arguments in order, one space
between each pair, [println] ending the line. That is plan.org's
Milestone 5 item, and it needed none of the rest of milestone 5 -- there
is nothing to dispatch on at run time and no user-supplied printer to
choose between, so no type variables are involved. The earlier note here
said a single [println] had to wait for generics; it did not.
The per-type family that used to live here -- [print-str], [print-i64],
[print-f64], [print-bytes], [print-line], [newline] -- is gone, and [print]
is strictly the better call for every one of them. [print] is the same walk
as [println] without the trailing newline, so it covers the no-newline case
that was the family's remaining excuse (see [show] in
test/programs/slices.flan). And [(print-i64 x)] forced an explicit
[(i64 x)] at every site, where [(print x)] takes the value as it is. That
is not only shorter: the cast through the signed printer turned a [u64]
above 2^63 into a negative number, where [print] routes it through
[flan_u64_to_bytes] and prints what it actually holds. Implicit widening
would have removed the cast at a [u8] or an [i32] site
on its own, but not at that one -- a [u64] widens into nothing at all, and
the printer it was being forced through was the wrong one. *)
let source = Prelude_src.source
let file = "<prelude>"
(* The bootstrap hook, and the whole of why a prelude function may now call a
prelude macro.
[Check.program] prepends this file to every program, and a macro module is
built by running [Check.program] over the prelude — so a prelude function
that calls a macro cannot be compiled *into the very module that would
expand it*. That is a cycle, not an ordering mistake, and it is broken by
making the prelude smaller for exactly the one build that cannot afford it:
while a macro module is being built, [Macro] installs a reduction here that
drops every [defn] depending, directly or transitively, on a macro. A
*macro* that lands in that set is refused by name — see [Macro.reduce].
A ref rather than a parameter because the reader is [Check.program], which
cannot be told, and because [Macro] sits above it and cannot be depended on
from here. *)
let bootstrap : (Form.t list -> Form.t list) ref = ref (fun fs -> fs)
(* The prelude's forms as written, before [bootstrap]. The file keeps the
name [<prelude>] rather than [prelude.fln]: nothing can open it, and a
diagnostic about a prelude form spells its code as the paren syntax does,
which is what [Source.indented_at] gives for a name that is not [.fln].
Read once per process: the indented reader over the whole prelude is
milliseconds, several readers want it per build, and a form is immutable. *)
let parsed = lazy (Indent_reader.read_all ~file source)
let read () = Lazy.force parsed
let forms () = !bootstrap (read ())