compose_eval/
lib.rs

1/*!
2The Compose Interpreter
3
4
5*/
6
7mod access;
8mod evaluated;
9mod expression;
10mod statement;
11pub mod test;
12mod vm;
13
14pub use crate::vm::Machine;
15use compose_library::diag::{IntoSourceDiagnostic, SourceDiagnostic, SourceResult, Warned, error};
16use compose_library::{Value, Vm};
17use compose_syntax::ast::Statement;
18use compose_syntax::{Source, Span};
19use ecow::{EcoVec, eco_vec};
20pub use evaluated::Evaluated;
21use std::cmp::min;
22use std::ops::Range;
23
24/**
25Represents a language construct that can be evaluated. Examples are expressions and statements.
26
27Evaluation may:
28  - Have side effects like writing to stdout or writing to a file.
29  - Allocate on the VM-managed heap.
30  - Trigger flow control (break, continue, return).
31  - Introduce lexical or flow bindings.
32
33# Contract
34
35## Scope management
36Implementors that enter **lexical** or **flow** scopes must leave these scopes before returning from [`Eval::eval`].
37
38To make this less error-prone, [`Machine`] provides several helpers:
39- [`Machine::in_lexical_scope`]
40- [`Machine::new_lexical_scope_guard`]
41- [`Machine::in_flow_scope_guard`]
42- [`Machine::new_flow_scope_guard`]
43
44To read more about the rationale behind scope usage see [`compose_library::foundations::scope::Scopes`].
45
46*Example: Evaluating a code block*
47
48```rust,ignore
49use compose_eval::{Eval, Machine};
50use compose_library::diag::SourceResult;
51use compose_syntax::ast::CodeBlock;
52use crate::Evaluated;
53
54impl Eval for CodeBlock<'_> {
55    fn eval(self, vm: &mut Machine) -> SourceResult<Evaluated> {
56        let flow = vm.flow.take();
57        let mut result = Evaluated::unit();
58
59        let statements = self.statements();
60
61        // in_lexical_scope enters a scope for the duration of the closure
62        vm.in_lexical_scope(|vm| {
63            for statement in statements {
64                result = statement.eval(vm)?;
65                if vm.flow.is_some() {
66                    break;
67                }
68            }
69            SourceResult::Ok(())
70        })?;
71
72        if let Some(flow) = flow {
73            vm.flow = Some(flow);
74        }
75
76        Ok(result)
77    }
78}
79```
80
81
82## Any interaction with the outside world should go through the [`compose_library::World`] trait object in the [`Machine::engine`].
83
84This is required to uphold the sandbox in which compose is evaluated and allows implementations to work in
85any situation is used in.
86
87*Example: writing to stdout*
88
89```rust,ignore
90# use compose_eval::{Eval, Machine, Evaluated};
91# use compose_syntax::{Span};
92# use compose_library::diag::{At, SourceResult};
93struct WriteToStdout { span: Span }
94
95impl Eval for WriteToStdout {
96    fn eval(self, vm: &mut Machine) -> SourceResult<Evaluated> {
97        vm
98            .engine
99            .world
100            .write(&mut |write| write!(write, "Hello, world from Compose!"))
101            .map_err(|e| format!("An error occurred while writing to stdout: {}", e))
102            .at(self.span)?;
103
104        Ok(Evaluated::unit())
105    }
106}
107```
108
109## Temporary values need to be rooted while performing GC
110
111A value returned from an evaluation may not yet be rooted yet. Any values on the stack within the [`Eval::eval`]
112function must be rooted while performing GC.
113
114Use [`Machine::temp_root_guard`] or [`Machine::temp_root_scope`] to open a temporary root scope. Then track
115any temporary values with [`Machine::track_tmp_root`].
116
117*Example: GC after evaluating a statement*
118
119```rust,ignore
120use compose_eval::{Eval, Machine, Evaluated};
121use compose_library::{Binding, IntoValue, Module, diag::SourceResult};
122use compose_syntax::{ast};
123
124impl Eval for ast::Statement<'_> {
125    fn eval(self, vm: &mut Machine) -> SourceResult<Evaluated> {
126        // This might return a value with a heap allocation.
127        // The VM might not yet know about it if it is not bound in any scope.
128        // But the value might still be used after this statement.
129        let result = match self {
130            ast::Statement::Expr(e) => e.eval(vm),
131            // ...
132            _ => unimplemented!()
133        };
134
135        // Temporarily root the value to ensure it is not GCed before we use it.
136        vm.temp_root_scope(|vm| {
137            if let Ok(result) = &result {
138                vm.track_tmp_root(result.value());
139            }
140
141            vm.maybe_gc();
142            Ok(())
143        })?;
144
145        result
146    }
147}
148```
149*/
150
151pub trait Eval {
152    fn eval(self, vm: &mut Machine) -> SourceResult<Evaluated>;
153}
154
155pub fn eval_source(
156    source: &Source,
157    vm: &mut Machine,
158) -> Warned<SourceResult<Value>> {
159    eval_source_range(source, 0..usize::MAX, vm)
160}
161
162pub fn eval(vm: &mut Machine) -> Warned<SourceResult<Value>> {
163    let entry_point = vm.engine().world.entry_point();
164    let source = match vm.engine().world.source(entry_point) {
165        Ok(source) => source,
166        Err(err) => {
167            return Warned::new(Err(eco_vec!(err.into_source_diagnostic(Span::detached()))));
168        }
169    };
170
171    eval_source(&source, vm)
172}
173
174/// Eval a source file.
175///
176/// eval_range: eval these nodes
177pub fn eval_source_range(
178    source: &Source,
179    eval_range: Range<usize>,
180    vm: &mut Machine,
181) -> Warned<SourceResult<Value>> {
182    let mut result = Value::unit();
183
184    let range_start = min(eval_range.start, source.nodes().len());
185    let range_end = min(eval_range.end, source.nodes().len());
186
187    let nodes = source.nodes().get(range_start..range_end).unwrap();
188    let errors = nodes
189        .iter()
190        .flat_map(|n| n.errors())
191        .map(|e| e.into())
192        .collect::<EcoVec<SourceDiagnostic>>();
193
194    let syntax_warnings = nodes
195        .iter()
196        .flat_map(|n| n.warnings())
197        .map(|e| e.into())
198        .collect::<EcoVec<SourceDiagnostic>>();
199
200    if !errors.is_empty() {
201        return Warned::new(Err(errors)).with_warnings(syntax_warnings);
202    }
203
204    for node in nodes {
205        let statement: Statement = match node.cast() {
206            Some(expr) => expr,
207            None => {
208                let span = node.span();
209                let err = error!(span, "expected a statement, found {:?}", node);
210
211                return build_err(&syntax_warnings, vm, eco_vec![err]);
212            }
213        };
214        result = match statement.eval(vm) {
215            Ok(value) => value.value,
216            Err(err) => return build_err(&syntax_warnings, vm, err),
217        }
218    }
219
220    let mut warnings = vm.sink_mut().take_warnings();
221    warnings.extend_from_slice(&syntax_warnings);
222
223    Warned::new(Ok(result)).with_warnings(warnings)
224}
225
226pub fn build_err(
227    syntax_warnings: &[SourceDiagnostic],
228    vm: &mut Machine,
229    errs: EcoVec<SourceDiagnostic>,
230) -> Warned<SourceResult<Value>> {
231    let mut warnings = vm.sink_mut().take_warnings();
232    warnings.extend_from_slice(syntax_warnings);
233
234    Warned::new(Err(errs)).with_warnings(warnings)
235}