Documented code, got basic example working

Isaac Clayton created

Change summary

Cargo.lock                          |  7 +++
crates/runner/Cargo.toml            |  1 
crates/runner/plugin/cargo_test.lua | 22 ++++++++++
crates/runner/src/lib.rs            | 40 ++---------------
crates/runner/src/lua.rs            | 62 ++++++++++++++++++++++++++++
crates/runner/src/main.rs           | 57 ++++++++++++++++++++-----
crates/runner/src/runtime.rs        | 68 ++++++++++++++++++++++++++----
7 files changed, 200 insertions(+), 57 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2654,6 +2654,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "map-macro"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b0858fc6e216d2d6222d661021d9b184550acd757fbd80a8f86224069422c"
+
 [[package]]
 name = "matchers"
 version = "0.1.0"
@@ -4011,6 +4017,7 @@ dependencies = [
 name = "runner"
 version = "0.1.0"
 dependencies = [
+ "map-macro",
  "mlua",
  "serde",
 ]

crates/runner/Cargo.toml 🔗

@@ -6,4 +6,5 @@ edition = "2021"
 [dependencies]
 mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored", "serialize"] }
 serde = "1.0"
+map-macro = "0.2"
 # bincode = "1.3"

crates/runner/plugin/cargo_test.lua 🔗

@@ -0,0 +1,22 @@
+print("initializing plugin...")
+
+query = [[(
+    (attribute_item
+        (meta_item
+            (identifier) @test)) @attribute
+    .
+    (function_item
+        name: (identifier) @name) @funciton
+)]]
+
+function run_test(name)
+    print('running test `' .. name .. '`:')
+    local command = 'cargo test -- ' .. name
+    local openPop = assert(io.popen(command, 'r'))
+    local output = openPop:read('*all')
+    openPop:close()
+    print('done running test')
+    return output
+end
+
+print("done initializing plugin.")

crates/runner/src/lib.rs 🔗

@@ -1,40 +1,10 @@
-use std::collections::{HashMap, HashSet};
+use mlua::{Function, Lua, LuaSerdeExt, Value};
+use serde::{de::DeserializeOwned, Serialize};
 
-use mlua::{Error, FromLua, Function, Lua, LuaSerdeExt, ToLua, UserData, Value};
+pub use map_macro::{map, set};
 
 pub mod runtime;
 pub use runtime::*;
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
 
-impl Runtime for Lua {
-    type Module = String;
-
-    fn init(module: Self::Module) -> Option<Self> {
-        let lua = Lua::new();
-        lua.load(&module).exec().ok()?;
-        return Some(lua);
-    }
-
-    fn handles(&self) -> Handles {
-        let mut globals = HashSet::new();
-        for pair in self.globals().pairs::<String, Value>() {
-            if let Ok((k, _)) = pair {
-                globals.insert(k);
-            }
-        }
-
-        globals
-    }
-
-    fn val<T: DeserializeOwned>(&self, name: String) -> Option<T> {
-        let val: Value = self.globals().get(name).ok()?;
-        Some(self.from_value(val).ok()?)
-    }
-
-    fn call<T: Serialize + DeserializeOwned>(&self, name: String, arg: T) -> Option<T> {
-        let fun: Function = self.globals().get(name).ok()?;
-        let arg: Value = self.to_value(&arg).ok()?;
-        let result = fun.call(arg).ok()?;
-        Some(self.from_value(result).ok()?)
-    }
-}
+pub mod lua;
+pub use lua::*;

crates/runner/src/lua.rs 🔗

@@ -0,0 +1,62 @@
+use mlua::Result;
+
+use crate::*;
+
+impl Runtime for Lua {
+    type Plugin = LuaPlugin;
+    type Error = mlua::Error;
+
+    fn init(module: Self::Plugin) -> Result<Self> {
+        let lua = Lua::new();
+
+        // for action in module.actions {
+        //     action(&mut lua).ok()?;
+        // }
+
+        lua.load(&module.source).exec()?;
+        return Ok(lua);
+    }
+
+    fn constant<T: DeserializeOwned>(&mut self, handle: &Handle) -> Result<T> {
+        let val: Value = self.globals().get(handle.inner())?;
+        Ok(self.from_value(val)?)
+    }
+
+    fn call<T: Serialize + DeserializeOwned>(&mut self, handle: &Handle, arg: T) -> Result<T> {
+        let fun: Function = self.globals().get(handle.inner())?;
+        let arg: Value = self.to_value(&arg)?;
+        let result = fun.call(arg)?;
+        Ok(self.from_value(result)?)
+    }
+
+    fn register_handle<T: AsRef<str>>(&mut self, name: T) -> bool {
+        self.globals()
+            .contains_key(name.as_ref().to_string())
+            .unwrap_or(false)
+    }
+}
+
+pub struct LuaPlugin {
+    // name: String,
+    source: String,
+    // actions: Vec<Box<dyn FnOnce(&mut Lua) -> Result<(), ()>>>,
+}
+
+impl LuaPlugin {
+    pub fn new(
+        // name: String,
+        source: String,
+    ) -> LuaPlugin {
+        LuaPlugin {
+            // name,
+            source,
+            // actions: Vec::new(),
+        }
+    }
+
+    // pub fn setup(mut self, action: fn(&mut Lua) -> Result<(), ()>) -> LuaPlugin {
+    //     let action = Box::new(action);
+    //     self.actions.push(action);
+    //     self
+    // }
+}

crates/runner/src/main.rs 🔗

@@ -1,25 +1,58 @@
-use mlua::{Lua, Result};
+use mlua::Lua;
 
 use runner::*;
 
-pub fn main() {
-    let lua: Lua = Runtime::init(
-        "query = \"Some random tree-sitter query\"\nprint(\"Hello from the Lua test runner!\")"
-            .to_string(),
-    )
-    .unwrap();
+pub fn main() -> Result<(), mlua::Error> {
+    let source = include_str!("../plugin/cargo_test.lua").to_string();
+
+    let module = LuaPlugin::new(source);
+    // .setup(|runtime| {
+    //     let greet = runtime
+    //         .create_function(|_, name: String| {
+    //             println!("Hello, {}!", name);
+    //             Ok(())
+    //         })
+    //         .map_err(|_| ())?;
+
+    //     runtime.globals().set("greet", greet).map_err(|_| ())?;
+    //     Ok(())
+    // });
+
+    let mut lua: Lua = Runtime::init(module)?;
     let runner: TestRunner = lua.as_interface::<TestRunner>().unwrap();
-    println!("{:#?}", runner);
+
+    println!("extracted interface: {:#?}", &runner);
+
+    let contents = runner.run_test(&mut lua, "it_works".into());
+
+    println!("test results:{}", contents.unwrap());
+
+    Ok(())
 }
 
+#[allow(dead_code)]
 #[derive(Debug)]
 struct TestRunner {
-    query: String,
+    pub query: String,
+    run_test: Handle,
 }
 
 impl Interface for TestRunner {
-    fn from_runtime<T: Runtime>(runtime: &T) -> Option<TestRunner> {
-        let query: String = runtime.val("query".to_string())?;
-        Some(TestRunner { query })
+    fn from_runtime<T: Runtime>(runtime: &mut T) -> Option<Self> {
+        let run_test = runtime.handle_for("run_test")?;
+        let query = runtime.handle_for("query")?;
+        let query: String = runtime.constant(&query).ok()?;
+        Some(TestRunner { query, run_test })
+    }
+}
+
+impl TestRunner {
+    pub fn run_test<T: Runtime>(&self, runtime: &mut T, test_name: String) -> Option<String> {
+        runtime.call(&self.run_test, test_name).ok()
     }
 }
+
+#[test]
+pub fn it_works() {
+    panic!("huh, that was surprising...");
+}

crates/runner/src/runtime.rs 🔗

@@ -1,29 +1,77 @@
-use std::collections::HashSet;
+// use std::Error;
 
-use mlua::{FromLua, Lua, ToLua, Value};
 use serde::{de::DeserializeOwned, Serialize};
 
-pub type Handles = HashSet<String>;
+/// Represents a handle to a constant or function in the Runtime.
+/// Should be constructed by calling [`Runtime::handle_for`].
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Handle(String);
 
+impl Handle {
+    pub fn inner(&self) -> &str {
+        &self.0
+    }
+}
+
+/// Represents an interface that can be implemented by a plugin.
 pub trait Interface
 where
     Self: Sized,
 {
-    fn from_runtime<T: Runtime>(runtime: &T) -> Option<Self>;
+    /// Create an interface from a given runtime.
+    /// All handles to be used by the interface should be registered and stored in `Self`.
+    fn from_runtime<T: Runtime>(runtime: &mut T) -> Option<Self>;
 }
 
 pub trait Runtime
 where
     Self: Sized,
 {
-    type Module;
+    /// Represents a plugin to be loaded by the runtime,
+    /// e.g. some source code + anything else needed to set up.
+    type Plugin;
+
+    /// The error type for this module.
+    /// Ideally should implement the [`std::err::Error`] trait.
+    type Error;
+
+    /// Initializes a plugin, returning a [`Runtime`] that can be queried.
+    /// Note that if you have any configuration,
+    fn init(plugin: Self::Plugin) -> Result<Self, Self::Error>;
 
-    fn init(plugin: Self::Module) -> Option<Self>;
-    fn handles(&self) -> Handles;
-    fn val<T: DeserializeOwned>(&self, name: String) -> Option<T>;
-    fn call<T: Serialize + DeserializeOwned>(&self, name: String, arg: T) -> Option<T>;
+    /// Returns a top-level constant from the module.
+    /// This can be used to extract configuration information from the module, for example.
+    /// Before calling this function, get a handle into the runtime using [`handle_for`].
+    fn constant<T: DeserializeOwned>(&mut self, handle: &Handle) -> Result<T, Self::Error>;
+
+    /// Call a function defined in the module.
+    fn call<T: Serialize + DeserializeOwned>(
+        &mut self,
+        handle: &Handle,
+        arg: T,
+    ) -> Result<T, Self::Error>;
+
+    /// Registers a handle with the runtime.
+    /// This is a mutable item if needed, but generally
+    /// this should be an immutable operation.
+    /// Returns whether the handle exists/was successfully registered.
+    fn register_handle<T: AsRef<str>>(&mut self, name: T) -> bool;
+
+    /// Returns the handle for a given name if the handle is defined.
+    /// Will only return an error if there was an error while trying to register the handle.
+    /// This function uses [`register_handle`], no need to implement this one.
+    fn handle_for<T: AsRef<str>>(&mut self, name: T) -> Option<Handle> {
+        if self.register_handle(&name) {
+            Some(Handle(name.as_ref().to_string()))
+        } else {
+            None
+        }
+    }
 
-    fn as_interface<T: Interface>(&self) -> Option<T> {
+    /// Creates the given interface from the current module.
+    /// Returns [`Error`] if the provided plugin does not match the expected interface.
+    /// Essentially wraps the [`Interface`] trait.
+    fn as_interface<T: Interface>(&mut self) -> Option<T> {
         Interface::from_runtime(self)
     }
 }