diff --git a/test/test_session.ml b/test/test_session.ml index 8cc38fb..de8a717 100644 --- a/test/test_session.ml +++ b/test/test_session.ml @@ -723,6 +723,55 @@ let () = | exception Loc.Error { Loc.dmsg = m; _ } -> fail "a session that refused an expansion could not expand afterwards: %s" m); + (* ── A type provider, expanded ───────────────────────────────────── + Two claims, and they are the two halves of the live-tuning loop. + + The first is the path. A macro that reads a data file resolves it the way + (embed "...") does — against the directory of the source file the *form* + is written in — and a macro cannot know where that is, because a Form + carries no location. The compiler pokes the call site's directory in + before every expansion, and the call site here is an origin the editor + sent, not a file on a command line. "assets/edn/tuning.edn" is beside + programs/edn-provide.flan and nowhere near this process's directory, so an + expansion that answered anything at all read the right file. + + The second is that the answer is readable. `C-c C-m` over a provider is + the only way to see what it decided, and a provider whose output nobody + can look at is a plugin. So this asserts on text a person would recognise: + the struct with its fields and derived types, the nested struct named for + its path, and the reader's dispatch on a key. Asserted as substrings + rather than in full — the expansion is some hundreds of characters and a + golden copy of it would fail on every comment reflowed in the derivation. + + It also re-reads on every expansion, which is what makes editing the .edn + and hitting C-c C-m a loop: nothing is cached but the macro module, and + that holds the macro's code, not the data. *) + (let pt, _ = Session.create ~file:"programs/edn-provide.flan" () in + match + Session.macroexpand ~origin:"programs/edn-provide.flan" ~all:false pt + "(edn/defedn Tuning \"assets/edn/tuning.edn\")" + with + | x -> + let got = Form.to_source x.Session.xafter in + List.iter + (fun want -> + if not (has got want) then + fail "expanding a defedn: %S is not in\n%s" want got) + [ (* The struct, with a type per field derived from the value. *) + "(defstruct Tuning [name string hp i64 speed f64 boss? bool"; + (* The vector, and the constructor that states its type so that + (vec-new) has something to take it from. *) + "drops (Vec i64)"; + (* The nested map, named for the path that reaches it, and the one + nested inside that. *) + "(defstruct Tuning-hitbox-offset [x i64 y i64])"; + "hitbox Tuning-hitbox"; + (* And the reader, dispatching on a key onto a field. *) + "(edn/keyword=? k \"speed\")"; + "(set (.speed out) (edn/need-float c))" ] + | exception Loc.Error { Loc.dmsg = m; _ } -> + fail "expanding a defedn through a session: %s" m); + (* A form typed into a file that is *imported as a package* has to be qualified the way the import qualified it, or it splices as a brand-new unrelated name: the evaluation reports success and the running program