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}