about summary refs log tree commit diff
path: root/tvix/eval/src/lib.rs
blob: 43a085108258f55148197277e23e6664dcbccb49 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! `tvix-eval` implements the evaluation of the Nix programming language in
//! Tvix.
//!
//! It is designed to allow users to use Nix as a versatile language for
//! different use-cases.
//!
//! This module exports the high-level functions and types needed for evaluating
//! Nix code and interacting with the language's data structures.
//!
//! Nix has several language features that make use of impurities (such as
//! reading from the NIX_PATH environment variable, or interacting with files).
//! These features are optional and the API of this crate exposes functionality
//! for controlling how they work.

mod builtins;
mod chunk;
mod compiler;
mod errors;
mod eval;
pub mod observer;
mod opcode;
mod pretty_ast;
mod source;
mod spans;
mod systems;
mod upvalues;
mod value;
mod vm;
mod warnings;

mod nix_search_path;
#[cfg(test)]
mod properties;
#[cfg(test)]
mod test_utils;
#[cfg(test)]
mod tests;

use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;

// Re-export the public interface used by other crates.
pub use crate::builtins::global_builtins;
pub use crate::compiler::{compile, prepare_globals};
pub use crate::errors::{Error, ErrorKind, EvalResult};
pub use crate::eval::{interpret, Options};
pub use crate::pretty_ast::pretty_print_expr;
pub use crate::source::SourceCode;
pub use crate::value::Value;
pub use crate::vm::run_lambda;
pub use crate::warnings::EvalWarning;

/// Internal-only parts of `tvix-eval`, exported for use in macros, but not part of the public
/// interface of the crate.
pub mod internal {
    pub use crate::value::{Builtin, BuiltinArgument};
    pub use crate::vm::VM;
}

// TODO: use Rc::unwrap_or_clone once it is stabilised.
// https://doc.rust-lang.org/std/rc/struct.Rc.html#method.unwrap_or_clone
pub(crate) fn unwrap_or_clone_rc<T: Clone>(rc: Rc<T>) -> T {
    Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone())
}

/// An `Evaluation` represents how a piece of Nix code is evaluated. It can be
/// instantiated and configured directly, or it can be accessed through the
/// various simplified helper methods available below.
#[derive(Clone)]
pub struct Evaluation<'a> {
    /// The Nix source code to be evaluated.
    code: &'a str,

    /// Optional location of the source code (i.e. path to the file it was read
    /// from). Used for error reporting, and for resolving relative paths in
    /// impure functions.
    location: Option<PathBuf>,

    /// Source code map used for error reporting.
    source_map: SourceCode,

    /// Top-level file reference for this code inside the source map.
    file: Arc<codemap::File>,

    /// Root expression of the Nix code after parsing.
    expr: Option<rnix::ast::Expr>,
}

/// Result of evaluating a piece of Nix code. If evaluation succeeded, a value
/// will be present (and potentially some warnings!). If evaluation failed,
/// errors will be present.
#[derive(Debug, Default)]
pub struct EvaluationResult {
    /// Nix value that the code evaluated to.
    pub value: Option<Value>,

    /// Errors that occured during evaluation (if any).
    pub errors: Vec<Error>,

    /// Warnings that occured during evaluation. Warnings are not critical, but
    /// should be addressed either to modernise code or improve performance.
    pub warnings: Vec<EvalWarning>,
}

impl<'a> Evaluation<'a> {
    /// Initialise an `Evaluation` for the given Nix source code snippet, and
    /// an optional code location.
    /// reporting the location of errors in the code.
    pub fn new(code: &'a str, location: Option<PathBuf>) -> Self {
        let source_map = SourceCode::new();

        let location_str = location
            .as_ref()
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_else(|| "[code]".into());

        let file = source_map.add_file(location_str, code.into());

        Evaluation {
            code,
            location,
            source_map,
            file,
            expr: None,
        }
    }

    /// Clone the reference to the contained source code map. This is used after
    /// an evaluation for pretty error printing.
    pub fn source_map(&self) -> SourceCode {
        self.source_map.clone()
    }

    /// Evaluate the provided source code.
    pub fn evaluate(&mut self) -> EvaluationResult {
        let mut result = EvaluationResult::default();
        let parsed = rnix::ast::Root::parse(self.code);
        let parse_errors = parsed.errors();

        if !parse_errors.is_empty() {
            result.errors.push(Error {
                kind: ErrorKind::ParseErrors(parse_errors.to_vec()),
                span: self.file.span,
            });
            return result;
        }

        // At this point we know that the code is free of parse errors and we
        // can continue to compile it.
        //
        // The root expression is persisted in self in case the caller wants
        // access to the parsed expression.
        self.expr = parsed.tree().expr();

        let builtins =
            crate::compiler::prepare_globals(Box::new(global_builtins(self.source_map())));

        let compiler_result = match compiler::compile(
            self.expr.as_ref().unwrap(),
            self.location.take(),
            self.file.clone(),
            builtins,
            &mut observer::NoOpObserver::default(), // TODO: compilation observer
        ) {
            Ok(result) => result,
            Err(err) => {
                result.errors.push(err);
                return result;
            }
        };

        result.warnings = compiler_result.warnings;

        if !compiler_result.errors.is_empty() {
            result.errors = compiler_result.errors;
            return result;
        }

        // If there were no errors during compilation, the resulting bytecode is
        // safe to execute.
        let vm_result = run_lambda(
            Default::default(), // TODO: add nix search path to `Evaluation`
            &mut observer::NoOpObserver::default(), // TODO: runtime observer
            compiler_result.lambda,
        );

        match vm_result {
            Ok(mut runtime_result) => {
                result.warnings.append(&mut runtime_result.warnings);
                result.value = Some(runtime_result.value);
            }
            Err(err) => {
                result.errors.push(err);
            }
        }

        result
    }
}