rune/
build.rs

1use core::fmt;
2use core::marker::PhantomData;
3use core::mem::take;
4
5use crate::alloc::borrow::TryToOwned;
6use crate::alloc::{self, String, Vec};
7use crate::ast::{Span, Spanned};
8#[cfg(feature = "std")]
9use crate::compile::FileSourceLoader as DefaultSourceLoader;
10#[cfg(not(feature = "std"))]
11use crate::compile::NoopSourceLoader as DefaultSourceLoader;
12use crate::compile::{
13    self, CompileVisitor, Located, MetaError, Options, ParseOptionError, Pool, SourceLoader,
14};
15use crate::runtime::unit::{DefaultStorage, UnitEncoder};
16use crate::runtime::Unit;
17use crate::sync::Arc;
18use crate::{parse, Context, Diagnostics, Item, SourceId, Sources, Vm};
19
20/// Error raised when we failed to load sources.
21///
22/// Look at the passed in [Diagnostics] instance for details.
23#[derive(Default, Debug)]
24#[non_exhaustive]
25pub struct BuildError {
26    kind: BuildErrorKind,
27}
28
29impl From<ParseOptionError> for BuildError {
30    #[inline]
31    fn from(error: ParseOptionError) -> Self {
32        Self {
33            kind: BuildErrorKind::ParseOptionError(error),
34        }
35    }
36}
37
38impl From<alloc::Error> for BuildError {
39    #[inline]
40    fn from(error: alloc::Error) -> Self {
41        Self {
42            kind: BuildErrorKind::Alloc(error),
43        }
44    }
45}
46
47#[derive(Default, Debug)]
48enum BuildErrorKind {
49    #[default]
50    Default,
51    ParseOptionError(ParseOptionError),
52    Alloc(alloc::Error),
53}
54
55impl fmt::Display for BuildError {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match &self.kind {
58            BuildErrorKind::Default => write!(
59                f,
60                "Failed to build rune sources (see diagnostics for details)"
61            ),
62            BuildErrorKind::ParseOptionError(error) => error.fmt(f),
63            BuildErrorKind::Alloc(error) => error.fmt(f),
64        }
65    }
66}
67
68impl core::error::Error for BuildError {
69    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
70        match &self.kind {
71            BuildErrorKind::Alloc(error) => Some(error),
72            _ => None,
73        }
74    }
75}
76
77/// Entry point to building a collection [`Sources`] of Rune into a default
78/// executable [`Unit`].
79///
80/// This returns a [`Build`] instance using a default configuration for a build
81/// that can be customized.
82///
83/// By default, if any error is encountered during compilation the error type
84/// [`BuildError`] doesn't provide any diagnostics on what went wrong. To get
85/// rich diagnostics you should instead associated a [`Diagnostics`] type
86/// through [`Build::with_diagnostics`] and examine it before handling any
87/// [`Err`] produced.
88///
89/// Uses the [`Source::name`] when generating diagnostics to reference the file.
90///
91/// [`Source::name`]: crate::Source::name
92///
93/// # Examples
94///
95/// Note: these must be built with the `emit` feature enabled (default) to give
96/// access to `rune::termcolor`.
97///
98/// ```no_run
99/// use rune::termcolor::{ColorChoice, StandardStream};
100/// use rune::{Context, Source, Vm};
101/// use rune::sync::Arc;
102///
103/// let context = Context::with_default_modules()?;
104/// let runtime = Arc::try_new(context.runtime()?)?;
105///
106/// let mut sources = rune::Sources::new();
107///
108/// sources.insert(Source::memory(r#"
109/// pub fn main() {
110///     println!("Hello World");
111/// }
112/// "#)?)?;
113///
114/// let mut diagnostics = rune::Diagnostics::new();
115///
116/// let result = rune::prepare(&mut sources)
117///     .with_context(&context)
118///     .with_diagnostics(&mut diagnostics)
119///     .build();
120///
121/// if !diagnostics.is_empty() {
122///     let mut writer = StandardStream::stderr(ColorChoice::Always);
123///     diagnostics.emit(&mut writer, &sources)?;
124/// }
125///
126/// let unit = result?;
127/// let unit = Arc::try_new(unit)?;
128/// let vm = Vm::new(runtime, unit);
129/// # Ok::<_, rune::support::Error>(())
130/// ```
131pub fn prepare(sources: &mut Sources) -> Build<'_, DefaultStorage> {
132    prepare_with(sources)
133}
134
135/// Prepare with a custom unit storage.
136pub fn prepare_with<S>(sources: &mut Sources) -> Build<'_, S>
137where
138    S: UnitEncoder,
139{
140    Build {
141        sources,
142        context: None,
143        diagnostics: None,
144        options: None,
145        args: Vec::new(),
146        visitors: Vec::new(),
147        source_loader: None,
148        _unit_storage: PhantomData,
149    }
150}
151
152/// A builder for a [Unit].
153///
154/// See [`rune::prepare`] for more.
155///
156/// [`rune::prepare`]: prepare
157pub struct Build<'a, S> {
158    sources: &'a mut Sources,
159    context: Option<&'a Context>,
160    diagnostics: Option<&'a mut Diagnostics>,
161    options: Option<&'a Options>,
162    args: Vec<String>,
163    visitors: Vec<&'a mut dyn compile::CompileVisitor>,
164    source_loader: Option<&'a mut dyn SourceLoader>,
165    _unit_storage: PhantomData<S>,
166}
167
168/// Wraps a collection of CompileVisitor
169struct CompileVisitorGroup<'a> {
170    visitors: Vec<&'a mut dyn compile::CompileVisitor>,
171}
172
173impl compile::CompileVisitor for CompileVisitorGroup<'_> {
174    fn register_meta(&mut self, meta: compile::MetaRef<'_>) -> Result<(), MetaError> {
175        for v in self.visitors.iter_mut() {
176            v.register_meta(meta)?;
177        }
178
179        Ok(())
180    }
181
182    fn visit_meta(
183        &mut self,
184        location: &dyn Located,
185        meta: compile::MetaRef<'_>,
186    ) -> Result<(), MetaError> {
187        for v in self.visitors.iter_mut() {
188            v.visit_meta(location, meta)?;
189        }
190
191        Ok(())
192    }
193
194    fn visit_variable_use(
195        &mut self,
196        source_id: SourceId,
197        var_span: &dyn Spanned,
198        span: &dyn Spanned,
199    ) -> Result<(), MetaError> {
200        for v in self.visitors.iter_mut() {
201            v.visit_variable_use(source_id, var_span, span)?;
202        }
203
204        Ok(())
205    }
206
207    fn visit_mod(&mut self, location: &dyn Located) -> Result<(), MetaError> {
208        for v in self.visitors.iter_mut() {
209            v.visit_mod(location)?;
210        }
211
212        Ok(())
213    }
214
215    fn visit_doc_comment(
216        &mut self,
217        location: &dyn Located,
218        item: &Item,
219        hash: crate::Hash,
220        doc: &str,
221    ) -> Result<(), MetaError> {
222        for v in self.visitors.iter_mut() {
223            v.visit_doc_comment(location, item, hash, doc)?;
224        }
225
226        Ok(())
227    }
228
229    fn visit_field_doc_comment(
230        &mut self,
231        location: &dyn Located,
232        item: &Item,
233        hash: crate::Hash,
234        field: &str,
235        doc: &str,
236    ) -> Result<(), MetaError> {
237        for v in self.visitors.iter_mut() {
238            v.visit_field_doc_comment(location, item, hash, field, doc)?;
239        }
240
241        Ok(())
242    }
243}
244
245impl<'a, S> Build<'a, S> {
246    /// Modify the current [`Build`] to use the given [`Context`] while
247    /// building.
248    ///
249    /// If unspecified the empty context constructed with [`Context::new`] will
250    /// be used. Since this counts as building without a context,
251    /// [`Vm::without_runtime`] can be used when running the produced [`Unit`].
252    #[inline]
253    pub fn with_context(mut self, context: &'a Context) -> Self {
254        self.context = Some(context);
255        self
256    }
257
258    /// Modify the current [Build] to use the given [Diagnostics] collection.
259    #[inline]
260    pub fn with_diagnostics(mut self, diagnostics: &'a mut Diagnostics) -> Self {
261        self.diagnostics = Some(diagnostics);
262        self
263    }
264
265    /// Modify the current [Build] to use the given [Options].
266    #[inline]
267    pub fn with_options(mut self, options: &'a Options) -> Self {
268        self.options = Some(options);
269        self
270    }
271
272    /// Associate an implicit argument with the build.
273    ///
274    /// When the produced unit is executed as a script, this argument will be
275    /// part of the top-level function that is being defined.
276    ///
277    /// This requires [`Options::script`] to be set. See the [`scripts`
278    /// example].
279    ///
280    /// [`scripts` example]: https://github.com/rune-rs/rune/blob/main/examples/examples/scripts.rs
281    ///
282    /// # Examples
283    ///
284    /// ```
285    /// use rune::{Source, Sources, Options};
286    ///
287    /// let mut sources = Sources::new();
288    /// sources.insert(Source::memory("(a + b * 2) / c")?)?;
289    ///
290    /// let mut options = Options::from_default_env()?;
291    /// options.script(true);
292    ///
293    /// let result = rune::prepare(&mut sources)
294    ///     .with_args(["a", "b"])?
295    ///     .with_arg("c")?
296    ///     .with_options(&options)
297    ///     .build()?;
298    /// # Ok::<(), rune::support::Error>(())
299    /// ```
300    pub fn with_arg(mut self, arg: impl AsRef<str>) -> alloc::Result<Self> {
301        self.args.try_push(arg.as_ref().try_to_owned()?)?;
302        Ok(self)
303    }
304
305    /// Associate a collection of implicit arguments with the build.
306    ///
307    /// When the produced unit is executed as a script, this argument will be
308    /// part of the top-level function that is being defined.
309    ///
310    /// This requires [`Options::script`] to be set. See the [`scripts`
311    /// example].
312    ///
313    /// [`scripts` example]: https://github.com/rune-rs/rune/blob/main/examples/examples/scripts.rs
314    ///
315    /// # Examples
316    ///
317    /// ```
318    /// use rune::{Source, Sources, Options};
319    ///
320    /// let mut sources = Sources::new();
321    /// sources.insert(Source::memory("(a + b * 2) / c")?)?;
322    ///
323    /// let mut options = Options::from_default_env()?;
324    /// options.script(true);
325    ///
326    /// let result = rune::prepare(&mut sources)
327    ///     .with_args(["a", "b"])?
328    ///     .with_arg("c")?
329    ///     .with_options(&options)
330    ///     .build()?;
331    /// # Ok::<(), rune::support::Error>(())
332    /// ```
333    pub fn with_args(mut self, args: impl IntoIterator<Item: AsRef<str>>) -> alloc::Result<Self> {
334        for arg in args {
335            self.args.try_push(arg.as_ref().try_to_owned()?)?;
336        }
337
338        Ok(self)
339    }
340
341    /// Modify the current [Build] to configure the given [CompileVisitor].
342    ///
343    /// A compile visitor allows for custom collecting of compile-time metadata.
344    /// Like if you want to collect every function that is discovered in the
345    /// project.
346    #[inline]
347    pub fn with_visitor(mut self, visitor: &'a mut dyn CompileVisitor) -> alloc::Result<Self> {
348        self.visitors.try_push(visitor)?;
349        Ok(self)
350    }
351
352    /// Modify the current [Build] to configure the given [SourceLoader].
353    ///
354    /// Source loaders are used to determine how sources are loaded externally
355    /// from the current file (as is neede when a module is imported).
356    #[inline]
357    pub fn with_source_loader(mut self, source_loader: &'a mut dyn SourceLoader) -> Self {
358        self.source_loader = Some(source_loader);
359        self
360    }
361
362    /// Build a [`Unit`] with the current configuration.
363    ///
364    /// See [`rune::prepare`] for more.
365    ///
366    /// [`rune::prepare`]: prepare
367    pub fn build(self) -> Result<Unit<S>, BuildError>
368    where
369        S: Default + UnitEncoder,
370    {
371        let (unit, ()) = self.build_inner(|_| ())?;
372        Ok(unit)
373    }
374
375    #[inline]
376    fn build_inner<O>(
377        mut self,
378        extra: impl FnOnce(&Context) -> O,
379    ) -> Result<(Unit<S>, O), BuildError>
380    where
381        S: Default + UnitEncoder,
382    {
383        let default_context;
384
385        let context = match self.context.take() {
386            Some(context) => context,
387            None => {
388                default_context = Context::new();
389                &default_context
390            }
391        };
392
393        let mut unit = compile::UnitBuilder::default();
394
395        let prelude = if context.has_default_modules() {
396            compile::Prelude::with_default_prelude()?
397        } else {
398            compile::Prelude::default()
399        };
400
401        let mut default_diagnostics;
402
403        let diagnostics = match self.diagnostics {
404            Some(diagnostics) => diagnostics,
405            None => {
406                default_diagnostics = Diagnostics::new();
407                &mut default_diagnostics
408            }
409        };
410
411        let default_options;
412
413        let options = match self.options {
414            Some(options) => options,
415            None => {
416                default_options = Options::from_default_env()?;
417                &default_options
418            }
419        };
420
421        let mut default_visitors;
422        let visitors = match self.visitors.is_empty() {
423            true => {
424                default_visitors = CompileVisitorGroup {
425                    visitors: Vec::new(),
426                };
427                &mut default_visitors
428            }
429            false => {
430                let v = take(&mut self.visitors);
431                default_visitors = CompileVisitorGroup { visitors: v };
432
433                &mut default_visitors
434            }
435        };
436
437        let mut default_source_loader;
438
439        let source_loader = match self.source_loader.take() {
440            Some(source_loader) => source_loader,
441            None => {
442                default_source_loader = DefaultSourceLoader::default();
443                &mut default_source_loader
444            }
445        };
446
447        if !self.args.is_empty() {
448            if let Some(options) = &mut self.options {
449                if !options.script {
450                    diagnostics.internal(
451                        SourceId::empty(),
452                        "cannot set script arguments without enabling script mode",
453                    )?;
454
455                    return Err(BuildError::default());
456                }
457            }
458
459            for arg in &self.args {
460                if !parse::is_ident(arg) {
461                    diagnostics.internal(
462                        SourceId::empty(),
463                        format!("script argument '{arg}' is not a valid identifier"),
464                    )?;
465                }
466            }
467        }
468
469        if diagnostics.has_error() {
470            return Err(BuildError::default());
471        }
472
473        let mut pool = Pool::new()?;
474        let mut unit_storage = S::default();
475
476        compile::compile(
477            &mut unit,
478            &prelude,
479            self.sources,
480            &mut pool,
481            context,
482            visitors,
483            diagnostics,
484            source_loader,
485            options,
486            &self.args,
487            &mut unit_storage,
488        )?;
489
490        if diagnostics.has_error() {
491            return Err(BuildError::default());
492        }
493
494        if options.link_checks {
495            unit.link(context, diagnostics)?;
496        }
497
498        if diagnostics.has_error() {
499            return Err(BuildError::default());
500        }
501
502        match unit.build(Span::empty(), unit_storage) {
503            Ok(unit) => Ok((unit, extra(context))),
504            Err(error) => {
505                diagnostics.error(SourceId::empty(), error)?;
506                Err(BuildError::default())
507            }
508        }
509    }
510}
511
512impl<'a> Build<'a, DefaultStorage> {
513    /// Convenience method to build a [`Vm`] directly from the current build
514    /// using default storage.
515    pub fn build_vm(self) -> Result<Vm, BuildError> {
516        let (unit, runtime) = self.build_inner(|context| context.runtime())?;
517        let runtime = runtime?;
518        let runtime = Arc::try_new(runtime)?;
519        let unit = Arc::try_new(unit)?;
520        Ok(Vm::new(runtime, unit))
521    }
522}