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}