From 3620168b6cdf2487abb3fea018e4ac9e4a44493a Mon Sep 17 00:00:00 2001 From: Peefy Date: Sat, 24 Aug 2024 21:07:16 +0800 Subject: [PATCH] feat: kcl rust plugin impl and example (#1599) Signed-off-by: peefy --- kclvm/Cargo.lock | 1 + ...vm_evaluator__tests__exec_with_plugin.snap | 5 +++ kclvm/evaluator/src/tests.rs | 45 +++++++++++++++++++ kclvm/runtime/Cargo.toml | 1 + kclvm/runtime/src/api/kclvm.rs | 13 ++++-- kclvm/runtime/src/lib.rs | 2 +- kclvm/runtime/src/stdlib/plugin.rs | 15 +++++++ 7 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 kclvm/evaluator/src/snapshots/kclvm_evaluator__tests__exec_with_plugin.snap diff --git a/kclvm/Cargo.lock b/kclvm/Cargo.lock index 7cf36c42d..581f2a5c3 100644 --- a/kclvm/Cargo.lock +++ b/kclvm/Cargo.lock @@ -1978,6 +1978,7 @@ name = "kclvm-runtime" version = "0.10.0-beta.2" dependencies = [ "ahash", + "anyhow", "base64 0.13.1", "bstr", "chrono", diff --git a/kclvm/evaluator/src/snapshots/kclvm_evaluator__tests__exec_with_plugin.snap b/kclvm/evaluator/src/snapshots/kclvm_evaluator__tests__exec_with_plugin.snap new file mode 100644 index 000000000..7541ff36e --- /dev/null +++ b/kclvm/evaluator/src/snapshots/kclvm_evaluator__tests__exec_with_plugin.snap @@ -0,0 +1,5 @@ +--- +source: evaluator/src/tests.rs +expression: "format!(\"{}\", evaluator.run().unwrap().1)" +--- +sum: 2 diff --git a/kclvm/evaluator/src/tests.rs b/kclvm/evaluator/src/tests.rs index f05faf42a..c61a951d1 100644 --- a/kclvm/evaluator/src/tests.rs +++ b/kclvm/evaluator/src/tests.rs @@ -2,6 +2,7 @@ use crate::Evaluator; use kclvm_ast::MAIN_PKG; use kclvm_loader::{load_packages, LoadPackageOptions}; use kclvm_parser::LoadProgramOptions; +use kclvm_runtime::{Context, ValueRef}; #[macro_export] macro_rules! evaluator_snapshot { @@ -504,6 +505,8 @@ fn test_if_stmt_setters() { assert_eq!(var_setters.len(), 3); } +use std::cell::RefCell; +use std::rc::Rc; use std::sync::Arc; use std::thread; @@ -555,3 +558,45 @@ fn run_code(source: &str) -> (String, String) { let evaluator = Evaluator::new(&p.program); evaluator.run().unwrap() } + +fn testing_sum(_: &Context, args: &ValueRef, _: &ValueRef) -> anyhow::Result { + let a = args + .arg_i_int(0, Some(0)) + .ok_or(anyhow::anyhow!("expect int value for the first param"))?; + let b = args + .arg_i_int(1, Some(0)) + .ok_or(anyhow::anyhow!("expect int value for the second param"))?; + Ok((a + b).into()) +} + +fn context_with_plugin() -> Rc> { + let mut plugin_functions: kclvm_runtime::IndexMap = + Default::default(); + let func = Arc::new(testing_sum); + plugin_functions.insert("testing.add".to_string(), func); + let mut ctx = Context::new(); + ctx.plugin_functions = plugin_functions; + Rc::new(RefCell::new(ctx)) +} + +#[test] +fn test_exec_with_plugin() { + let src = r#" +import kcl_plugin.testing + +sum = testing.add(1, 1) +"#; + let p = load_packages(&LoadPackageOptions { + paths: vec!["test.k".to_string()], + load_opts: Some(LoadProgramOptions { + load_plugins: true, + k_code_list: vec![src.to_string()], + ..Default::default() + }), + load_builtin: false, + ..Default::default() + }) + .unwrap(); + let evaluator = Evaluator::new_with_runtime_ctx(&p.program, context_with_plugin()); + insta::assert_snapshot!(format!("{}", evaluator.run().unwrap().1)); +} diff --git a/kclvm/runtime/Cargo.toml b/kclvm/runtime/Cargo.toml index f5a4d0657..e5c8d7e36 100644 --- a/kclvm/runtime/Cargo.toml +++ b/kclvm/runtime/Cargo.toml @@ -31,6 +31,7 @@ glob = "0.3.0" uuid = { version = "1.7.0", features = ["serde", "v4"] } handlebars = "5.1.2" walkdir = "2.5.0" +anyhow = "1" [[bin]] name = "gen-api-spec" diff --git a/kclvm/runtime/src/api/kclvm.rs b/kclvm/runtime/src/api/kclvm.rs index 4f7779a30..7c101948f 100644 --- a/kclvm/runtime/src/api/kclvm.rs +++ b/kclvm/runtime/src/api/kclvm.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::panic::{RefUnwindSafe, UnwindSafe}; use std::rc::Rc; +use std::sync::Arc; use std::{ cell::RefCell, cmp::Ordering, @@ -329,15 +330,19 @@ impl Default for ContextBuffer { } } -#[derive(PartialEq, Clone, Default, Debug)] +/// Plugin functions +pub type PluginFunction = + Arc anyhow::Result + Send + Sync>; + +#[derive(Clone, Default)] pub struct Context { /// Runtime evaluation config. pub cfg: ContextConfig, - /// kcl.mod path or the pwd path pub module_path: String, /// Program work directory pub workdir: String, + /// Runtime backtrace frame for the debugger. pub backtrace: Vec, /// Imported package path to check the cyclic import process. pub imported_pkgpath: HashSet, @@ -352,7 +357,7 @@ pub struct Context { pub import_names: IndexMap>, /// A buffer to store plugin or hooks function calling results. pub buffer: ContextBuffer, - /// Objects is to store all KCL object pointers at runtime. + /// Objects is to store all KCL object pointers at runtime for GC. pub objects: IndexSet, /// Log message used to store print results. pub log_message: String, @@ -364,6 +369,8 @@ pub struct Context { pub panic_info: PanicInfo, /// Planning options pub plan_opts: PlanOptions, + /// Builtin plugin functions, the key of the map is the form . e.g., `hello.say_hello` + pub plugin_functions: IndexMap, } impl UnwindSafe for Context {} diff --git a/kclvm/runtime/src/lib.rs b/kclvm/runtime/src/lib.rs index c8a6a019c..517a340ab 100644 --- a/kclvm/runtime/src/lib.rs +++ b/kclvm/runtime/src/lib.rs @@ -128,4 +128,4 @@ pub use self::_kclvm::*; pub mod _kclvm_addr; pub use self::_kclvm_addr::*; -type IndexMap = indexmap::IndexMap; +pub type IndexMap = indexmap::IndexMap; diff --git a/kclvm/runtime/src/stdlib/plugin.rs b/kclvm/runtime/src/stdlib/plugin.rs index dc96b1814..081e4962d 100644 --- a/kclvm/runtime/src/stdlib/plugin.rs +++ b/kclvm/runtime/src/stdlib/plugin.rs @@ -20,6 +20,9 @@ lazy_static! { > = Mutex::new(None); } +/// KCL plugin module prefix +pub const PLUGIN_MODULE_PREFIX: &str = "kcl_plugin."; + #[no_mangle] #[runtime_fn] pub extern "C" fn kclvm_plugin_init( @@ -46,6 +49,18 @@ pub unsafe extern "C" fn kclvm_plugin_invoke( args: *const kclvm_value_ref_t, kwargs: *const kclvm_value_ref_t, ) -> *const kclvm_value_ref_t { + let ctx_ref = mut_ptr_as_ref(ctx); + let method_ref = c2str(method); + let plugin_short_method = match method_ref.strip_prefix(PLUGIN_MODULE_PREFIX) { + Some(s) => s, + None => method_ref, + }; + if let Some(func) = ctx_ref.plugin_functions.get(plugin_short_method) { + let args = ptr_as_ref(args); + let kwargs = ptr_as_ref(kwargs); + let result = func(ctx_ref, args, kwargs); + return result.unwrap().into_raw(ctx_ref); + } let args_s = kclvm_value_to_json_value_with_null(ctx, args); let kwargs_s = kclvm_value_to_json_value_with_null(ctx, kwargs);