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