1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::{
5 capability::CapabilityName,
6 env::Cx,
7 error::{Diagnostic, Result, Severity},
8 expr::Expr,
9 hint::{HintMetadata, diagnostic_hints_value},
10 id::{CORE_EVAL_REPLY_CLASS_ID, CORE_EVAL_REQUEST_CLASS_ID, ClassId, Symbol},
11 object::{ClassRef, Object, ShapeRef},
12 value::Value,
13};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Phase {
22 Read,
24 Expand,
26 Compile,
28 Eval,
30}
31
32pub trait MacroExpander: Send + Sync {
37 fn expand_expr(&self, cx: &mut Cx, phase: Phase, expr: Expr) -> Result<Expr>;
39}
40
41pub type MacroExpanderRef = Arc<dyn MacroExpander>;
43
44#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
46pub enum Consistency {
47 LocalOnly,
49 #[default]
51 LocalFirst,
52 RemoteOnly,
54}
55
56impl Consistency {
57 pub fn as_symbol(self) -> Symbol {
59 Symbol::new(match self {
60 Self::LocalOnly => "local-only",
61 Self::LocalFirst => "local-first",
62 Self::RemoteOnly => "remote-only",
63 })
64 }
65}
66
67#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
69pub enum EvalMode {
70 #[default]
72 Eval,
73 Logic,
75}
76
77impl EvalMode {
78 pub fn as_symbol(self) -> Symbol {
80 Symbol::new(match self {
81 Self::Eval => "eval",
82 Self::Logic => "logic",
83 })
84 }
85}
86
87#[derive(Clone)]
94pub struct EvalRequest {
95 pub expr: Expr,
97 pub result_shape: Option<ShapeRef>,
99 pub required_capabilities: Vec<CapabilityName>,
101 pub deadline: Option<Duration>,
103 pub consistency: Consistency,
105 pub mode: EvalMode,
107 pub answer_limit: Option<usize>,
109 pub stream_buffer: Option<usize>,
111 pub stream: bool,
113 pub trace: bool,
115}
116
117#[derive(Clone)]
120pub struct EvalReply {
121 pub value: Value,
123 pub diagnostics: Vec<Diagnostic>,
125 pub trace: Option<Value>,
127}
128
129pub trait EvalFabric: Send + Sync {
137 fn realize(&self, cx: &mut Cx, request: EvalRequest) -> Result<EvalReply>;
139}
140
141pub type EvalFabricRef = Arc<dyn EvalFabric>;
143
144impl Object for EvalRequest {
145 fn display(&self, _cx: &mut Cx) -> Result<String> {
146 Ok("#<EvalRequest>".to_owned())
147 }
148
149 fn as_any(&self) -> &dyn std::any::Any {
150 self
151 }
152}
153
154impl crate::ObjectCompat for EvalRequest {
155 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
156 runtime_class(
157 cx,
158 CORE_EVAL_REQUEST_CLASS_ID,
159 Symbol::qualified("core", "EvalRequest"),
160 )
161 }
162 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
163 self.as_table(cx)?.object().as_expr(cx)
164 }
165 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
166 let result_shape = match &self.result_shape {
167 Some(shape) => shape.clone(),
168 None => cx.factory().nil()?,
169 };
170 let deadline = match self.deadline {
171 Some(deadline) => cx.factory().string(format_duration(deadline))?,
172 None => cx.factory().nil()?,
173 };
174 let required_capabilities = cx.factory().list(
175 self.required_capabilities
176 .iter()
177 .map(|capability| cx.factory().string(capability.as_str().to_owned()))
178 .collect::<Result<Vec<_>>>()?,
179 )?;
180 cx.factory().table(vec![
181 (Symbol::new("expr"), cx.factory().expr(self.expr.clone())?),
182 (Symbol::new("result-shape"), result_shape),
183 (Symbol::new("requires"), required_capabilities),
184 (Symbol::new("deadline"), deadline),
185 (
186 Symbol::new("consistency"),
187 cx.factory().symbol(self.consistency.as_symbol())?,
188 ),
189 (
190 Symbol::new("mode"),
191 cx.factory().symbol(self.mode.as_symbol())?,
192 ),
193 (
194 Symbol::new("answer-limit"),
195 match self.answer_limit {
196 Some(limit) => cx.factory().string(limit.to_string())?,
197 None => cx.factory().nil()?,
198 },
199 ),
200 (
201 Symbol::new("stream-buffer"),
202 match self.stream_buffer {
203 Some(limit) => cx.factory().string(limit.to_string())?,
204 None => cx.factory().nil()?,
205 },
206 ),
207 (Symbol::new("stream"), cx.factory().bool(self.stream)?),
208 (Symbol::new("trace"), cx.factory().bool(self.trace)?),
209 ])
210 }
211}
212
213impl Object for EvalReply {
214 fn display(&self, _cx: &mut Cx) -> Result<String> {
215 Ok("#<EvalReply>".to_owned())
216 }
217
218 fn as_any(&self) -> &dyn std::any::Any {
219 self
220 }
221}
222
223impl crate::ObjectCompat for EvalReply {
224 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
225 runtime_class(
226 cx,
227 CORE_EVAL_REPLY_CLASS_ID,
228 Symbol::qualified("core", "EvalReply"),
229 )
230 }
231 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
232 self.as_table(cx)?.object().as_expr(cx)
233 }
234 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
235 let trace = match &self.trace {
236 Some(trace) => trace.clone(),
237 None => cx.factory().nil()?,
238 };
239 let diagnostic_values = self
240 .diagnostics
241 .iter()
242 .map(|diagnostic| diagnostic_value(cx, diagnostic))
243 .collect::<Result<Vec<_>>>()?;
244 let diagnostics = cx.factory().list(diagnostic_values)?;
245 cx.factory().table(vec![
246 (Symbol::new("value"), self.value.clone()),
247 (Symbol::new("diagnostics"), diagnostics),
248 (Symbol::new("trace"), trace),
249 ])
250 }
251}
252
253fn runtime_class(cx: &mut Cx, id: ClassId, symbol: Symbol) -> Result<ClassRef> {
254 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
255 return Ok(value.clone());
256 }
257 cx.factory().class_stub(id, symbol)
258}
259
260fn format_duration(duration: Duration) -> String {
261 if duration.subsec_nanos() == 0 && duration.as_secs() > 0 {
262 format!("{}s", duration.as_secs())
263 } else {
264 format!("{}ms", duration.as_millis())
265 }
266}
267
268fn diagnostic_value(cx: &mut Cx, diagnostic: &Diagnostic) -> Result<Value> {
269 let hints = diagnostic_hints_value(cx, diagnostic)?;
270 let severity = cx.factory().symbol(Symbol::new(match diagnostic.severity {
271 Severity::Error => "error",
272 Severity::Warning => "warning",
273 Severity::Info => "info",
274 Severity::Note => "note",
275 }))?;
276 let source = match &diagnostic.source {
277 Some(source) => cx.factory().string(source.0.to_string())?,
278 None => cx.factory().nil()?,
279 };
280 let span = match &diagnostic.span {
281 Some(span) => cx.factory().table(vec![
282 (
283 Symbol::new("start"),
284 cx.factory().string(span.start.to_string())?,
285 ),
286 (
287 Symbol::new("end"),
288 cx.factory().string(span.end.to_string())?,
289 ),
290 ])?,
291 None => cx.factory().nil()?,
292 };
293 let code = match &diagnostic.code {
294 Some(code) => cx.factory().symbol(code.clone())?,
295 None => cx.factory().nil()?,
296 };
297 let related_values = diagnostic
298 .related
299 .iter()
300 .filter(|related| !HintMetadata::is_hint_diagnostic(related))
301 .map(|related| diagnostic_value(cx, related))
302 .collect::<Result<Vec<_>>>()?;
303 let related = cx.factory().list(related_values)?;
304 cx.factory().table(vec![
305 (Symbol::new("severity"), severity),
306 (
307 Symbol::new("message"),
308 cx.factory().string(diagnostic.message.clone())?,
309 ),
310 (Symbol::new("source"), source),
311 (Symbol::new("span"), span),
312 (Symbol::new("code"), code),
313 (Symbol::new("related"), related),
314 (Symbol::new("hints"), hints),
315 ])
316}