Skip to main content

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::{Globals, Unit};
17use crate::sync::Arc;
18use crate::{parse, Context, Diagnostics, Item, SourceId, Sources, Statics, 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        statics: None,
147        visitors: Vec::new(),
148        source_loader: None,
149        _unit_storage: PhantomData,
150    }
151}
152
153/// A builder for a [Unit].
154///
155/// See [`rune::prepare`] for more.
156///
157/// [`rune::prepare`]: prepare
158pub struct Build<'a, S> {
159    sources: &'a mut Sources,
160    context: Option<&'a Context>,
161    diagnostics: Option<&'a mut Diagnostics>,
162    options: Option<&'a Options>,
163    args: Vec<String>,
164    statics: Option<&'a Statics>,
165    visitors: Vec<&'a mut dyn compile::CompileVisitor>,
166    source_loader: Option<&'a mut dyn SourceLoader>,
167    _unit_storage: PhantomData<S>,
168}
169
170/// Wraps a collection of CompileVisitor
171struct CompileVisitorGroup<'a> {
172    visitors: Vec<&'a mut dyn compile::CompileVisitor>,
173}
174
175impl compile::CompileVisitor for CompileVisitorGroup<'_> {
176    fn register_meta(&mut self, meta: compile::MetaRef<'_>) -> Result<(), MetaError> {
177        for v in self.visitors.iter_mut() {
178            v.register_meta(meta)?;
179        }
180
181        Ok(())
182    }
183
184    fn visit_meta(
185        &mut self,
186        location: &dyn Located,
187        meta: compile::MetaRef<'_>,
188    ) -> Result<(), MetaError> {
189        for v in self.visitors.iter_mut() {
190            v.visit_meta(location, meta)?;
191        }
192
193        Ok(())
194    }
195
196    fn visit_variable_use(
197        &mut self,
198        source_id: SourceId,
199        var_span: &dyn Spanned,
200        span: &dyn Spanned,
201    ) -> Result<(), MetaError> {
202        for v in self.visitors.iter_mut() {
203            v.visit_variable_use(source_id, var_span, span)?;
204        }
205
206        Ok(())
207    }
208
209    fn visit_mod(&mut self, location: &dyn Located) -> Result<(), MetaError> {
210        for v in self.visitors.iter_mut() {
211            v.visit_mod(location)?;
212        }
213
214        Ok(())
215    }
216
217    fn visit_doc_comment(
218        &mut self,
219        location: &dyn Located,
220        item: &Item,
221        hash: crate::Hash,
222        doc: &str,
223    ) -> Result<(), MetaError> {
224        for v in self.visitors.iter_mut() {
225            v.visit_doc_comment(location, item, hash, doc)?;
226        }
227
228        Ok(())
229    }
230
231    fn visit_field_doc_comment(
232        &mut self,
233        location: &dyn Located,
234        item: &Item,
235        hash: crate::Hash,
236        field: &str,
237        doc: &str,
238    ) -> Result<(), MetaError> {
239        for v in self.visitors.iter_mut() {
240            v.visit_field_doc_comment(location, item, hash, field, doc)?;
241        }
242
243        Ok(())
244    }
245}
246
247impl<'a, S> Build<'a, S> {
248    /// Modify the current [`Build`] to use the given [`Context`] while
249    /// building.
250    ///
251    /// If unspecified the empty context constructed with [`Context::new`] will
252    /// be used. Since this counts as building without a context,
253    /// [`Vm::without_runtime`] can be used when running the produced [`Unit`].
254    #[inline]
255    pub fn with_context(mut self, context: &'a Context) -> Self {
256        self.context = Some(context);
257        self
258    }
259
260    /// Modify the current [Build] to use the given [Diagnostics] collection.
261    #[inline]
262    pub fn with_diagnostics(mut self, diagnostics: &'a mut Diagnostics) -> Self {
263        self.diagnostics = Some(diagnostics);
264        self
265    }
266
267    /// Modify the current [Build] to use the given [Options].
268    #[inline]
269    pub fn with_options(mut self, options: &'a Options) -> Self {
270        self.options = Some(options);
271        self
272    }
273
274    /// Associate an implicit argument with the build.
275    ///
276    /// When the produced unit is executed as a script, this argument will be
277    /// part of the top-level function that is being defined.
278    ///
279    /// This requires [`Options::script`] to be set. See the [`scripts`
280    /// example].
281    ///
282    /// [`scripts` example]: https://github.com/rune-rs/rune/blob/main/examples/examples/scripts.rs
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use rune::{Source, Sources, Options};
288    ///
289    /// let mut sources = Sources::new();
290    /// sources.insert(Source::memory("(a + b * 2) / c")?)?;
291    ///
292    /// let mut options = Options::from_default_env()?;
293    /// options.script(true);
294    ///
295    /// let result = rune::prepare(&mut sources)
296    ///     .with_args(["a", "b"])?
297    ///     .with_arg("c")?
298    ///     .with_options(&options)
299    ///     .build()?;
300    /// # Ok::<(), rune::support::Error>(())
301    /// ```
302    pub fn with_arg(mut self, arg: impl AsRef<str>) -> alloc::Result<Self> {
303        self.args.try_push(arg.as_ref().try_to_owned()?)?;
304        Ok(self)
305    }
306
307    /// Associate a collection of implicit arguments with the build.
308    ///
309    /// When the produced unit is executed as a script, this argument will be
310    /// part of the top-level function that is being defined.
311    ///
312    /// This requires [`Options::script`] to be set. See the [`scripts`
313    /// example].
314    ///
315    /// [`scripts` example]: https://github.com/rune-rs/rune/blob/main/examples/examples/scripts.rs
316    ///
317    /// # Examples
318    ///
319    /// ```
320    /// use rune::{Source, Sources, Options};
321    ///
322    /// let mut sources = Sources::new();
323    /// sources.insert(Source::memory("(a + b * 2) / c")?)?;
324    ///
325    /// let mut options = Options::from_default_env()?;
326    /// options.script(true);
327    ///
328    /// let result = rune::prepare(&mut sources)
329    ///     .with_args(["a", "b"])?
330    ///     .with_arg("c")?
331    ///     .with_options(&options)
332    ///     .build()?;
333    /// # Ok::<(), rune::support::Error>(())
334    /// ```
335    pub fn with_args(mut self, args: impl IntoIterator<Item: AsRef<str>>) -> alloc::Result<Self> {
336        for arg in args {
337            self.args.try_push(arg.as_ref().try_to_owned()?)?;
338        }
339
340        Ok(self)
341    }
342
343    /// Declare a collection of [`Statics`] with the build.
344    ///
345    /// Every static declared this way is added to the unit as if the source had
346    /// declared it, which lets a script use a static the host owns without
347    /// declaring it itself. Such a static has no initializer, so the caller has
348    /// to assign it through [`Globals::set`] before anything reads it.
349    ///
350    /// [`Globals::set`]: crate::runtime::Globals::set
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// use rune::{Source, Sources, Statics};
356    /// use rune::runtime::Globals;
357    /// use rune::sync::Arc;
358    ///
359    /// let mut sources = Sources::new();
360    /// sources.insert(Source::memory("pub fn main() { LIMIT }")?)?;
361    ///
362    /// let mut statics = Statics::new();
363    /// statics.insert(["LIMIT"])?;
364    ///
365    /// let unit = rune::prepare(&mut sources)
366    ///     .with_statics(&statics)
367    ///     .build()?;
368    ///
369    /// let globals = Globals::new(Arc::try_new(unit)?)?;
370    /// globals.set(["LIMIT"], rune::to_value(42i64)?)?;
371    /// # Ok::<(), rune::support::Error>(())
372    /// ```
373    #[inline]
374    pub fn with_statics(mut self, statics: &'a Statics) -> Self {
375        self.statics = Some(statics);
376        self
377    }
378
379    /// Modify the current [Build] to configure the given [CompileVisitor].
380    ///
381    /// A compile visitor allows for custom collecting of compile-time metadata.
382    /// Like if you want to collect every function that is discovered in the
383    /// project.
384    #[inline]
385    pub fn with_visitor(mut self, visitor: &'a mut dyn CompileVisitor) -> alloc::Result<Self> {
386        self.visitors.try_push(visitor)?;
387        Ok(self)
388    }
389
390    /// Modify the current [Build] to configure the given [SourceLoader].
391    ///
392    /// Source loaders are used to determine how sources are loaded externally
393    /// from the current file (as is neede when a module is imported).
394    #[inline]
395    pub fn with_source_loader(mut self, source_loader: &'a mut dyn SourceLoader) -> Self {
396        self.source_loader = Some(source_loader);
397        self
398    }
399
400    /// Build a [`Unit`] with the current configuration.
401    ///
402    /// See [`rune::prepare`] for more.
403    ///
404    /// [`rune::prepare`]: prepare
405    pub fn build(self) -> Result<Unit<S>, BuildError>
406    where
407        S: Default + UnitEncoder,
408    {
409        let (unit, ()) = self.build_inner(|_| ())?;
410        Ok(unit)
411    }
412
413    #[inline]
414    fn build_inner<O>(
415        mut self,
416        extra: impl FnOnce(&Context) -> O,
417    ) -> Result<(Unit<S>, O), BuildError>
418    where
419        S: Default + UnitEncoder,
420    {
421        let default_context;
422
423        let context = match self.context.take() {
424            Some(context) => context,
425            None => {
426                default_context = Context::new();
427                &default_context
428            }
429        };
430
431        let mut unit = compile::UnitBuilder::default();
432
433        let prelude = if context.has_default_modules() {
434            compile::Prelude::with_default_prelude()?
435        } else {
436            compile::Prelude::default()
437        };
438
439        let mut default_diagnostics;
440
441        let diagnostics = match self.diagnostics {
442            Some(diagnostics) => diagnostics,
443            None => {
444                default_diagnostics = Diagnostics::new();
445                &mut default_diagnostics
446            }
447        };
448
449        let default_options;
450
451        let options = match self.options {
452            Some(options) => options,
453            None => {
454                default_options = Options::from_default_env()?;
455                &default_options
456            }
457        };
458
459        let mut default_visitors;
460        let visitors = match self.visitors.is_empty() {
461            true => {
462                default_visitors = CompileVisitorGroup {
463                    visitors: Vec::new(),
464                };
465                &mut default_visitors
466            }
467            false => {
468                let v = take(&mut self.visitors);
469                default_visitors = CompileVisitorGroup { visitors: v };
470
471                &mut default_visitors
472            }
473        };
474
475        let mut default_source_loader;
476
477        let source_loader = match self.source_loader.take() {
478            Some(source_loader) => source_loader,
479            None => {
480                default_source_loader = DefaultSourceLoader::default();
481                &mut default_source_loader
482            }
483        };
484
485        if !self.args.is_empty() {
486            if let Some(options) = &mut self.options {
487                if !options.script {
488                    diagnostics.internal(
489                        SourceId::empty(),
490                        "cannot set script arguments without enabling script mode",
491                    )?;
492
493                    return Err(BuildError::default());
494                }
495            }
496
497            for arg in &self.args {
498                if !parse::is_ident(arg) {
499                    diagnostics.internal(
500                        SourceId::empty(),
501                        format!("script argument '{arg}' is not a valid identifier"),
502                    )?;
503                }
504            }
505        }
506
507        let default_statics;
508
509        let statics = match self.statics.take() {
510            Some(statics) => statics,
511            None => {
512                default_statics = Statics::new();
513                &default_statics
514            }
515        };
516
517        for s in statics.iter() {
518            let item = s.item();
519
520            if item.is_empty() {
521                diagnostics.internal(SourceId::empty(), "static must be declared with a name")?;
522                continue;
523            }
524
525            for c in item.iter() {
526                if !c.as_str().is_some_and(parse::is_ident) {
527                    diagnostics.internal(
528                        SourceId::empty(),
529                        format!("static '{item}' is not a valid item"),
530                    )?;
531                    break;
532                }
533            }
534        }
535
536        if diagnostics.has_error() {
537            return Err(BuildError::default());
538        }
539
540        let mut pool = Pool::new()?;
541        let mut unit_storage = S::default();
542
543        compile::compile(
544            &mut unit,
545            &prelude,
546            self.sources,
547            &mut pool,
548            context,
549            visitors,
550            diagnostics,
551            source_loader,
552            options,
553            &self.args,
554            statics,
555            &mut unit_storage,
556        )?;
557
558        if diagnostics.has_error() {
559            return Err(BuildError::default());
560        }
561
562        if options.link_checks {
563            unit.link(context, diagnostics)?;
564        }
565
566        if diagnostics.has_error() {
567            return Err(BuildError::default());
568        }
569
570        match unit.build(Span::empty(), unit_storage) {
571            Ok(unit) => Ok((unit, extra(context))),
572            Err(error) => {
573                diagnostics.error(SourceId::empty(), error)?;
574                Err(BuildError::default())
575            }
576        }
577    }
578}
579
580impl<'a> Build<'a, DefaultStorage> {
581    /// Convenience method to build a [`Vm`] directly from the current build
582    /// using default storage.
583    ///
584    /// The virtual machine is configured with freshly allocated [`Globals`]
585    /// storage for any static items the unit declares. Use [`Vm::globals`] to
586    /// access it.
587    pub fn build_vm(self) -> Result<Vm, BuildError> {
588        let (unit, runtime) = self.build_inner(|context| context.runtime())?;
589        let runtime = runtime?;
590        let runtime = Arc::try_new(runtime)?;
591        let unit = Arc::try_new(unit)?;
592        let globals = Globals::new(unit.clone())?;
593        Ok(Vm::new(runtime, unit).with_globals(globals))
594    }
595}