rune/compile/
options.rs

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