Skip to main content

tidepool_macro/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3
4mod expand;
5
6/// Embeds and evaluates a Haskell Core expression at runtime.
7///
8/// Accepts either a `.cbor` path (pre-compiled CBOR) or a `.hs` path (Haskell
9/// source compiled on-demand via `nix run .#tidepool-extract`).
10///
11/// For `.cbor` paths, the file is embedded directly via `include_bytes!`. For
12/// `.hs` paths, the macro invokes GHC through nix at compile time, producing
13/// CBOR in `target/tidepool-cbor/`, then embeds the result. The `.hs` source
14/// file is tracked by cargo for automatic recompilation.
15///
16/// # Haskell Source Support
17///
18/// When given a `.hs` path, the macro compiles it via `nix run .#tidepool-extract`.
19/// This requires `nix` to be available on `PATH`.
20///
21/// **Path resolution:** `.hs` paths resolve relative to `CARGO_MANIFEST_DIR`
22/// (the crate root). `.cbor` paths resolve relative to the calling file (standard
23/// `include_bytes!` behavior).
24///
25/// For modules with multiple top-level bindings, specify which binding to
26/// evaluate using the `::binding` syntax. If only one binding exists (excluding
27/// metadata), it is selected automatically.
28///
29/// # Panics
30///
31/// The generated code will panic during CBOR deserialization if the embedded data
32/// is malformed or incompatible with the expected format.
33///
34/// # Dependencies
35///
36/// The expansion of this macro expects the following crates to be available in the
37/// caller's scope:
38///
39/// - `tidepool_repr`
40/// - `tidepool_eval`
41///
42/// # Returns
43///
44/// Returns a `Result<tidepool_eval::Value, tidepool_eval::error::EvalError>`.
45///
46/// # Examples
47///
48/// ```ignore
49/// // Pre-compiled CBOR
50/// let val = haskell_eval!("../../haskell/test/Identity_cbor/identity.cbor").unwrap();
51///
52/// // Haskell source (single binding)
53/// let val = haskell_eval!("../../haskell/test/SingleBinding.hs").unwrap();
54///
55/// // Haskell source with binding selector
56/// let val = haskell_eval!("../../haskell/test/Identity.hs::identity").unwrap();
57/// ```
58#[proc_macro]
59pub fn haskell_eval(input: TokenStream) -> TokenStream {
60    expand::expand(input.into()).into()
61}
62
63/// Embeds a Haskell Core expression and its DataConTable without evaluating.
64///
65/// Unlike `haskell_eval!`, this macro does NOT evaluate the expression. It
66/// returns `(CoreExpr, DataConTable)` — suitable for effect-driven execution
67/// via `EffectMachine` where the caller controls evaluation.
68///
69/// Accepts the same path formats as `haskell_eval!`:
70/// - `.cbor` path (pre-compiled CBOR, requires a sibling `meta.cbor`)
71/// - `.hs` path (compiled on-demand via `nix run .#tidepool-extract`)
72/// - `.hs::binding` syntax for multi-binding modules
73///
74/// # Returns
75///
76/// Returns `(tidepool_repr::CoreExpr, tidepool_repr::DataConTable)`.
77///
78/// # Examples
79///
80/// ```ignore
81/// let (expr, table) = haskell_expr!("../Guess.hs::game");
82/// let mut heap = tidepool_eval::heap::VecHeap::new();
83/// let mut machine = tidepool_effect::EffectMachine::new(&table, &mut heap).unwrap();
84/// ```
85#[proc_macro]
86pub fn haskell_expr(input: TokenStream) -> TokenStream {
87    expand::expand_expr(input.into()).into()
88}
89
90/// Embeds inline Haskell source as a Core expression with its DataConTable.
91///
92/// Writes the Haskell source to a temporary file, compiles it via
93/// `nix run .#tidepool-extract`, and embeds the resulting CBOR.
94///
95/// Supports `include` paths for importing local Haskell modules.
96///
97/// # Returns
98///
99/// Returns `(tidepool_repr::CoreExpr, tidepool_repr::DataConTable)`.
100///
101/// # Examples
102///
103/// ```ignore
104/// let (expr, table) = haskell_inline! {
105///     target = "game",
106///     include = "haskell",
107///     r#"
108///         import Effects
109///
110///         game :: Eff '[Console, Rng] ()
111///         game = do
112///           target <- randInt 1 100
113///           emit "I'm thinking of a number between 1 and 100."
114///           guessLoop target
115///     "#
116/// };
117/// ```
118#[proc_macro]
119pub fn haskell_inline(input: TokenStream) -> TokenStream {
120    expand::expand_inline(input.into()).into()
121}