rune/compile/
options.rs

1use core::fmt;
2
3use ::rust_alloc::boxed::Box;
4
5/// Error raised when trying to parse an invalid option.
6#[derive(Debug, Clone)]
7pub struct ParseOptionError {
8    env: Option<&'static str>,
9    option: Box<str>,
10}
11
12impl fmt::Display for ParseOptionError {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        write!(f, "Unsupported compile option `{}`", self.option)?;
15
16        if let Some(env) = self.env {
17            write!(f, " (environment `{env}`)")?;
18        }
19
20        Ok(())
21    }
22}
23
24impl core::error::Error for ParseOptionError {}
25
26/// Options specific to formatting.
27#[derive(Debug, Clone)]
28pub(crate) struct FmtOptions {
29    /// Attempt to format even when faced with syntax errors.
30    pub(crate) error_recovery: bool,
31    /// Force newline at end of document.
32    pub(crate) force_newline: bool,
33}
34
35impl FmtOptions {
36    /// The default format option.
37    pub(crate) const DEFAULT: Self = Self {
38        error_recovery: false,
39        force_newline: true,
40    };
41
42    /// Parse an option with the extra diagnostics metadata.
43    fn parse_option_with(
44        &mut self,
45        option: &str,
46        env: Option<&'static str>,
47    ) -> Result<(), ParseOptionError> {
48        let (head, tail) = if let Some((head, tail)) = option.trim().split_once('=') {
49            (head.trim(), Some(tail.trim()))
50        } else {
51            (option.trim(), None)
52        };
53
54        match head {
55            "error-recovery" => {
56                self.error_recovery = tail.map_or(true, |s| s == "true");
57            }
58            "force-newline" => {
59                self.force_newline = tail.map_or(true, |s| s == "true");
60            }
61            _ => {
62                return Err(ParseOptionError {
63                    env,
64                    option: option.into(),
65                });
66            }
67        }
68
69        Ok(())
70    }
71}
72
73impl Default for FmtOptions {
74    #[inline]
75    fn default() -> Self {
76        FmtOptions::DEFAULT
77    }
78}
79
80/// Documentation for a single compiler option.
81#[non_exhaustive]
82pub struct OptionMeta {
83    /// The key.
84    pub key: &'static str,
85    /// Whether the option is unstable or not.
86    pub unstable: bool,
87    /// The documentation for the option.
88    pub doc: &'static [&'static str],
89    /// The default value for the option.
90    pub default: &'static str,
91    /// Available options.
92    pub options: &'static str,
93}
94
95/// Options that can be provided to the compiler.
96///
97/// See [Build::with_options][crate::Build::with_options].
98#[derive(Debug, Clone)]
99pub struct Options {
100    /// Perform link-time checks.
101    pub(crate) link_checks: bool,
102    /// Memoize the instance function in a loop.
103    pub(crate) memoize_instance_fn: bool,
104    /// Include debug information when compiling.
105    pub(crate) debug_info: bool,
106    /// Support macros.
107    pub(crate) macros: bool,
108    /// Support bytecode caching.
109    pub(crate) bytecode: bool,
110    /// Build sources as function bodies.
111    ///
112    /// The function to run will be named 0, which can be constructed with
113    /// `Hash::EMPTY`.
114    pub(crate) function_body: bool,
115    /// When running tests, include std tests.
116    pub(crate) test_std: bool,
117    /// Enable lowering optimizations.
118    pub(crate) lowering: u8,
119    /// Print source tree.
120    pub(crate) print_tree: bool,
121    /// Use the v2 compiler.
122    pub(crate) v2: bool,
123    /// Maximum macro depth.
124    pub(crate) max_macro_depth: usize,
125    /// Rune format options.
126    pub(crate) fmt: FmtOptions,
127}
128
129impl Options {
130    /// The default options.
131    pub(crate) const DEFAULT: Options = Options {
132        link_checks: true,
133        memoize_instance_fn: true,
134        debug_info: true,
135        macros: true,
136        bytecode: false,
137        function_body: false,
138        test_std: false,
139        lowering: 0,
140        print_tree: false,
141        v2: false,
142        max_macro_depth: 64,
143        fmt: FmtOptions::DEFAULT,
144    };
145
146    /// Construct lossy rune options from the `RUNEFLAGS` environment variable.
147    pub fn from_default_env() -> Result<Self, ParseOptionError> {
148        #[allow(unused_mut)]
149        let mut options = Self::DEFAULT;
150
151        #[cfg(feature = "std")]
152        {
153            /// The environment variable where runeflags are loaded from.
154            static ENV: &str = "RUNEFLAGS";
155
156            if let Some(value) = std::env::var_os(ENV) {
157                let value = value.to_string_lossy();
158                options.parse_option_with(&value, Some(ENV))?;
159            }
160        }
161
162        Ok(options)
163    }
164
165    /// Get a list and documentation for all available compiler options.
166    pub fn available() -> &'static [OptionMeta] {
167        static BOOL: &str = "true, false";
168        static VALUES: &[OptionMeta] = &[
169            OptionMeta {
170                key: "link-checks",
171                unstable: false,
172                doc: &docstring! {
173                    /// Perform link-time checks to ensure that
174                    /// function hashes which are referenced during
175                    /// compilation exist.
176                },
177                default: "true",
178                options: BOOL,
179            },
180            OptionMeta {
181                key: "memoize-instance-fn",
182                unstable: false,
183                doc: &docstring! {
184                    /// Memoize the instance function in a loop.
185                },
186                default: "true",
187                options: BOOL,
188            },
189            OptionMeta {
190                key: "debug-info",
191                unstable: false,
192                doc: &docstring! {
193                    /// Include debug information when compiling.
194                    ///
195                    /// This provides better diagnostics, but also
196                    /// increases memory usage.
197                },
198                default: "true",
199                options: BOOL,
200            },
201            OptionMeta {
202                key: "macros",
203                unstable: false,
204                doc: &docstring! {
205                    /// Support macro expansion.
206                },
207                default: "true",
208                options: BOOL,
209            },
210            OptionMeta {
211                key: "bytecode",
212                unstable: true,
213                doc: &docstring! {
214                    /// Make use of bytecode, which might make
215                    /// compilation units smaller.
216                },
217                default: "false",
218                options: BOOL,
219            },
220            OptionMeta {
221                key: "function-body",
222                unstable: true,
223                doc: &docstring! {
224                    /// Causes sources to be treated as-if they were
225                    /// function bodies, rather than modules.
226                },
227                default: "false",
228                options: BOOL,
229            },
230            OptionMeta {
231                key: "test-std",
232                unstable: true,
233                doc: &docstring! {
234                    /// When running tests, includes tests found in the
235                    /// standard library.
236                },
237                default: "false",
238                options: BOOL,
239            },
240            OptionMeta {
241                key: "lowering",
242                unstable: true,
243                doc: &docstring! {
244                    /// Enable lowering optimizations.
245                    ///
246                    /// Supports a value of 0-3 with increasingly higher
247                    /// levels of optimizations applied.
248                    ///
249                    /// Enabling a higher level results in better code
250                    /// generation, but contributes to compilation times.
251                },
252                default: "0",
253                options: "0-3",
254            },
255            OptionMeta {
256                key: "print-tree",
257                unstable: false,
258                doc: &docstring! {
259                    /// Print the parsed source tree when formatting to
260                    /// standard output.
261                    ///
262                    /// Only avialable when the `std` feature is enabled.
263                },
264                default: "false",
265                options: BOOL,
266            },
267            OptionMeta {
268                key: "v2",
269                unstable: true,
270                doc: &docstring! {
271                    /// Use the v2 compiler.
272                },
273                default: "false",
274                options: BOOL,
275            },
276            OptionMeta {
277                key: "max-macro-depth",
278                unstable: true,
279                doc: &docstring! {
280                    /// Maximum supported macro depth.
281                },
282                default: "64",
283                options: "<number>",
284            },
285            OptionMeta {
286                key: "fmt.error-recovery",
287                unstable: true,
288                doc: &docstring! {
289                    /// Perform error recovery when formatting.
290                    ///
291                    /// This allows code to be formatted even if it
292                    /// contains invalid syntax.
293                },
294                default: "false",
295                options: BOOL,
296            },
297            OptionMeta {
298                key: "fmt.force-newline",
299                unstable: true,
300                doc: &docstring! {
301                    /// Force newline at end of document.
302                },
303                default: "true",
304                options: BOOL,
305            },
306        ];
307
308        VALUES
309    }
310
311    /// Parse a compiler option. This is the function which parses the
312    /// `<option>[=<value>]` syntax, which is used by among other things the
313    /// Rune CLI with the `-O <option>[=<value>]` option.
314    ///
315    /// It can be used to consistenly parse a collection of options by other
316    /// programs as well.
317    pub fn parse_option(&mut self, option: &str) -> Result<(), ParseOptionError> {
318        self.parse_option_with(option, None)
319    }
320
321    fn parse_option_with(
322        &mut self,
323        option: &str,
324        env: Option<&'static str>,
325    ) -> Result<(), ParseOptionError> {
326        for option in option.split(',') {
327            let option = option.trim();
328
329            let (head, tail) = if let Some((head, tail)) = option.trim().split_once('=') {
330                (head.trim(), Some(tail.trim()))
331            } else {
332                (option.trim(), None)
333            };
334
335            match head {
336                "memoize-instance-fn" => {
337                    self.memoize_instance_fn = tail.map_or(true, |s| s == "true");
338                }
339                "debug-info" => {
340                    self.debug_info = tail.map_or(true, |s| s == "true");
341                }
342                "link-checks" => {
343                    self.link_checks = tail.map_or(true, |s| s == "true");
344                }
345                "macros" => {
346                    self.macros = tail.map_or(true, |s| s == "true");
347                }
348                "bytecode" => {
349                    self.bytecode = tail.map_or(true, |s| s == "true");
350                }
351                "function-body" => {
352                    self.function_body = tail.map_or(true, |s| s == "true");
353                }
354                "test-std" => {
355                    self.test_std = tail.map_or(true, |s| s == "true");
356                }
357                "lowering" => {
358                    self.lowering = match tail {
359                        Some("0") | None => 0,
360                        Some("1") => 1,
361                        _ => {
362                            return Err(ParseOptionError {
363                                env,
364                                option: option.into(),
365                            })
366                        }
367                    };
368                }
369                "print-tree" if cfg!(feature = "std") => {
370                    self.print_tree = tail.map_or(true, |s| s == "true");
371                }
372                "v2" => {
373                    self.v2 = tail.map_or(true, |s| s == "true");
374                }
375                "max-macro-depth" => {
376                    let Some(Ok(number)) = tail.map(str::parse) else {
377                        return Err(ParseOptionError {
378                            env,
379                            option: option.into(),
380                        });
381                    };
382
383                    self.max_macro_depth = number;
384                }
385                other => {
386                    let Some((head, tail)) = other.split_once('.') else {
387                        return Err(ParseOptionError {
388                            env,
389                            option: option.into(),
390                        });
391                    };
392
393                    let head = head.trim();
394                    let tail = tail.trim();
395
396                    match head {
397                        "fmt" => {
398                            self.fmt.parse_option_with(tail, env)?;
399                        }
400                        _ => {
401                            return Err(ParseOptionError {
402                                env,
403                                option: option.into(),
404                            });
405                        }
406                    }
407                }
408            }
409        }
410
411        Ok(())
412    }
413
414    /// Enable the test configuration flag.
415    pub fn test(&mut self, _enabled: bool) {
416        // ignored
417    }
418
419    /// Set if debug info is enabled or not. Defaults to `true`.
420    pub fn debug_info(&mut self, enabled: bool) {
421        self.debug_info = enabled;
422    }
423
424    /// Set if link checks are enabled or not. Defaults to `true`. This will
425    /// cause compilation to fail if an instruction references a function which
426    /// does not exist.
427    pub fn link_checks(&mut self, enabled: bool) {
428        self.link_checks = enabled;
429    }
430
431    /// Set if macros are enabled or not. Defaults to `false`.
432    pub fn macros(&mut self, enabled: bool) {
433        self.macros = enabled;
434    }
435
436    /// Set if bytecode caching is enabled or not. Defaults to `false`.
437    pub fn bytecode(&mut self, enabled: bool) {
438        self.bytecode = enabled;
439    }
440
441    /// Memoize the instance function in a loop. Defaults to `false`.
442    pub fn memoize_instance_fn(&mut self, enabled: bool) {
443        self.memoize_instance_fn = enabled;
444    }
445
446    /// Whether to build sources as scripts where the source is executed like a
447    /// function body.
448    pub fn script(&mut self, enabled: bool) {
449        self.function_body = enabled;
450    }
451}
452
453impl Default for Options {
454    #[inline]
455    fn default() -> Self {
456        Options::DEFAULT
457    }
458}