From b2a89dd0c1a83c0f6ca1d3c50a8f9a03dbe7c50d Mon Sep 17 00:00:00 2001 From: peefy Date: Mon, 20 Nov 2023 18:45:06 +0800 Subject: [PATCH] feat: support print functions for normal logger writer and not only for stdout Signed-off-by: peefy --- VERSION | 2 +- kclvm/api/src/capi_test.rs | 20 ++- kclvm/api/src/service/service_impl.rs | 8 +- .../src/testdata/exec-program-with-print.json | 6 + .../exec-program-with-print.response.json | 6 + kclvm/api/src/testdata/hello_with_print.k | 2 + kclvm/cmd/src/run.rs | 30 +++-- kclvm/cmd/src/tests.rs | 24 +++- kclvm/cmd/src/vet.rs | 3 +- kclvm/runner/src/lib.rs | 126 +++++++----------- kclvm/runner/src/runner.rs | 125 +++++++++++------ kclvm/runner/src/tests.rs | 30 ++++- kclvm/runtime/src/_kcl_run.rs | 44 +++--- kclvm/runtime/src/api/kclvm.rs | 2 + kclvm/runtime/src/stdlib/builtin_api.rs | 9 +- kclvm/spec/gpyrpc/gpyrpc.proto | 4 +- kclvm/src/lib.rs | 2 +- kclvm/tools/src/lint/tests.rs | 10 +- kclvm/tools/src/vet/tests.rs | 10 +- kclvm/tools/src/vet/validator.rs | 17 ++- 20 files changed, 285 insertions(+), 195 deletions(-) create mode 100644 kclvm/api/src/testdata/exec-program-with-print.json create mode 100644 kclvm/api/src/testdata/exec-program-with-print.response.json create mode 100644 kclvm/api/src/testdata/hello_with_print.k diff --git a/VERSION b/VERSION index 27d43f42d..7b63a5e02 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.0-alpha.2 \ No newline at end of file +0.7.0-beta.1 \ No newline at end of file diff --git a/kclvm/api/src/capi_test.rs b/kclvm/api/src/capi_test.rs index 4956b7113..0eba6d6c3 100644 --- a/kclvm/api/src/capi_test.rs +++ b/kclvm/api/src/capi_test.rs @@ -17,7 +17,7 @@ fn test_c_api_call_exec_program() { "KclvmService.ExecProgram", "exec-program.json", "exec-program.response.json", - |res| res.escaped_time = "0".to_owned(), + |_| {}, ); } @@ -27,7 +27,7 @@ fn test_c_api_call_exec_program_with_external_pkg() { "KclvmService.ExecProgram", "exec-program-with-external-pkg.json", "exec-program-with-external-pkg.response.json", - |res| res.escaped_time = "0".to_owned(), + |_| {}, ); } @@ -37,7 +37,7 @@ fn test_c_api_call_exec_program_with_include_schema_type_path() { "KclvmService.ExecProgram", "exec-program-with-include-schema-type-path.json", "exec-program-with-include-schema-type-path.response.json", - |res| res.escaped_time = "0".to_owned(), + |_| {}, ); } @@ -47,7 +47,17 @@ fn test_c_api_call_exec_program_with_path_selector() { "KclvmService.ExecProgram", "exec-program-with-path-selector.json", "exec-program-with-path-selector.response.json", - |res| res.escaped_time = "0".to_owned(), + |_| {}, + ); +} + +#[test] +fn test_c_api_call_exec_program_with_print() { + test_c_api::( + "KclvmService.ExecProgram", + "exec-program-with-print.json", + "exec-program-with-print.response.json", + |_| {}, ); } @@ -111,7 +121,7 @@ fn test_c_api_call_exec_program_with_recursive() { "KclvmService.ExecProgram", "exec-program-with-recursive.json", "exec-program-with-recursive.response.json", - |res| res.escaped_time = "0".to_owned(), + |_| {}, ); } diff --git a/kclvm/api/src/service/service_impl.rs b/kclvm/api/src/service/service_impl.rs index ee5c0ce1b..b08755ab1 100644 --- a/kclvm/api/src/service/service_impl.rs +++ b/kclvm/api/src/service/service_impl.rs @@ -102,12 +102,14 @@ impl KclvmServiceImpl { let result = exec_program( sess, &kclvm_runner::ExecProgramArgs::from_str(args_json.as_str()), - )?; + ) + .map_err(|err| err.to_string())?; Ok(ExecProgramResult { json_result: result.json_result, yaml_result: result.yaml_result, - escaped_time: result.escaped_time, + log_message: result.log_message, + err_message: result.err_message, }) } @@ -415,7 +417,7 @@ impl KclvmServiceImpl { transform_str_para(&args.code), )) { Ok(success) => (success, "".to_string()), - Err(err) => (false, err), + Err(err) => (false, err.to_string()), }; Ok(ValidateCodeResult { success, diff --git a/kclvm/api/src/testdata/exec-program-with-print.json b/kclvm/api/src/testdata/exec-program-with-print.json new file mode 100644 index 000000000..55459c183 --- /dev/null +++ b/kclvm/api/src/testdata/exec-program-with-print.json @@ -0,0 +1,6 @@ +{ + "work_dir" : "./src/testdata", + "k_filename_list":[ + "hello_with_print.k" + ] +} \ No newline at end of file diff --git a/kclvm/api/src/testdata/exec-program-with-print.response.json b/kclvm/api/src/testdata/exec-program-with-print.response.json new file mode 100644 index 000000000..769f8a1a5 --- /dev/null +++ b/kclvm/api/src/testdata/exec-program-with-print.response.json @@ -0,0 +1,6 @@ +{ + "json_result": "[{\"a\": 1}]", + "yaml_result": "a: 1", + "log_message": "Hello world\n", + "err_message": "" +} \ No newline at end of file diff --git a/kclvm/api/src/testdata/hello_with_print.k b/kclvm/api/src/testdata/hello_with_print.k new file mode 100644 index 000000000..5c0f43e6d --- /dev/null +++ b/kclvm/api/src/testdata/hello_with_print.k @@ -0,0 +1,2 @@ +print("Hello world") +a = 1 diff --git a/kclvm/cmd/src/run.rs b/kclvm/cmd/src/run.rs index b66a286a9..9ab1fd469 100644 --- a/kclvm/cmd/src/run.rs +++ b/kclvm/cmd/src/run.rs @@ -15,17 +15,31 @@ pub fn run_command(matches: &ArgMatches, writer: &mut W) -> Result<()> let output = settings.output(); let sess = Arc::new(ParseSession::default()); match exec_program(sess.clone(), &settings.try_into()?) { - Ok(result) => match output { - Some(o) => { - std::fs::write(o, result.yaml_result)?; + Ok(result) => { + // Output log message + if !result.log_message.is_empty() { + write!(writer, "{}", result.log_message)?; } - // [`println!`] is not a good way to output content to stdout, - // using [`writeln`] can be better to redirect the output. - None => writeln!(writer, "{}", result.yaml_result)?, - }, + // Output execute error message + if !result.err_message.is_empty() { + if !sess.0.diag_handler.has_errors()? { + sess.0.add_err(StringError(result.err_message))?; + } + sess.0.emit_stashed_diagnostics_and_abort()?; + } + if !result.yaml_result.is_empty() { + match output { + Some(o) => std::fs::write(o, result.yaml_result)?, + // [`println!`] is not a good way to output content to stdout, + // using [`writeln`] can be better to redirect the output. + None => writeln!(writer, "{}", result.yaml_result)?, + } + } + } + // Other error message Err(msg) => { if !sess.0.diag_handler.has_errors()? { - sess.0.add_err(StringError(msg))?; + sess.0.add_err(StringError(msg.to_string()))?; } sess.0.emit_stashed_diagnostics_and_abort()?; } diff --git a/kclvm/cmd/src/tests.rs b/kclvm/cmd/src/tests.rs index 167a6680f..2348e62f0 100644 --- a/kclvm/cmd/src/tests.rs +++ b/kclvm/cmd/src/tests.rs @@ -7,7 +7,7 @@ use std::{ use kclvm_config::modfile::KCL_PKG_PATH; use kclvm_parser::ParseSession; -use kclvm_runner::exec_program; +use kclvm_runner::{exec_program, MapErrorResult}; use crate::{ app, @@ -522,7 +522,10 @@ fn test_main_pkg_not_found() { ]); let settings = must_build_settings(matches.subcommand_matches("run").unwrap()); let sess = Arc::new(ParseSession::default()); - match exec_program(sess.clone(), &settings.try_into().unwrap()) { + match exec_program(sess.clone(), &settings.try_into().unwrap()) + .map_err_to_result() + .map_err(|e| e.to_string()) + { Ok(_) => panic!("unreachable code."), Err(msg) => assert_eq!( msg, @@ -563,7 +566,7 @@ fn test_plugin_not_found() { ]); let settings = must_build_settings(matches.subcommand_matches("run").unwrap()); let sess = Arc::new(ParseSession::default()); - match exec_program(sess.clone(), &settings.try_into().unwrap()) { + match exec_program(sess.clone(), &settings.try_into().unwrap()).map_err_to_result().map_err(|e|e.to_string()) { Ok(_) => panic!("unreachable code."), Err(msg) => assert!(msg.contains("the plugin package `kcl_plugin.not_exist` is not found, please confirm if plugin mode is enabled")), } @@ -578,7 +581,10 @@ fn test_error_message_fuzz_matched() { ]); let settings = must_build_settings(matches.subcommand_matches("run").unwrap()); let sess = Arc::new(ParseSession::default()); - match exec_program(sess.clone(), &settings.try_into().unwrap()) { + match exec_program(sess.clone(), &settings.try_into().unwrap()) + .map_err_to_result() + .map_err(|e| e.to_string()) + { Ok(_) => panic!("unreachable code."), Err(msg) => { assert!(msg @@ -596,7 +602,10 @@ fn test_error_message_fuzz_unmatched() { ]); let settings = must_build_settings(matches.subcommand_matches("run").unwrap()); let sess = Arc::new(ParseSession::default()); - match exec_program(sess.clone(), &settings.try_into().unwrap()) { + match exec_program(sess.clone(), &settings.try_into().unwrap()) + .map_err_to_result() + .map_err(|e| e.to_string()) + { Ok(_) => panic!("unreachable code."), Err(msg) => { assert!(msg.contains("attribute 'a' not found in schema 'Person'")) @@ -613,7 +622,10 @@ fn test_keyword_argument_error_message() { ]); let settings = must_build_settings(matches.subcommand_matches("run").unwrap()); let sess = Arc::new(ParseSession::default()); - match exec_program(sess.clone(), &settings.try_into().unwrap()) { + match exec_program(sess.clone(), &settings.try_into().unwrap()) + .map_err_to_result() + .map_err(|e| e.to_string()) + { Ok(_) => panic!("unreachable code."), Err(msg) => { assert!(msg.contains("keyword argument 'ID' not found")); diff --git a/kclvm/cmd/src/vet.rs b/kclvm/cmd/src/vet.rs index 86a1d2950..c2c1e355a 100644 --- a/kclvm/cmd/src/vet.rs +++ b/kclvm/cmd/src/vet.rs @@ -30,8 +30,7 @@ pub fn vet_command(matches: &ArgMatches) -> Result<()> { Some(kcl_file.to_string()), None, )) - .map_err(|err| anyhow::anyhow!(err))?; - Ok(()) + .map(|_| ()) } _ => Err(anyhow::anyhow!("No input data file or kcl file")), } diff --git a/kclvm/runner/src/lib.rs b/kclvm/runner/src/lib.rs index abe10fab8..0e7aeaab1 100644 --- a/kclvm/runner/src/lib.rs +++ b/kclvm/runner/src/lib.rs @@ -1,6 +1,6 @@ -use std::{collections::HashMap, path::Path, sync::Arc, time::SystemTime}; +use std::{collections::HashMap, path::Path, sync::Arc}; -use anyhow::Result; +use anyhow::{anyhow, bail, Result}; use assembler::KclvmLibAssembler; use kclvm_ast::{ ast::{Module, Program}, @@ -15,8 +15,8 @@ use kclvm_sema::resolver::{ resolve_program, resolve_program_with_opts, scope::ProgramScope, Options, }; use linker::Command; -pub use runner::ExecProgramArgs; -use runner::{ExecProgramResult, KclvmRunner, KclvmRunnerOptions}; +pub use runner::{ExecProgramArgs, ExecProgramResult, MapErrorResult}; +use runner::{KclLibRunner, KclLibRunnerOptions}; use tempfile::tempdir; pub mod assembler; @@ -50,7 +50,7 @@ pub mod tests; /// After linking all dynamic link libraries by KclvmLinker, method "KclvmLinker::link_all_libs" will return a path /// for dynamic link library after linking. /// -/// At last, KclvmRunner will be constructed and call method "run" to execute the kcl program. +/// At last, KclLibRunner will be constructed and call method "run" to execute the kcl program. /// /// **Note that it is not thread safe.** /// @@ -71,64 +71,37 @@ pub mod tests; /// // Result is the kcl in json format. /// let result = exec_program(sess, &args).unwrap(); /// ``` -pub fn exec_program( - sess: Arc, - args: &ExecProgramArgs, -) -> Result { +pub fn exec_program(sess: Arc, args: &ExecProgramArgs) -> Result { // parse args from json string let opts = args.get_load_program_options(); let k_files = &args.k_filename_list; let work_dir = args.work_dir.clone().unwrap_or_default(); let k_files = expand_input_files(k_files); - let kcl_paths = canonicalize_input_files(&k_files, work_dir, false)?; + let kcl_paths = + canonicalize_input_files(&k_files, work_dir, false).map_err(|err| anyhow!(err))?; let kcl_paths_str = kcl_paths.iter().map(|s| s.as_str()).collect::>(); - let mut program = load_program(sess.clone(), kcl_paths_str.as_slice(), Some(opts), None)?; + let mut program = load_program(sess.clone(), kcl_paths_str.as_slice(), Some(opts), None) + .map_err(|err| anyhow!(err))?; - if let Err(err) = apply_overrides( + apply_overrides( &mut program, &args.overrides, &[], args.print_override_ast || args.debug > 0, - ) { - return Err(err.to_string()); + )?; + let mut result = execute(sess, program, args)?; + // If it is a empty result, return it directly + if result.json_result.is_empty() { + return Ok(result); } - - let start_time = SystemTime::now(); - let exec_result = execute(sess, program, args); - let escape_time = match SystemTime::now().duration_since(start_time) { - Ok(dur) => dur.as_secs_f32(), - Err(err) => return Err(err.to_string()), - }; - let mut result = ExecProgramResult { - escaped_time: escape_time.to_string(), - ..Default::default() - }; - // Exec result is a JSON or YAML string. - let exec_result = match exec_result { - Ok(res) => { - if res.is_empty() { - return Ok(result); - } else { - res - } - } - Err(res) => { - if res.is_empty() { - return Ok(result); - } else { - return Err(res); - } - } - }; - let mut ctx = Context::new(); - let kcl_val = match ValueRef::from_yaml_stream(&mut ctx, &exec_result) { - Ok(v) => v, - Err(err) => return Err(err.to_string()), - }; // Filter values with the path selector. - let kcl_val = kcl_val.filter_by_path(&args.path_selector)?; + let mut ctx = Context::new(); + let kcl_val = ValueRef::from_yaml_stream(&mut ctx, &result.json_result)?; + let kcl_val = kcl_val + .filter_by_path(&args.path_selector) + .map_err(|err| anyhow!(err))?; // Plan values. let (json_result, yaml_result) = kcl_val.plan( &mut ctx, @@ -168,7 +141,7 @@ pub fn exec_program( /// After linking all dynamic link libraries by KclvmLinker, method "KclvmLinker::link_all_libs" will return a path /// for dynamic link library after linking. /// -/// At last, KclvmRunner will be constructed and call method "run" to execute the kcl program. +/// At last, KclLibRunner will be constructed and call method "run" to execute the kcl program. /// /// **Note that it is not thread safe.** /// @@ -192,13 +165,13 @@ pub fn exec_program( /// /// // Resolve ast, generate libs, link libs and execute. /// // Result is the kcl in json format. -/// let result = execute(sess, prog, &args).unwrap(); +/// let (result, _) = execute(sess, prog, &args); /// ``` pub fn execute( sess: Arc, mut program: Program, args: &ExecProgramArgs, -) -> Result { +) -> Result { // If the user only wants to compile the kcl program, the following code will only resolve ast. if args.compile_only { let mut resolve_opts = Options::default(); @@ -206,19 +179,19 @@ pub fn execute( // Resolve ast let scope = resolve_program_with_opts(&mut program, resolve_opts, None); emit_compile_diag_to_string(sess, &scope, args.compile_only)?; - return Ok("".to_string()); + return Ok(ExecProgramResult::default()); } // Resolve ast let scope = resolve_program(&mut program); emit_compile_diag_to_string(sess, &scope, false)?; // Create a temp entry file and the temp dir will be delete automatically - let temp_dir = tempdir().map_err(|e| e.to_string())?; - let temp_dir_path = temp_dir.path().to_str().ok_or(format!( + let temp_dir = tempdir()?; + let temp_dir_path = temp_dir.path().to_str().ok_or(anyhow!( "Internal error: {}: No such file or directory", temp_dir.path().display() ))?; - let temp_entry_file = temp_file(temp_dir_path).map_err(|e| e.to_string())?; + let temp_entry_file = temp_file(temp_dir_path)?; // Generate libs let lib_paths = assembler::KclvmAssembler::new( @@ -228,33 +201,34 @@ pub fn execute( KclvmLibAssembler::LLVM, args.get_package_maps_from_external_pkg(), ) - .gen_libs() - .map_err(|e| e.to_string())?; + .gen_libs()?; - // Link libs + // Link libs into one library let lib_suffix = Command::get_lib_suffix(); let temp_out_lib_file = format!("{}{}", temp_entry_file, lib_suffix); - let lib_path = linker::KclvmLinker::link_all_libs(lib_paths, temp_out_lib_file) - .map_err(|e| e.to_string())?; + let lib_path = linker::KclvmLinker::link_all_libs(lib_paths, temp_out_lib_file)?; - // Run - let runner = KclvmRunner::new(Some(KclvmRunnerOptions { + // Run the library + let runner = KclLibRunner::new(Some(KclLibRunnerOptions { plugin_agent_ptr: args.plugin_agent, })); - let result = runner.run(&lib_path, args); + let mut result = runner.run(&lib_path, args)?; - remove_file(&lib_path).map_err(|e| e.to_string())?; - clean_tmp_files(&temp_entry_file, &lib_suffix).map_err(|e| e.to_string())?; + remove_file(&lib_path)?; + clean_tmp_files(&temp_entry_file, &lib_suffix)?; // Wrap runtime error into diagnostic style string. - result.map_err(|err| { - match Handler::default() - .add_diagnostic(>::into(PanicInfo::from(err))) + if !result.err_message.is_empty() { + result.err_message = match Handler::default() + .add_diagnostic(>::into(PanicInfo::from( + result.err_message.as_str(), + ))) .emit_to_string() { Ok(msg) => msg, Err(err) => err.to_string(), - } - }) + }; + } + Ok(result) } /// `execute_module` can directly execute the ast `Module`. @@ -263,7 +237,7 @@ pub fn execute( /// For more information, see doc above method `execute`. /// /// **Note that it is not thread safe.** -pub fn execute_module(mut m: Module) -> Result { +pub fn execute_module(mut m: Module) -> Result { m.pkg = MAIN_PKG.to_string(); let mut pkgs = HashMap::new(); @@ -315,12 +289,8 @@ fn emit_compile_diag_to_string( sess: Arc, scope: &ProgramScope, include_warnings: bool, -) -> Result<(), String> { - let mut res_str = sess - .1 - .borrow_mut() - .emit_to_string() - .map_err(|err| err.to_string())?; +) -> Result<()> { + let mut res_str = sess.1.borrow_mut().emit_to_string()?; let sema_err = scope.emit_diagnostics_to_string(sess.0.clone(), include_warnings); if sema_err.is_err() { #[cfg(not(target_os = "windows"))] @@ -333,5 +303,5 @@ fn emit_compile_diag_to_string( res_str .is_empty() .then(|| Ok(())) - .unwrap_or_else(|| Err(res_str)) + .unwrap_or_else(|| bail!(res_str)) } diff --git a/kclvm/runner/src/runner.rs b/kclvm/runner/src/runner.rs index e19909283..ed8bdcd8d 100644 --- a/kclvm/runner/src/runner.rs +++ b/kclvm/runner/src/runner.rs @@ -1,3 +1,4 @@ +use anyhow::{anyhow, Result}; use std::collections::HashMap; use kclvm_ast::ast; @@ -85,8 +86,42 @@ impl ExecProgramArgs { pub struct ExecProgramResult { pub json_result: String, pub yaml_result: String, + pub log_message: String, + pub err_message: String, +} - pub escaped_time: String, +pub trait MapErrorResult { + /// Map execute error message into the [`Result::Err`] + fn map_err_to_result(self) -> Result + where + Self: Sized; +} + +impl MapErrorResult for ExecProgramResult { + /// Map execute error message into the [`Result::Err`] + fn map_err_to_result(self) -> Result + where + Self: Sized, + { + if self.err_message.is_empty() { + Ok(self) + } else { + Err(anyhow!(self.err_message)) + } + } +} + +impl MapErrorResult for Result { + /// Map execute error message into the [`Result::Err`] + fn map_err_to_result(self) -> Result + where + Self: Sized, + { + match self { + Ok(result) => result.map_err_to_result(), + Err(err) => Err(err), + } + } } impl ExecProgramArgs { @@ -172,42 +207,37 @@ impl TryFrom for ExecProgramArgs { } #[derive(Debug, Default)] -pub struct KclvmRunnerOptions { +pub struct KclLibRunnerOptions { pub plugin_agent_ptr: u64, } -pub struct KclvmRunner { - opts: KclvmRunnerOptions, +pub struct KclLibRunner { + opts: KclLibRunnerOptions, } -impl KclvmRunner { +impl KclLibRunner { /// New a runner using the lib path and options. - pub fn new(opts: Option) -> Self { + pub fn new(opts: Option) -> Self { Self { opts: opts.unwrap_or_default(), } } /// Run kcl library with exec arguments. - pub fn run(&self, lib_path: &str, args: &ExecProgramArgs) -> Result { + pub fn run(&self, lib_path: &str, args: &ExecProgramArgs) -> Result { unsafe { - let lib = libloading::Library::new( - std::path::PathBuf::from(lib_path) - .canonicalize() - .map_err(|e| e.to_string())?, - ) - .map_err(|e| e.to_string())?; + let lib = libloading::Library::new(std::path::PathBuf::from(lib_path).canonicalize()?)?; Self::lib_kclvm_plugin_init(&lib, self.opts.plugin_agent_ptr)?; Self::lib_kcl_run(&lib, args) } } } -impl KclvmRunner { +impl KclLibRunner { unsafe fn lib_kclvm_plugin_init( lib: &libloading::Library, plugin_method_ptr: u64, - ) -> Result<(), String> { + ) -> Result<()> { // get kclvm_plugin_init let kclvm_plugin_init: libloading::Symbol< unsafe extern "C" fn( @@ -217,7 +247,7 @@ impl KclvmRunner { kwargs_json: *const i8, ) -> *const i8, ), - > = lib.get(b"kclvm_plugin_init").map_err(|e| e.to_string())?; + > = lib.get(b"kclvm_plugin_init")?; // get plugin_method let plugin_method_ptr = plugin_method_ptr; @@ -241,7 +271,7 @@ impl KclvmRunner { unsafe fn lib_kcl_run( lib: &libloading::Library, args: &ExecProgramArgs, - ) -> Result { + ) -> Result { let kcl_run: libloading::Symbol< unsafe extern "C" fn( kclvm_main_ptr: u64, // main.k => kclvm_main @@ -253,17 +283,19 @@ impl KclvmRunner { disable_schema_check: i32, list_option_mode: i32, debug_mode: i32, - result_buffer_len: kclvm_size_t, + result_buffer_len: *mut kclvm_size_t, result_buffer: *mut kclvm_char_t, - warn_buffer_len: kclvm_size_t, + warn_buffer_len: *mut kclvm_size_t, warn_buffer: *mut kclvm_char_t, + log_buffer_len: *mut kclvm_size_t, + log_buffer: *mut kclvm_char_t, ) -> kclvm_size_t, - > = lib.get(b"_kcl_run").map_err(|e| e.to_string())?; + > = lib.get(b"_kcl_run")?; - let kclvm_main: libloading::Symbol = - lib.get(b"kclvm_main").map_err(|e| e.to_string())?; + let kclvm_main: libloading::Symbol = lib.get(b"kclvm_main")?; let kclvm_main_ptr = kclvm_main.into_raw().into_raw() as u64; + // CLI configs let option_len = args.args.len() as kclvm_size_t; let cstr_argv: Vec<_> = args @@ -295,21 +327,27 @@ impl KclvmRunner { let p: *const *const kclvm_char_t = p_argv.as_ptr(); let option_values = p; - let strict_range_check = args.strict_range_check as i32; let disable_none = args.disable_none as i32; let disable_schema_check = 0; // todo let list_option_mode = 0; // todo let debug_mode = args.debug; - let mut result = vec![0u8; RESULT_SIZE]; - let result_buffer_len = result.len() as i32 - 1; - let result_buffer = result.as_mut_ptr() as *mut i8; + // Exec json result + let mut json_result = vec![0u8; RESULT_SIZE]; + let mut result_buffer_len = json_result.len() as i32 - 1; + let json_result_buffer = json_result.as_mut_ptr() as *mut i8; + // Exec warning data let mut warn_data = vec![0u8; RESULT_SIZE]; - let warn_buffer_len = warn_data.len() as i32 - 1; + let mut warn_buffer_len = warn_data.len() as i32 - 1; let warn_buffer = warn_data.as_mut_ptr() as *mut i8; + // Exec log data + let mut log_data = vec![0u8; RESULT_SIZE]; + let mut log_buffer_len = log_data.len() as i32 - 1; + let log_buffer = log_data.as_mut_ptr() as *mut i8; + let n = kcl_run( kclvm_main_ptr, option_len, @@ -320,25 +358,28 @@ impl KclvmRunner { disable_schema_check, list_option_mode, debug_mode, - result_buffer_len, - result_buffer, - warn_buffer_len, + &mut result_buffer_len, + json_result_buffer, + &mut warn_buffer_len, warn_buffer, + &mut log_buffer_len, + log_buffer, ); - - if n == 0 { - Ok("".to_string()) - } else if n > 0 { - let return_len = n; - let s = - std::str::from_utf8(&result[0..return_len as usize]).map_err(|e| e.to_string())?; - wrap_msg_in_result(s) - } else { + let mut result = ExecProgramResult { + log_message: String::from_utf8(log_data[0..log_buffer_len as usize].to_vec())?, + ..Default::default() + }; + if n > 0 { + let s = std::str::from_utf8(&json_result[0..n as usize])?; + match wrap_msg_in_result(s) { + Ok(json) => result.json_result = json, + Err(err) => result.err_message = err, + } + } else if n < 0 { let return_len = 0 - n; - let s = std::str::from_utf8(&warn_data[0..return_len as usize]) - .map_err(|e| e.to_string())?; - Err(s.to_string()) + result.err_message = String::from_utf8(warn_data[0..return_len as usize].to_vec())?; } + Ok(result) } } diff --git a/kclvm/runner/src/tests.rs b/kclvm/runner/src/tests.rs index 98512a657..bb58546ce 100644 --- a/kclvm/runner/src/tests.rs +++ b/kclvm/runner/src/tests.rs @@ -208,7 +208,9 @@ fn execute_for_test(kcl_path: &String) -> String { // Parse kcl file let program = load_test_program(kcl_path.to_string()); // Generate libs, link libs and execute. - execute(Arc::new(ParseSession::default()), program, &args).unwrap() + execute(Arc::new(ParseSession::default()), program, &args) + .unwrap() + .json_result } fn gen_assembler(entry_file: &str, test_kcl_case_path: &str) -> KclvmAssembler { @@ -524,7 +526,7 @@ fn test_compile_dir_recursive() { // Resolve ATS, generate libs, link libs and execute. let res = execute(sess, program, &args); assert!(res.is_ok()); - assert_eq!(res.unwrap(), "{\"k1\": \"Hello k1!\", \"k2\": \"Hello k2!\", \"The_first_kcl_program\": \"Hello World!\"}"); + assert_eq!(res.unwrap().json_result, "{\"k1\": \"Hello k1!\", \"k2\": \"Hello k2!\", \"The_first_kcl_program\": \"Hello World!\"}"); } #[test] @@ -581,7 +583,7 @@ fn test_indent_error() { assert!(res.is_err()); if let Err(err_msg) = res { let expect_err = fs::read_to_string(err_file).expect("Failed to read file"); - assert!(err_msg.contains(&expect_err)); + assert!(err_msg.to_string().contains(&expect_err)); } } } @@ -594,7 +596,16 @@ fn exec(file: &str) -> Result { // Load AST program let program = load_program(sess.clone(), &[file], Some(opts), None).unwrap(); // Resolve ATS, generate libs, link libs and execute. - execute(sess, program, &args) + match execute(sess, program, &args) { + Ok(result) => { + if result.err_message.is_empty() { + Ok(result.json_result) + } else { + Err(result.err_message) + } + } + Err(err) => Err(err.to_string()), + } } /// Run all kcl files at path and compare the exec result with the expect output. @@ -636,7 +647,12 @@ fn exec_with_err_result_at(path: &str) { for (kcl_file, _) in kcl_files.iter().zip(&output_files) { let mut args = ExecProgramArgs::default(); args.k_filename_list.push(kcl_file.to_string()); - assert!(exec_program(Arc::new(ParseSession::default()), &args).is_err()); + let result = exec_program(Arc::new(ParseSession::default()), &args); + if let Ok(result) = result { + assert!(!result.err_message.is_empty(), "{}", result.err_message); + } else { + assert!(result.is_err()); + } } }); assert!(result.is_ok()); @@ -673,11 +689,11 @@ fn test_compile_with_file_pattern() { let res = exec_program(Arc::new(ParseSession::default()), &args); assert!(res.is_ok()); assert_eq!( - res.clone().unwrap().yaml_result, + res.as_ref().unwrap().yaml_result, "k3: Hello World!\nk1: Hello World!\nk2: Hello World!" ); assert_eq!( - res.unwrap().json_result, + res.as_ref().unwrap().json_result, "[{\"k3\": \"Hello World!\", \"k1\": \"Hello World!\", \"k2\": \"Hello World!\"}]" ); } diff --git a/kclvm/runtime/src/_kcl_run.rs b/kclvm/runtime/src/_kcl_run.rs index cfd32d0de..91a449a40 100644 --- a/kclvm/runtime/src/_kcl_run.rs +++ b/kclvm/runtime/src/_kcl_run.rs @@ -58,10 +58,12 @@ pub unsafe extern "C" fn _kcl_run( disable_schema_check: i32, list_option_mode: i32, debug_mode: i32, - result_buffer_len: kclvm_size_t, + result_buffer_len: *mut kclvm_size_t, result_buffer: *mut kclvm_char_t, - warn_buffer_len: kclvm_size_t, + warn_buffer_len: *mut kclvm_size_t, warn_buffer: *mut kclvm_char_t, + log_buffer_len: *mut kclvm_size_t, + log_buffer: *mut kclvm_char_t, ) -> kclvm_size_t { let ctx = kclvm_context_new(); @@ -110,35 +112,36 @@ pub unsafe extern "C" fn _kcl_run( let ctx = mut_ptr_as_ref(ctx); ctx.set_panic_info(&record); }); + // Get the runtime context. + let ctx_ref = ptr_as_ref(ctx); + // Copy log message pointer + let c_str_ptr = ctx_ref.log_message.as_ptr() as *const i8; + let c_str_len = ctx_ref.log_message.len() as i32; + if c_str_len <= *log_buffer_len { + std::ptr::copy(c_str_ptr, log_buffer, c_str_len as usize); + *log_buffer_len = c_str_len + } + // Copy panic info message pointer + let json_panic_info = ctx_ref.get_panic_info_json_string(); + let c_str_ptr = json_panic_info.as_ptr() as *const i8; + let c_str_len = json_panic_info.len() as i32; match result { Ok(n) => { - let ctx_ref = ptr_as_ref(ctx); - let json_panic_info = ctx_ref.get_panic_info_json_string(); - - let c_str_ptr = json_panic_info.as_ptr() as *const i8; - let c_str_len = json_panic_info.len() as i32; - unsafe { - if c_str_len <= warn_buffer_len { + if c_str_len <= *warn_buffer_len { std::ptr::copy(c_str_ptr, warn_buffer, c_str_len as usize); + *warn_buffer_len = c_str_len } } - kclvm_context_delete(ctx); n } Err(_) => { - let ctx_ref = ptr_as_ref(ctx); - let json_panic_info = ctx_ref.get_panic_info_json_string(); - - let c_str_ptr = json_panic_info.as_ptr() as *const i8; - let c_str_len = json_panic_info.len() as i32; - let mut return_len = c_str_len; - unsafe { - if return_len <= result_buffer_len { + if return_len <= *result_buffer_len { std::ptr::copy(c_str_ptr, result_buffer, return_len as usize); + *result_buffer_len = return_len } else { *result_buffer = '\0' as kclvm_char_t; return_len = 0 - return_len; @@ -163,7 +166,7 @@ unsafe fn _kcl_run_in_closure( disable_schema_check: i32, list_option_mode: i32, debug_mode: i32, - result_buffer_len: kclvm_size_t, + result_buffer_len: *mut kclvm_size_t, result_buffer: *mut kclvm_char_t, ) -> kclvm_size_t { let kclvm_main = (&kclvm_main_ptr as *const u64) as *const () @@ -196,8 +199,9 @@ unsafe fn _kcl_run_in_closure( let mut return_len = c_str_len; - if return_len <= result_buffer_len { + if return_len <= *result_buffer_len { std::ptr::copy(c_str_ptr, result_buffer, return_len as usize); + *result_buffer_len = return_len; } else { *result_buffer = '\0' as kclvm_char_t; return_len = 0 - return_len; diff --git a/kclvm/runtime/src/api/kclvm.rs b/kclvm/runtime/src/api/kclvm.rs index a99b132f6..cf9f4261c 100644 --- a/kclvm/runtime/src/api/kclvm.rs +++ b/kclvm/runtime/src/api/kclvm.rs @@ -388,6 +388,8 @@ pub struct Context { pub buffer: ContextBuffer, /// objects is to store all KCL object pointers. pub objects: IndexSet, + /// Log message used to store print results. + pub log_message: String, } impl UnwindSafe for Context {} diff --git a/kclvm/runtime/src/stdlib/builtin_api.rs b/kclvm/runtime/src/stdlib/builtin_api.rs index 84439904c..befc9dc57 100644 --- a/kclvm/runtime/src/stdlib/builtin_api.rs +++ b/kclvm/runtime/src/stdlib/builtin_api.rs @@ -258,18 +258,17 @@ pub unsafe extern "C" fn kclvm_builtin_print( ) -> *mut kclvm_value_ref_t { let args = ptr_as_ref(args); let kwargs = ptr_as_ref(kwargs); + let ctx_ref = mut_ptr_as_ref(ctx); // args let list = args.as_list_ref(); let values: Vec = list.values.iter().map(|v| v.to_string()).collect(); - print!("{}", values.join(" ")); + ctx_ref.log_message.push_str(&values.join(" ")); let dict = kwargs.as_dict_ref(); // kwargs: end if let Some(c) = dict.values.get("end") { - print!("{c}"); - use std::io::Write; - let _ = std::io::stdout().flush(); + ctx_ref.log_message.push_str(&format!("{c}")); } else { - println!(); + ctx_ref.log_message.push('\n'); } kclvm_value_None(ctx) } diff --git a/kclvm/spec/gpyrpc/gpyrpc.proto b/kclvm/spec/gpyrpc/gpyrpc.proto index 12c94ddd6..1733de9d6 100644 --- a/kclvm/spec/gpyrpc/gpyrpc.proto +++ b/kclvm/spec/gpyrpc/gpyrpc.proto @@ -166,8 +166,8 @@ message ExecProgram_Args { message ExecProgram_Result { string json_result = 1; string yaml_result = 2; - - string escaped_time = 101; + string log_message = 3; + string err_message = 4; } message ResetPlugin_Args { diff --git a/kclvm/src/lib.rs b/kclvm/src/lib.rs index 74949d229..fe1753861 100644 --- a/kclvm/src/lib.rs +++ b/kclvm/src/lib.rs @@ -57,7 +57,7 @@ fn kclvm_cli_run_unsafe(args: *const i8, plugin_agent: *const i8) -> Result>() + ); for (diag, m) in errors.iter().zip(msgs.iter()) { assert_eq!(diag.messages[0].message, m.to_string()); } diff --git a/kclvm/tools/src/vet/tests.rs b/kclvm/tools/src/vet/tests.rs index 3d89865b1..14ab61f86 100644 --- a/kclvm/tools/src/vet/tests.rs +++ b/kclvm/tools/src/vet/tests.rs @@ -437,7 +437,7 @@ mod test_validater { ); let result = validate(opt).unwrap_err(); - assert!(result.contains("Error"), "{result}"); + assert!(result.to_string().contains("Error"), "{result}"); } } } @@ -461,7 +461,7 @@ mod test_validater { r"^Failed to load KCL file 'validationTempKCLCode.k'. Because .*" ) .unwrap() - .is_match(&err)) + .is_match(&err.to_string())) } } } @@ -475,7 +475,7 @@ mod test_validater { let opt = ValidateOption::new( None, "value".to_string(), - "The validated file path is invalid".to_string(), + "invalid/file/path".to_string(), LoaderKind::JSON, None, Some(kcl_code), @@ -486,7 +486,7 @@ mod test_validater { panic!("unreachable") } Err(err) => { - assert_eq!(err, "Failed to load validated file.") + assert_eq!(err.to_string(), "Failed to Load 'invalid/file/path'") } } } @@ -514,7 +514,7 @@ mod test_validater { panic!("unreachable") } Err(err) => { - assert_eq!(err, "Failed to load validated file.") + assert_eq!(err.to_string(), "Failed to Load JSON") } } } diff --git a/kclvm/tools/src/vet/validator.rs b/kclvm/tools/src/vet/validator.rs index 668b07ccf..6deb8f4d3 100644 --- a/kclvm/tools/src/vet/validator.rs +++ b/kclvm/tools/src/vet/validator.rs @@ -66,11 +66,12 @@ //! ``` use super::expr_builder::ExprBuilder; pub use crate::util::loader::LoaderKind; +use anyhow::{anyhow, Result}; use kclvm_ast::{ ast::{AssignStmt, Expr, ExprContext, Identifier, Module, Node, NodeRef, SchemaStmt, Stmt}, node_ref, }; -use kclvm_runner::execute_module; +use kclvm_runner::{execute_module, MapErrorResult}; const TMP_FILE: &str = "validationTempKCLCode.k"; @@ -170,13 +171,14 @@ const TMP_FILE: &str = "validationTempKCLCode.k"; /// "is_warning": false /// } /// ``` -pub fn validate(val_opt: ValidateOption) -> Result { +pub fn validate(val_opt: ValidateOption) -> Result { let k_path = match val_opt.kcl_path { Some(path) => path, None => TMP_FILE.to_string(), }; - let mut module = kclvm_parser::parse_file(&k_path, val_opt.kcl_code)?; + let mut module = + kclvm_parser::parse_file(&k_path, val_opt.kcl_code).map_err(|err| anyhow!(err))?; let schemas = filter_schema_stmt(&module); let schema_name = match val_opt.schema_name { @@ -185,18 +187,15 @@ pub fn validate(val_opt: ValidateOption) -> Result { }; let expr_builder = - ExprBuilder::new_with_file_path(val_opt.validated_file_kind, val_opt.validated_file_path) - .map_err(|_| "Failed to load validated file.".to_string())?; + ExprBuilder::new_with_file_path(val_opt.validated_file_kind, val_opt.validated_file_path)?; - let validated_expr = expr_builder - .build(schema_name) - .map_err(|_| "Failed to load validated file.".to_string())?; + let validated_expr = expr_builder.build(schema_name)?; let assign_stmt = build_assign(&val_opt.attribute_name, validated_expr); module.body.insert(0, assign_stmt); - execute_module(module).map(|_| true) + execute_module(module).map_err_to_result().map(|_| true) } fn build_assign(attr_name: &str, node: NodeRef) -> NodeRef {