77 lines
4.4 KiB
OCaml
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 ())
|