1use std::any::Any;
4use std::collections::BTreeMap;
5use std::fmt;
6use std::fs;
7use std::io;
8use std::path::Path;
9
10use kinetik_diag::{Diagnostic, SourceFile, SourceId, Span};
11use kinetik_parse::parse_module;
12use kinetik_runtime::HostId;
13pub use kinetik_runtime::{HostHandle, Value};
14use kinetik_vm::{HostNativeResult, RuntimeError, Vm};
15
16pub type Result<T> = std::result::Result<T, Error>;
18
19#[derive(Clone, Debug, PartialEq)]
21pub struct ReloadReport {
22 pub value: Value,
24 pub diagnostics: Vec<Diagnostic>,
26}
27
28#[derive(Debug)]
30pub enum Error {
31 Io(io::Error),
33 Parse {
35 source: Box<SourceFile>,
37 diagnostics: Vec<Diagnostic>,
39 },
40 Runtime {
42 source: Option<Box<SourceFile>>,
44 error: Box<RuntimeError>,
46 },
47 MissingFunction(String),
49 Conversion(String),
51 StaleHostHandle {
53 id: u64,
55 },
56 HostTypeMismatch {
58 expected: &'static str,
60 },
61}
62
63impl fmt::Display for Error {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 Self::Io(error) => write!(f, "failed to read script: {error}"),
67 Self::Parse { diagnostics, .. } => {
68 write!(
69 f,
70 "failed to parse script with {} diagnostic(s)",
71 diagnostics.len()
72 )
73 }
74 Self::Runtime { error, .. } => write!(f, "runtime error: {}", error.message()),
75 Self::MissingFunction(name) => write!(f, "function `{name}` is not defined"),
76 Self::Conversion(message) => f.write_str(message),
77 Self::StaleHostHandle { id } => write!(f, "stale host handle #{id}"),
78 Self::HostTypeMismatch { expected } => {
79 write!(f, "host handle does not contain `{expected}`")
80 }
81 }
82 }
83}
84
85impl std::error::Error for Error {}
86
87impl From<io::Error> for Error {
88 fn from(error: io::Error) -> Self {
89 Self::Io(error)
90 }
91}
92
93#[derive(Debug)]
95pub struct Kinetik {
96 vm: Vm,
97 sources: BTreeMap<SourceId, SourceFile>,
98 hosts: HostRegistry,
99 next_source_id: u32,
100}
101
102impl Default for Kinetik {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108impl Kinetik {
109 #[must_use]
111 pub fn new() -> Self {
112 Self {
113 vm: Vm::new(),
114 sources: BTreeMap::new(),
115 hosts: HostRegistry::default(),
116 next_source_id: 1,
117 }
118 }
119
120 pub fn register_native<F>(&mut self, name: impl Into<String>, function: F)
122 where
123 F: Fn(&[Value]) -> HostNativeResult + Send + Sync + 'static,
124 {
125 self.vm.define_host_native(name, function);
126 }
127
128 pub fn insert_host<T>(&mut self, value: T) -> Value
130 where
131 T: Any + Send + Sync + 'static,
132 {
133 self.hosts.insert(value)
134 }
135
136 pub fn remove_host(&mut self, value: &Value) -> Result<()> {
142 self.hosts.remove(value)
143 }
144
145 pub fn with_host<T, R>(&self, value: &Value, read: impl FnOnce(&T) -> R) -> Result<R>
151 where
152 T: Any + Send + Sync + 'static,
153 {
154 self.hosts.with(value, read)
155 }
156
157 pub fn with_host_mut<T, R>(
163 &mut self,
164 value: &Value,
165 write: impl FnOnce(&mut T) -> R,
166 ) -> Result<R>
167 where
168 T: Any + Send + Sync + 'static,
169 {
170 self.hosts.with_mut(value, write)
171 }
172
173 pub fn define_global(&mut self, name: impl Into<String>, value: Value) {
175 self.vm.define_global(name, value);
176 }
177
178 pub fn load_source(
184 &mut self,
185 name: impl Into<String>,
186 text: impl Into<String>,
187 ) -> Result<Value> {
188 self.eval_source(name, text)
189 }
190
191 pub fn reload_source(
200 &mut self,
201 name: impl Into<String>,
202 text: impl Into<String>,
203 ) -> Result<ReloadReport> {
204 let source = self.source_file(name, text);
205 let parsed = parse_module(&source);
206 if parsed.has_errors() {
207 return Err(Error::Parse {
208 source: Box::new(source),
209 diagnostics: parsed.diagnostics,
210 });
211 }
212
213 let Some(module) = parsed.node else {
214 return Err(Error::Conversion(String::from(
215 "parser did not produce a module",
216 )));
217 };
218
219 let previous = self.vm.globals();
220 let stale_tasks = self.vm.cancel_stale_tasks();
221 self.sources.insert(source.id(), source.clone());
222 let value = self
223 .vm
224 .eval_module(&module)
225 .map_err(|error| self.runtime_error(error))?;
226
227 let mut diagnostics = preserve_compatible_globals(&mut self.vm, &previous, module.span);
228 if stale_tasks > 0 {
229 diagnostics.push(
230 Diagnostic::warning(format!(
231 "cancelled {stale_tasks} stale task(s) during reload"
232 ))
233 .with_span(module.span),
234 );
235 }
236
237 Ok(ReloadReport { value, diagnostics })
238 }
239
240 fn eval_source(&mut self, name: impl Into<String>, text: impl Into<String>) -> Result<Value> {
241 let source = self.source_file(name, text);
242 let parsed = parse_module(&source);
243 if parsed.has_errors() {
244 return Err(Error::Parse {
245 source: Box::new(source),
246 diagnostics: parsed.diagnostics,
247 });
248 }
249
250 self.sources.insert(source.id(), source.clone());
251 let Some(module) = parsed.node else {
252 return Err(Error::Conversion(String::from(
253 "parser did not produce a module",
254 )));
255 };
256 let value = self
257 .vm
258 .eval_module(&module)
259 .map_err(|error| self.runtime_error(error))?;
260 Ok(value)
261 }
262
263 pub fn load_file(&mut self, path: impl AsRef<Path>) -> Result<Value> {
269 let path = path.as_ref();
270 let text = fs::read_to_string(path)?;
271 self.load_source(path.display().to_string(), text)
272 }
273
274 pub fn call_function(&mut self, name: &str, args: &[Value]) -> Result<Vec<Value>> {
280 let callee = self
281 .vm
282 .get(name)
283 .ok_or_else(|| Error::MissingFunction(name.to_owned()))?;
284 self.vm
285 .call(&callee, args, Span::new(SourceId(0), 0, 0))
286 .map_err(|error| self.runtime_error(error))
287 }
288
289 #[must_use]
291 pub fn get(&self, name: &str) -> Option<Value> {
292 self.vm.get(name)
293 }
294
295 pub fn drain_output(&mut self) -> impl Iterator<Item = String> + '_ {
297 self.vm.drain_output()
298 }
299
300 fn source_file(&mut self, name: impl Into<String>, text: impl Into<String>) -> SourceFile {
301 let id = SourceId(self.next_source_id);
302 self.next_source_id += 1;
303 SourceFile::new(id, name, text)
304 }
305
306 fn runtime_error(&self, error: RuntimeError) -> Error {
307 Error::Runtime {
308 source: self
309 .sources
310 .get(&error.span().source)
311 .cloned()
312 .map(Box::new),
313 error: Box::new(error),
314 }
315 }
316}
317
318fn preserve_compatible_globals(
319 vm: &mut Vm,
320 previous: &BTreeMap<String, Value>,
321 span: Span,
322) -> Vec<Diagnostic> {
323 let current = vm.globals();
324 let mut diagnostics = Vec::new();
325
326 for (name, old_value) in previous {
327 let Some(new_value) = current.get(name) else {
328 continue;
329 };
330 if matches!(new_value, Value::Function(_)) {
331 continue;
332 }
333 if old_value.type_name() == new_value.type_name() {
334 vm.define_global(name.clone(), old_value.clone());
335 } else {
336 diagnostics.push(
337 Diagnostic::warning(format!(
338 "reload recreated `{name}` because its type changed from {} to {}",
339 old_value.type_name(),
340 new_value.type_name()
341 ))
342 .with_span(span),
343 );
344 }
345 }
346
347 vm.refresh_function_globals();
348 diagnostics
349}
350
351pub trait IntoValue {
353 fn into_value(self) -> Value;
355}
356
357impl IntoValue for Value {
358 fn into_value(self) -> Value {
359 self
360 }
361}
362
363impl IntoValue for () {
364 fn into_value(self) -> Value {
365 Value::Nil
366 }
367}
368
369impl IntoValue for bool {
370 fn into_value(self) -> Value {
371 Value::bool(self)
372 }
373}
374
375impl IntoValue for f64 {
376 fn into_value(self) -> Value {
377 Value::number(self)
378 }
379}
380
381impl IntoValue for String {
382 fn into_value(self) -> Value {
383 Value::string(self)
384 }
385}
386
387impl IntoValue for &str {
388 fn into_value(self) -> Value {
389 Value::string(self)
390 }
391}
392
393impl<T: IntoValue> IntoValue for Vec<T> {
394 fn into_value(self) -> Value {
395 Value::array(
396 self.into_iter()
397 .map(IntoValue::into_value)
398 .collect::<Vec<_>>(),
399 )
400 }
401}
402
403impl<T: IntoValue> IntoValue for BTreeMap<String, T> {
404 fn into_value(self) -> Value {
405 Value::object(
406 self.into_iter()
407 .map(|(key, value)| (key, value.into_value())),
408 )
409 }
410}
411
412pub trait FromValue: Sized {
414 fn from_value(value: Value) -> Result<Self>;
420}
421
422impl FromValue for Value {
423 fn from_value(value: Value) -> Result<Self> {
424 Ok(value)
425 }
426}
427
428impl FromValue for bool {
429 fn from_value(value: Value) -> Result<Self> {
430 match value {
431 Value::Bool(value) => Ok(value),
432 other => Err(type_error("bool", &other)),
433 }
434 }
435}
436
437impl FromValue for f64 {
438 fn from_value(value: Value) -> Result<Self> {
439 match value {
440 Value::Number(value) => Ok(value),
441 other => Err(type_error("number", &other)),
442 }
443 }
444}
445
446impl FromValue for String {
447 fn from_value(value: Value) -> Result<Self> {
448 match value {
449 Value::String(value) => Ok(value),
450 other => Err(type_error("string", &other)),
451 }
452 }
453}
454
455impl<T: FromValue> FromValue for Vec<T> {
456 fn from_value(value: Value) -> Result<Self> {
457 match value {
458 Value::Array(array) => array
459 .elements()
460 .iter()
461 .cloned()
462 .map(T::from_value)
463 .collect::<Result<Vec<_>>>(),
464 other => Err(type_error("array", &other)),
465 }
466 }
467}
468
469impl<T: FromValue> FromValue for BTreeMap<String, T> {
470 fn from_value(value: Value) -> Result<Self> {
471 match value {
472 Value::Object(object) => object
473 .fields()
474 .iter()
475 .map(|(key, value)| T::from_value(value.clone()).map(|value| (key.clone(), value)))
476 .collect::<Result<BTreeMap<_, _>>>(),
477 other => Err(type_error("object", &other)),
478 }
479 }
480}
481
482fn type_error(expected: &str, value: &Value) -> Error {
483 Error::Conversion(format!("expected {expected}, got {}", value.type_name()))
484}
485
486#[derive(Default)]
487struct HostRegistry {
488 entries: BTreeMap<HostId, HostEntry>,
489 next_id: u64,
490}
491
492impl fmt::Debug for HostRegistry {
493 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
494 f.debug_struct("HostRegistry")
495 .field("entry_count", &self.entries.len())
496 .field("next_id", &self.next_id)
497 .finish()
498 }
499}
500
501impl HostRegistry {
502 fn insert<T>(&mut self, value: T) -> Value
503 where
504 T: Any + Send + Sync + 'static,
505 {
506 self.next_id += 1;
507 let id = HostId::new(self.next_id);
508 let generation = 1;
509 self.entries.insert(
510 id,
511 HostEntry {
512 generation,
513 value: Box::new(value),
514 },
515 );
516 Value::host(id, generation)
517 }
518
519 fn remove(&mut self, value: &Value) -> Result<()> {
520 let handle = host_handle(value)?;
521 let entry = self
522 .entries
523 .get(&handle.id())
524 .ok_or_else(|| stale_handle(handle))?;
525 if entry.generation != handle.generation() {
526 return Err(stale_handle(handle));
527 }
528 self.entries.remove(&handle.id());
529 Ok(())
530 }
531
532 fn with<T, R>(&self, value: &Value, read: impl FnOnce(&T) -> R) -> Result<R>
533 where
534 T: Any + Send + Sync + 'static,
535 {
536 let entry = self.entry(value)?;
537 let Some(value) = entry.value.downcast_ref::<T>() else {
538 return Err(Error::HostTypeMismatch {
539 expected: std::any::type_name::<T>(),
540 });
541 };
542 Ok(read(value))
543 }
544
545 fn with_mut<T, R>(&mut self, value: &Value, write: impl FnOnce(&mut T) -> R) -> Result<R>
546 where
547 T: Any + Send + Sync + 'static,
548 {
549 let entry = self.entry_mut(value)?;
550 let Some(value) = entry.value.downcast_mut::<T>() else {
551 return Err(Error::HostTypeMismatch {
552 expected: std::any::type_name::<T>(),
553 });
554 };
555 Ok(write(value))
556 }
557
558 fn entry(&self, value: &Value) -> Result<&HostEntry> {
559 let handle = host_handle(value)?;
560 let entry = self
561 .entries
562 .get(&handle.id())
563 .ok_or_else(|| stale_handle(handle))?;
564 if entry.generation == handle.generation() {
565 Ok(entry)
566 } else {
567 Err(stale_handle(handle))
568 }
569 }
570
571 fn entry_mut(&mut self, value: &Value) -> Result<&mut HostEntry> {
572 let handle = host_handle(value)?;
573 let entry = self
574 .entries
575 .get_mut(&handle.id())
576 .ok_or_else(|| stale_handle(handle))?;
577 if entry.generation == handle.generation() {
578 Ok(entry)
579 } else {
580 Err(stale_handle(handle))
581 }
582 }
583}
584
585struct HostEntry {
586 generation: u64,
587 value: Box<dyn Any + Send + Sync>,
588}
589
590fn host_handle(value: &Value) -> Result<HostHandle> {
591 match value {
592 Value::Host(handle) => Ok(*handle),
593 other => Err(type_error("native host handle", other)),
594 }
595}
596
597fn stale_handle(handle: HostHandle) -> Error {
598 Error::StaleHostHandle {
599 id: handle.id().get(),
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::{Error, FromValue, IntoValue, Kinetik, Value};
606
607 #[test]
608 fn converts_core_values() {
609 assert_eq!(true.into_value(), Value::bool(true));
610 assert_eq!(String::from("hi").into_value(), Value::string("hi"));
611
612 let value = vec![1.0, 2.0].into_value();
613 assert_eq!(
614 Vec::<f64>::from_value(value).expect("array"),
615 vec![1.0, 2.0]
616 );
617 }
618
619 #[test]
620 fn creates_runtime() {
621 let mut runtime = Kinetik::new();
622 runtime
623 .load_source("test.kn", "let x = 2\n")
624 .expect("loads source");
625
626 assert_eq!(runtime.get("x"), Some(Value::number(2.0)));
627 }
628
629 #[test]
630 fn stores_and_invalidates_host_handles() {
631 let mut runtime = Kinetik::new();
632 let handle = runtime.insert_host(String::from("player"));
633
634 let name = runtime
635 .with_host::<String, _>(&handle, Clone::clone)
636 .expect("host value");
637 assert_eq!(name, "player");
638
639 runtime
640 .with_host_mut::<String, _>(&handle, |name| name.push_str("-1"))
641 .expect("host value mut");
642 assert_eq!(
643 runtime
644 .with_host::<String, _>(&handle, Clone::clone)
645 .expect("host value"),
646 "player-1"
647 );
648
649 runtime.remove_host(&handle).expect("removes host value");
650 assert!(matches!(
651 runtime.with_host::<String, _>(&handle, Clone::clone),
652 Err(Error::StaleHostHandle { .. })
653 ));
654 }
655}