chandeliers_lus/
lib.rs

1//! Declarative macros of the Chandeliers suite.
2//!
3//! This crate defines the `proc_macro`s provided to compile Lustre code.
4//! For now only the `decl` macro is available to compile an entire program.
5
6#![feature(lint_reasons)]
7#![feature(proc_macro_diagnostic)]
8#![warn(
9    missing_docs,
10    unused_crate_dependencies,
11    unused_macro_rules,
12    variant_size_differences,
13    clippy::allow_attributes,
14    clippy::allow_attributes_without_reason,
15    clippy::expect_used,
16    clippy::indexing_slicing,
17    clippy::missing_docs_in_private_items,
18    clippy::multiple_inherent_impl,
19    clippy::panic,
20    clippy::pedantic,
21    clippy::str_to_string,
22    clippy::unreachable,
23    clippy::unwrap_used,
24    clippy::use_debug
25)]
26
27mod pipeline;
28
29use chandeliers_err::{self as err, EAccum, Error};
30use chandeliers_san::sp::Sp;
31use chandeliers_syn as syntax;
32
33// Dependencies for integration tests: silence the "unused dependencies" warning.
34#[cfg(test)]
35mod integration_deps {
36    use chandeliers_sem as _;
37    use chandeliers_std as _;
38    use rand as _;
39}
40
41/// Frontend of `chandeliers_lus`.
42///
43/// Usage:
44/// ```
45/// chandeliers_lus::decl! {
46///     #[trace[stderr]] // This means that every invocation of the node will print
47///                      // debug information.
48///     node count() returns (n : int);
49///     let n = 0 fby n + 1; tel;
50///
51///     #[main(15)] // This means that this is the main function of the program.
52///                 // It must have signature () -> () and will run 15 times.
53///     node system() returns ();
54///     var n : int;
55///     let
56///         n = count();
57///         assert n > 0; // This will of course fail on the first iteration.
58///                       // An assertion error will be printed and `system` will
59///                       // abort.
60///     tel
61/// }
62/// ```
63#[proc_macro]
64pub fn decl(i: proc_macro::TokenStream) -> proc_macro::TokenStream {
65    // Parsing the input.
66    use syn::parse_macro_input;
67    let prog = parse_macro_input!(i as Sp<syntax::Prog>);
68    let mut eaccum = EAccum::default();
69    // All logic is here.
70    let prog = prog_pipeline(&mut eaccum, prog);
71    // Finally collect errors.
72    let fatal = eaccum.is_fatal();
73    let (es, ws) = eaccum.fetch();
74    let Some(prog) = prog else {
75        err::consistency!(fatal, "No program generated, but no fatal error emitted");
76        for e in es {
77            emit(e, proc_macro::Level::Error);
78        }
79        return proc_macro2::TokenStream::new().into();
80    };
81    for w in ws {
82        // FIXME: this could be a Warning
83        emit(w, proc_macro::Level::Error);
84    }
85    prog
86}
87
88/// Apply all compiler passes.
89fn prog_pipeline(eaccum: &mut EAccum, prog: Sp<syntax::Prog>) -> Option<proc_macro::TokenStream> {
90    // Just let the trait impls from [pipeline] guide you...
91    pipeline::CompilerPass::new(eaccum, prog)?
92        .finish()
93        .apply(eaccum)?
94        .finish()
95        .apply(eaccum)?
96        .finish()
97        .apply(eaccum)?
98        .finish()
99        .apply(eaccum)?
100        .finish()
101        .codegen()
102}
103
104/// Generate a run of trybuild test cases.
105/// Usage: `compiling!(test_name with expected_outcome in path/to/test/folder)`.
106macro_rules! compiling {
107    ($fun:ident with $testing:ident in $($dir:ident / )*) => {
108        #[test]
109        fn $fun() {
110            let t = trybuild::TestCases::new();
111            t.$testing(concat!("tests/", $( concat!(stringify!($dir), "/") , )* "**/*.rs"));
112        }
113    };
114}
115
116// Our battery of tests
117// Compile-Fail tests. These should fail to compile.
118compiling!(fail_ui_causality with compile_fail in compile_fail/ui/causality/);
119compiling!(fail_ui_syn with compile_fail in compile_fail/ui/syn/);
120compiling!(fail_ui_tc with compile_fail in compile_fail/ui/tc/);
121compiling!(fail_ui_options with compile_fail in compile_fail/ui/options/);
122compiling!(fail_std with compile_fail in compile_fail/std/);
123compiling!(fail_ui_clk with compile_fail in compile_fail/ui/clk/);
124// Pass tests. These should compile and run.
125compiling!(pass_ui with pass in pass/ui/);
126compiling!(pass_syn with pass in pass/syn/);
127compiling!(pass_std with pass in pass/std/);
128compiling!(pass_fromslides with pass in pass/fromslides/);
129compiling!(pass_given with pass in pass/given/);
130compiling!(pass_options with pass in pass/options/);
131compiling!(pass_poly with pass in pass/poly/);
132compiling!(pass_registers with pass in pass/registers/);
133// Warn tests, which are technically implemented as fail tests
134// by having `#[deny(warnings)]`.
135compiling!(warn_dead_code with compile_fail in warn/dead_code/);
136compiling!(warn_options with compile_fail in warn/options/);
137// Not shown here: there is one extra test in `tests/` which
138// is a `fail` test.
139// `assert-false` is expected to compile successfully, but fail at runtime.
140// This is not supported by the test framework used here, so it's handled manually.
141
142/// Emit one error message from a sequence of spans and associated hint messages.
143fn emit(elements: Error, level: proc_macro::Level) -> proc_macro2::TokenStream {
144    let mut elements = elements.into_iter();
145    let Some((msg, span)) = elements.next() else {
146        err::abort!("This error message is empty")
147    };
148    let Some(span) = span else {
149        err::abort!("The very first error should always have an associated span")
150    };
151    let mut d = proc_macro::Diagnostic::spanned(
152        span.unwrap(/* proc_macro2::Span */).unwrap(/* proc_macro::Span */),
153        level,
154        msg,
155    );
156    for (msg, span) in elements {
157        if let Some(span) = span {
158            d = d.span_note(
159                span.unwrap(/* proc_macro2::Span */).unwrap(/* proc_macro::Span */),
160                msg,
161            );
162        } else {
163            d = d.note(msg);
164        }
165    }
166    d.emit();
167    proc_macro2::TokenStream::new()
168}