8.3 KiB
Handoff — the pointer arm agrees already promised
lib/cimport.ml's agrees decides whether a hand-written declare-c and the C header
say the same thing. Its comment had always listed three differences that are "expected and
are not reported"; the code implemented two of them. The missing one — a (Ptr T) where
the header says T * — is what ../PORTING.md §A.1 and §A.2 ran into, and it is why
GetCodepointPrevious could not be bound honestly and why UpdateTexture had to be handed
(addr (.r (at (slice-from-ptr pixels n) 0))).
The arm is built. This file is the record of what it accepts, what it still refuses, and the one thing that turned out not to be its fault.
What the arm is
ptr_agrees and agrees_c beside agrees in lib/cimport.ml, consulted only from the
two declare-c sites in diff_bound. It takes the C spelling and not only the
rendered Flan type, because by the time a parameter has been rendered the header's word is
gone: param_ty turns const char * into string and value_ty turns void * into
(Ptr u8), and neither of those is what the header said. A hand-written line that
disagrees with a rendering is not disagreeing with the header, and that distinction is the
whole of the arm.
agrees itself is untouched. check_structs uses it for field widths, and a struct
field is about layout — every pointer in a struct is one word whatever it points at — so
widening it there would buy nothing and would cost the discipline the field check exists
to keep.
What it accepts
(Ptr T)against a CT *for the sameT.(Ptr u8)againstchar *andconst char *. Inside a pointer,charandunsigned charare two spellings of one byte and the package uses(Ptr u8)for both. Thei8/u8tolerance is the pointee's and not the world's; as a scalar or a field the two are still a real disagreement, and there is a test for that.(Ptr T)for anyTagainstvoid *andconst void *. This is the judgement call and the reason is sharper than "it is opaque":value_tyinvents the(Ptr u8)it renders avoid *as, so the check without this arm was enforcing cimport's own guess as if the header had said it. What is not given up is that it is a pointer at all — the Flan side must still be a(Ptr _), so ani32or astringdeclared against avoid *is still a finding. raylib spells thirty-odd parametersvoid *and none of them is a scalar.(Ptr Key)against a header'sint *, through the existing enum arm. The same four bytes through a pointer as beside one.
What it refuses
(Ptr A)againstB *for different namedAandB. Two structs of the same size are still two different structs.- Anything that is not a pointer on the Flan side against a C pointer,
void *included. - Everything
agreesalready refused, unchanged.
What const does here: nothing, and the reason is invisible
Worth writing down because reading the code does not show it. A non-const char *
parameter makes param_ty refuse, and diff_bound's | None -> None means a
parameter the importer cannot render at all is skipped rather than compared. So a
hand-written (Ptr u8) over a char * has always passed — unexamined, not approved. Only
const char * ever reaches the comparison, and that is the case the arm exists for. The
rewritten comment at the head of diff_bound now says so.
The comment at lib/cimport.ml:1291
Rewritten from a promise into a description. The count it carried — "176 declare-c
lines", twenty short on the day it was read — is gone rather than corrected, and both
comments now name the grep that answers the question instead of recording an answer that
goes stale. vendor/raylib/headers carried the same rot, "425 declarations … 172
hand-written", and got the same treatment. Neither file states a number any more.
Then it was used
§A.1 came out entirely, and it is the arm's own case.
vendor/raylib/raylib.flan has get-codepoint-previous-raw — (Ptr u8) over a
const char *, which is precisely what the arm was missing — and a get-codepoint-previous
wrapper that takes the bytes and an offset and builds the interior pointer itself.
generated.flan no longer carries the string-faced version: a binding that is wrong for
the only direction it reads in is worse than no binding, and the hand-written declaration
suppresses the generated one by C symbol. examples/text-codepoints-loading.flan's
step-back is one call to it; the continuation-byte walk and continuation? are deleted.
The answer is 12356 and 3, verified by running it.
test/programs/raylib-codepoints.flan would have caught the old answer, verified by
perturbation rather than by argument: make step-back set size to 0 — which is exactly
what the broken string call produced — and both rows go red, because the backward walk
never reaches offset 0 and runs off the end of its array.
§A.2's call site came out; the cast moved rather than vanished. The header check does
accept the (Ptr Color) binding now. What the header check was masking is that
lib/shim.ml:651 refuses a second declare-c for a C symbol the package already binds,
and that rule is right: a shim emits one C prototype per declaration, and two prototypes
for UpdateTexture that disagree about a parameter type is a C file that does not compile.
Its message says what to write instead — "another Flan name for it is a defn" — so
raylib.flan has update-texture-colors, a one-line defn over the generated
update-texture:
(defn update-texture-colors [texture Texture2D pixels (Ptr Color)] ()
(update-texture texture (addr (.r pixels))))
The (Ptr u8) face has to stay the declared one, because (.data im-copy) is already a
(Ptr u8) over the same bytes and (Ptr u8) → (Ptr Color) is the direction the language
cannot write. (addr (.r pixels)) was checked against the bytes rather than reasoned
about: a gen-image-color 2 1 red through load-image-colors, read back through the u8
pointer, gives 230 41 55 255. So the honest statement is that §A.2 is half closed — the
example reads (rl/update-texture-colors texture pixels), the cast exists once with a name
and a comment on it instead of once per call site, and the general gap the section is
actually about is a pointer reinterpretation, which is not a checker arm.
Verification
dune build --root .anddune test --root .clean; the dev suite is 232 checks, 0 failures, unchanged — the new checks are intest_flan.ml, which reports pass/fail rather than a count.- New checks in
test/test_flan.mlover three new functions intest/headers/sample.h(blit,scratch,pair_len_p): six declarations that must agree, five that must still be reported, each asserting on the message and not on a count. All of them were confirmed live by breakingptr_agreesin three different directions and watching the right subset go red — the negatives matter most, and they are why the arm can be widened without the check becoming decoration. - The raylib acceptance cases ran rather than skipped (
ldconfigfindslibraylib.so.550); the only "skipping" line in the whole run is the web one, which wants a wasm build of raylib that is not in this tree. flan generate-c vendor/raylibcompletes, which is itself the check: it compares everydefstruct, every hand-writtendeclare-cand every mapped constant againstraylib-5.5.hand refuses to write when they disagree.bash web/examples/check.shgreen.spike/x86/survey.shon the finished tree: 103 MATCH, 0 DIFFER, 0 REFUSED (38 skip — 28 that do not compile on purpose, 8 with no main, 2 that run forever). Expected rather than surprising: nothing here is below the IR, and the one surveyed program that changed istest/programs/raylib-codepoints.flan. Run it detached —setsid timeout 2400 spike/x86/survey.sh > log 2>&1 </dev/null— because a foreground run takes the signal sent to its process group and returns a spurious 143.
Still open
- A pointer reinterpretation in the language.
(addr (.r p))works when the first field is the type you want and there is no general form of it. This is §A.2's real subject. - Nothing else. The three differences the comment lists are now all three implemented.