diff --git a/kclvm/compiler_base/3rdparty/rustc_span/src/lib.rs b/kclvm/compiler_base/3rdparty/rustc_span/src/lib.rs index 83d6f1a24..548bafb36 100644 --- a/kclvm/compiler_base/3rdparty/rustc_span/src/lib.rs +++ b/kclvm/compiler_base/3rdparty/rustc_span/src/lib.rs @@ -14,7 +14,7 @@ //! This API is completely unstable and subject to change. mod caching_source_map_view; -mod fatal_error; +pub mod fatal_error; pub mod source_map; pub use self::caching_source_map_view::CachingSourceMapView; use rustc_data_structures::sync::Lrc; diff --git a/kclvm/compiler_base/error/Cargo.toml b/kclvm/compiler_base/error/Cargo.toml index f7ec91b00..c845f0d25 100644 --- a/kclvm/compiler_base/error/Cargo.toml +++ b/kclvm/compiler_base/error/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +compiler_base_span = {path = "../span", version = "0.1.0"} compiler_base_macros = {path = "../macros", version = "0.1.0"} rustc_errors = {path="../3rdparty/rustc_errors", version="0.1.0"} unic-langid = {version="0.9.0", features = ["macros"]} diff --git a/kclvm/compiler_base/error/src/diagnostic/components.rs b/kclvm/compiler_base/error/src/diagnostic/components.rs index 1f635285e..d64796639 100644 --- a/kclvm/compiler_base/error/src/diagnostic/components.rs +++ b/kclvm/compiler_base/error/src/diagnostic/components.rs @@ -8,9 +8,9 @@ use rustc_errors::styled_buffer::StyledBuffer; /// # Examples /// /// ```rust -/// # use crate::compiler_base_error::diagnostic::Component; -/// # use compiler_base_error::diagnostic::components::Label; -/// # use compiler_base_error::diagnostic::style::DiagnosticStyle; +/// # use compiler_base_error::Component; +/// # use compiler_base_error::components::Label; +/// # use compiler_base_error::DiagnosticStyle; /// # use rustc_errors::styled_buffer::StyledBuffer; /// /// let mut sb = StyledBuffer::::new(); diff --git a/kclvm/compiler_base/error/src/diagnostic/diagnostic_handler.rs b/kclvm/compiler_base/error/src/diagnostic/diagnostic_handler.rs new file mode 100644 index 000000000..09a191845 --- /dev/null +++ b/kclvm/compiler_base/error/src/diagnostic/diagnostic_handler.rs @@ -0,0 +1,612 @@ +//! This crate provides `DiagnosticHandler` supports diagnostic messages to terminal stderr. +//! +//! `DiagnosticHandler` mainly consists of 4 parts: +//! - Emitter: Emit the styled string to terminal stderr. +//! - Template Loader: Load template files locally and find messages from file contents. +//! - A set for Diagnostics: All the diagnostic messages. +//! +//! For more information about diagnostic, see doc in "compiler_base/error/diagnostic/mod.rs". +//! For more information about emitter, see doc in "compiler_base/error/src/emitter.rs". +//! For more information about template loader, see doc in "compiler_base/error/src/diagnostic/diagnostic_message.rs". + +use crate::{ + diagnostic::diagnostic_message::TemplateLoader, Diagnostic, DiagnosticStyle, Emitter, + TerminalEmitter, +}; +use anyhow::{bail, Context, Result}; +use compiler_base_span::fatal_error::FatalError; +use fluent::FluentArgs; +use std::sync::{Arc, Mutex}; + +// Default template resource file path. +const DEFAULT_TEMPLATE_RESOURCE: &'static str = "./src/diagnostic/locales/en-US/"; + +/// `DiagnosticHandler` supports diagnostic messages to terminal stderr. +/// +/// `DiagnosticHandler` will load template file(*ftl) directory when instantiating through the constructor `new_with_template_dir()`. +/// "*.ftl" file looks like, e.g. './src/diagnostic/locales/en-US/default.ftl' : +/// ``` ignore +/// invalid-syntax = Invalid syntax +/// .expected = Expected one of `{$expected_items}` +/// ``` +/// There are two lines in './src/diagnostic/locales/en-US/default.ftl'. +/// - In line 1, `invalid-syntax` is a `index`, `Invalid syntax` is the `Message String` to this `index`. +/// - In line 2, `.expected` is another `index`, it is a `sub_index` of `invalid-syntax`. +/// - In line 2, `sub_index` must start with a point `.` and it is optional and can be more than one. +/// - In line 2, `Expected one of `{$expected_items}`` is the `Message String` to `.expected`. It is an interpolated string. +/// - In line 2, `{$expected_items}` is a `MessageArgs` of the `Expected one of `{$expected_items}`` +/// and `MessageArgs` can be recognized as a Key-Value entry, it is optional. +/// +/// The pattern of above '*.ftl' file looks like: +/// ``` ignore +/// <'index'> = <'message_string' with optional 'MessageArgs'> +/// = <'message_string' with optional 'MessageArgs'>* +/// ``` +/// +/// Note: `DiagnosticHandler` uses `Mutex` internally to ensure thread safety, +/// so you don't need to use references like `Arc` or `Mutex` to make `DiagnosticHandler` thread safe. +/// +/// When your compiler needs to use `Compiler-Base-Error` to displaying diagnostics, you need to create a `DiagnosticHandler` at first. +/// For more information about how to create a `DiagnosticHandler`, see the doc above method `new_with_template_dir()`. +/// Since creating `DiagnosticHandler` needs to load the locally template (*.ftl) file, it may cause I/O performance loss, +/// so we recommend you create `DiagnosticHandler` eagerly and globally in the compiler and pass references to other modules that use `DiagnosticHandler`. +/// +/// And since `DiagnosticHandler` provides methods that do not supports mutable references "&mut self", so passing immutable references (&) is enough. +/// +/// For Example: +/// +/// 1. You can put `DiagnosticHandler` on the same level as `Lexer`, `Parser` and `CodeGenerator` in your compiler. +/// ```ignore +/// struct Compiler { +/// diag_handler: DiagnosticHandler, +/// lang_lexer: Lexer, +/// lang_parser: Parser, +/// code_generator: CodeGenerator +/// } +/// ``` +/// +/// 2. And send the immutable references to `Lexer`, `Parser` and `CodeGenerator` to displaying the diagnostic during compiling. +/// ```ignore +/// impl Compiler { +/// fn compile(&self) { +/// self.lang_lexer.lex(&self.diag_handler); +/// self.lang_parser.parse(&self.diag_handler); +/// self.code_generator.gen(&self.diag_handler); +/// } +/// } +/// ``` +/// +/// ```ignore +/// impl Lexer { +/// fn lex(&self, diag_handler: &DiagnosticHandler){ +/// handler.XXXX(); // do something to diaplay diagnostic. +/// } +/// } +/// ``` +/// +pub struct DiagnosticHandler { + handler_inner: Mutex, +} + +impl DiagnosticHandler { + /// Load all (*.ftl) template files under default directory. + /// + /// Default directory "./src/diagnostic/locales/en-US/" + /// Call the constructor 'new_with_template_dir()' to load the file. + /// For more information about the constructor 'new_with_template_dir()', see the doc above 'new_with_template_dir()'. + pub fn default() -> Result { + DiagnosticHandler::new_with_template_dir( + DEFAULT_TEMPLATE_RESOURCE, + ) + .with_context(|| { + format!( + "Failed to init `TemplateLoader` from '{}'", + DEFAULT_TEMPLATE_RESOURCE + ) + }) + } + + /// Load all (*.ftl) template files under directory `template_dir`. + /// `DiagnosticHandler` will load all the files end with "*.ftl" under the directory recursively. + /// If directory `template_dir` does not exist, this method will return an error. + /// + /// template_files + /// | + /// |---- template.ftl + /// |---- sub_template_files + /// | + /// |---- sub_template.ftl + /// + /// 'template.ftl' and 'sub_template.ftl' can both loaded by the `new_with_template_dir()`. + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticHandler; + /// let diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/"); + /// match diag_handler { + /// Ok(_) => {} + /// Err(_) => {panic!("`diag_handler` should be Ok(...)")} + /// } + /// + /// // './src_invalid/diagnostic/locales/en-US/' does not exist. + /// let diag_handler_invalid = DiagnosticHandler::new_with_template_dir("./src_invalid/diagnostic/locales/en-US/"); + /// match diag_handler_invalid { + /// Ok(_) => {panic!("`diag_handler_invalid` should be Err(...)")} + /// Err(_) => {} + /// } + /// ``` + pub fn new_with_template_dir(template_dir: &str) -> Result { + let handler_inner = DiagnosticHandlerInner::new_with_template_dir(template_dir) + .with_context(|| format!("Failed to init `TemplateLoader` from '{}'", template_dir))?; + Ok(Self { + handler_inner: Mutex::new(handler_inner), + }) + } + + /// Add a diagnostic generated from error to `DiagnosticHandler`. + /// `DiagnosticHandler` contains a set of `Diagnostic` + /// + /// Note: `DiagnosticHandler` does not deduplicate diagnostics. + /// If you add two same diagnostics, you will see two same messages in the terminal. + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticStyle; + /// # use compiler_base_error::DiagnosticHandler; + /// # use compiler_base_error::Diagnostic; + /// let diag_1 = Diagnostic::::new(); + /// let mut diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// assert_eq!(diag_handler.diagnostics_count().unwrap(), 0); + /// + /// diag_handler.add_err_diagnostic(diag_1); + /// assert_eq!(diag_handler.diagnostics_count().unwrap(), 1); + /// ``` + pub fn add_err_diagnostic(&self, diag: Diagnostic) -> Result<()> { + match self.handler_inner.lock() { + Ok(mut inner) => { + inner.add_err_diagnostic(diag); + Ok(()) + } + Err(_) => bail!("Add Error Diagnostic Failed."), + } + } + + /// Add a diagnostic generated from warning to `DiagnosticHandler`. + /// `DiagnosticHandler` contains a set of `Diagnostic` + /// + /// Note: `DiagnosticHandler` does not deduplicate diagnostics. + /// If you add two same diagnostics, you will see two same messages in the terminal. + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticStyle; + /// # use compiler_base_error::DiagnosticHandler; + /// # use compiler_base_error::Diagnostic; + /// let diag_1 = Diagnostic::::new(); + /// let mut diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// assert_eq!(diag_handler.diagnostics_count().unwrap(), 0); + /// + /// diag_handler.add_warn_diagnostic(diag_1); + /// assert_eq!(diag_handler.diagnostics_count().unwrap(), 1); + /// ``` + pub fn add_warn_diagnostic(&self, diag: Diagnostic) -> Result<()> { + match self.handler_inner.lock() { + Ok(mut inner) => { + inner.add_warn_diagnostic(diag); + Ok(()) + } + Err(_) => bail!("Add Warn Diagnostic Failed."), + } + } + + /// Get count of diagnostics in `DiagnosticHandler`. + /// `DiagnosticHandler` contains a set of `Diagnostic` + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticStyle; + /// # use compiler_base_error::DiagnosticHandler; + /// # use compiler_base_error::Diagnostic; + /// let diag_1 = Diagnostic::::new(); + /// let mut diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// assert_eq!(diag_handler.diagnostics_count().unwrap(), 0); + /// + /// diag_handler.add_warn_diagnostic(diag_1); + /// assert_eq!(diag_handler.diagnostics_count().unwrap(), 1); + /// ``` + pub fn diagnostics_count(&self) -> Result { + match self.handler_inner.lock() { + Ok(inner) => Ok(inner.diagnostics_count()), + Err(_) => bail!("Diagnostics Counts Failed."), + } + } + + /// Emit the diagnostic messages generated from error to to terminal stderr. + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticStyle; + /// # use compiler_base_error::DiagnosticHandler; + /// # use compiler_base_error::Diagnostic; + /// let diag_1 = Diagnostic::::new(); + /// let mut diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// + /// assert_eq!(diag_handler.has_errors().unwrap(), false); + /// diag_handler.emit_error_diagnostic(diag_1); + /// assert_eq!(diag_handler.has_errors().unwrap(), true); + /// ``` + pub fn emit_error_diagnostic(&self, diag: Diagnostic) -> Result<()> { + match self.handler_inner.lock() { + Ok(mut inner) => { + inner.emit_error_diagnostic(diag); + Ok(()) + } + Err(_) => bail!("Emit Error Diagnostics Failed."), + } + } + + /// Emit the diagnostic messages generated from warning to to terminal stderr. + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticStyle; + /// # use compiler_base_error::DiagnosticHandler; + /// # use compiler_base_error::Diagnostic; + /// let diag_1 = Diagnostic::::new(); + /// let mut diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// + /// assert_eq!(diag_handler.has_warns().unwrap(), false); + /// diag_handler.emit_warn_diagnostic(diag_1); + /// assert_eq!(diag_handler.has_warns().unwrap(), true); + /// ``` + pub fn emit_warn_diagnostic(&self, diag: Diagnostic) -> Result<()> { + match self.handler_inner.lock() { + Ok(mut inner) => { + inner.emit_warn_diagnostic(diag); + Ok(()) + } + Err(_) => bail!("Emit Warn Diagnostics Failed."), + } + } + + /// Emit all the diagnostics messages to to terminal stderr. + /// `DiagnosticHandler` contains a set of `Diagnostic` + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticStyle; + /// # use compiler_base_error::DiagnosticHandler; + /// # use compiler_base_error::Diagnostic; + /// let diag_1 = Diagnostic::::new(); + /// let diag_2 = Diagnostic::::new(); + /// let mut diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// + /// diag_handler.add_err_diagnostic(diag_1); + /// diag_handler.add_err_diagnostic(diag_2); + /// diag_handler.emit_stashed_diagnostics(); + /// ``` + pub fn emit_stashed_diagnostics(&self) -> Result<()> { + match self.handler_inner.lock() { + Ok(mut inner) => { + inner.emit_stashed_diagnostics(); + Ok(()) + } + Err(_) => bail!("Emit Stashed Diagnostics Failed."), + } + } + + /// If some diagnotsics generated by errors, `has_errors` returns `True`. + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticStyle; + /// # use compiler_base_error::DiagnosticHandler; + /// # use compiler_base_error::Diagnostic; + /// let diag_1 = Diagnostic::::new(); + /// let mut diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// + /// assert_eq!(diag_handler.has_errors().unwrap(), false); + /// diag_handler.emit_error_diagnostic(diag_1); + /// assert_eq!(diag_handler.has_errors().unwrap(), true); + /// ``` + pub fn has_errors(&self) -> Result { + match self.handler_inner.lock() { + Ok(inner) => Ok(inner.has_errors()), + Err(_) => bail!("Check Has Errors Failed."), + } + } + + /// If some diagnotsics generated by warnings, `has_errors` returns `True`. + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticStyle; + /// # use compiler_base_error::DiagnosticHandler; + /// # use compiler_base_error::Diagnostic; + /// let diag_1 = Diagnostic::::new(); + /// let mut diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// + /// assert_eq!(diag_handler.has_warns().unwrap(), false); + /// diag_handler.emit_warn_diagnostic(diag_1); + /// assert_eq!(diag_handler.has_warns().unwrap(), true); + /// ``` + pub fn has_warns(&self) -> Result { + match self.handler_inner.lock() { + Ok(inner) => Ok(inner.has_warns()), + Err(_) => bail!("Check Has Warns Failed."), + } + } + + /// After emitting all the diagnostics, it will panic. + /// + /// # Examples + /// + /// ```rust + /// # use compiler_base_error::DiagnosticStyle; + /// # use compiler_base_error::Diagnostic; + /// # use compiler_base_error::DiagnosticHandler; + /// # use std::panic; + /// let diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// + /// diag_handler.abort_if_errors().unwrap(); + /// diag_handler.add_warn_diagnostic(Diagnostic::::new()).unwrap(); + /// + /// diag_handler.abort_if_errors().unwrap(); + /// diag_handler.add_err_diagnostic(Diagnostic::::new()).unwrap(); + /// + /// let result = panic::catch_unwind(|| { + /// diag_handler.abort_if_errors().unwrap(); + /// }); + /// assert!(result.is_err()); + /// ``` + pub fn abort_if_errors(&self) -> Result<()> { + match self.handler_inner.lock() { + Ok(mut inner) => { + inner.abort_if_errors(); + Ok(()) + } + Err(_) => bail!("Abort If Errors Failed."), + } + } + + /// Get the message string from "*.ftl" file by `index`, `sub_index` and `MessageArgs`. + /// And for the 'default.ftl' shown above, you can get messages as follow: + /// + /// ```ignore + /// invalid-syntax = Invalid syntax + /// .expected = Expected one of `{$expected_items}` + /// ``` + /// + /// 1. If you want the message 'Invalid syntax' in line 1. + /// + /// ``` rust + /// # use compiler_base_error::MessageArgs; + /// # use compiler_base_error::DiagnosticHandler; + /// + /// // 1. Prepare an empty `MessageArgs`, Message in line 1 is not an interpolated string. + /// let no_args = MessageArgs::new(); + /// + /// // 2. `index` is 'invalid-syntax' and has no `sub_index`. + /// let index = "invalid-syntax"; + /// let sub_index = None; + /// + /// // 3. Create the `DiagnosticHandler` with template (*.ftl) files directory. + /// let diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// + /// // 4. Get the message. + /// let msg_in_line_1 = diag_handler.get_diagnostic_msg(index, sub_index, &no_args).unwrap(); + /// + /// assert_eq!(msg_in_line_1, "Invalid syntax"); + /// ``` + /// + /// 2. If you want the message 'Expected one of `{$expected_items}`' in line 2. + /// + /// ``` rust + /// # use compiler_base_error::MessageArgs; + /// # use compiler_base_error::DiagnosticHandler; + /// + /// // 1. Prepare the `MessageArgs` for `{$expected_items}`. + /// let mut args = MessageArgs::new(); + /// args.set("expected_items", "I am an expected item"); + /// + /// // 2. `index` is 'invalid-syntax'. + /// let index = "invalid-syntax"; + /// + /// // 3. `sub_index` is 'expected'. + /// let sub_index = "expected"; + /// + /// // 4. Create the `DiagnosticHandler` with template (*.ftl) files directory. + /// let diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + /// + /// // 5. Get the message. + /// let msg_in_line_2 = diag_handler.get_diagnostic_msg(index, Some(sub_index), &args).unwrap(); + /// + /// assert_eq!(msg_in_line_2, "Expected one of `\u{2068}I am an expected item\u{2069}`"); + /// ``` + pub fn get_diagnostic_msg( + &self, + index: &str, + sub_index: Option<&str>, + args: &MessageArgs, + ) -> Result { + match self.handler_inner.lock() { + Ok(inner) => inner.get_diagnostic_msg(index, sub_index, args), + Err(_) => bail!("Find Diagnostic Message Failed."), + } + } +} + +/// `MessageArgs` is the arguments of the interpolated string. +/// +/// `MessageArgs` is a Key-Value entry which only supports "set" and without "get". +/// You need getting nothing from `MessageArgs`. Only setting it and senting it to `DiagnosticHandler` is enough. +/// +/// Note: Currently both `Key` and `Value` of `MessageArgs` types only support string (&str). +/// +/// # Examples +/// +/// ``` rust +/// # use compiler_base_error::DiagnosticHandler; +/// # use compiler_base_error::MessageArgs; +/// +/// let index = "invalid-syntax"; +/// let sub_index = Some("expected"); +/// let mut msg_args = MessageArgs::new(); +/// // You only need "set()". +/// msg_args.set("This is Key", "This is Value"); +/// +/// // Create the `DiagnosticHandler` with template (*.ftl) files directory. +/// let diag_handler = DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); +/// +/// // When you use it, just sent it to `DiagnosticHandler`. +/// let msg_in_line_1 = diag_handler.get_diagnostic_msg(index, sub_index, &msg_args); +/// ``` +/// +/// For more information about the `DiagnosticHandler` see the doc above struct `DiagnosticHandler`. +pub struct MessageArgs<'a>(pub(crate) FluentArgs<'a>); +impl<'a> MessageArgs<'a> { + pub fn new() -> Self { + Self(FluentArgs::new()) + } + + pub fn set(&mut self, k: &'a str, v: &'a str) { + self.0.set(k, v); + } +} + +pub(crate) struct DiagnosticHandlerInner { + emitter: Box>, + diagnostics: Vec>, + err_count: usize, + warn_count: usize, + template_loader: Arc, +} + +impl DiagnosticHandlerInner { + /// Load all (*.ftl) template files under directory `template_dir`. + pub(crate) fn new_with_template_dir(template_dir: &str) -> Result { + let template_loader = TemplateLoader::new_with_template_dir(template_dir) + .with_context(|| format!("Failed to init `TemplateLoader` from '{}'", template_dir))?; + + Ok(Self { + err_count: 0, + warn_count: 0, + emitter: Box::new(TerminalEmitter::default()), + diagnostics: vec![], + template_loader: Arc::new(template_loader), + }) + } + + /// Add a diagnostic generated from error to `DiagnosticHandler`. + /// `DiagnosticHandler` contains a set of `Diagnostic` + pub(crate) fn add_err_diagnostic(&mut self, diag: Diagnostic) { + self.diagnostics.push(diag); + self.err_count += 1; + } + + /// Add a diagnostic generated from warning to `DiagnosticHandler`. + /// `DiagnosticHandler` contains a set of `Diagnostic` + pub(crate) fn add_warn_diagnostic(&mut self, diag: Diagnostic) { + self.diagnostics.push(diag); + self.warn_count += 1; + } + + /// Get count of diagnostics in `DiagnosticHandler`. + /// `DiagnosticHandler` contains a set of `Diagnostic` + #[inline] + pub(crate) fn diagnostics_count(&self) -> usize { + self.diagnostics.len() + } + + /// Emit the diagnostic messages generated from error to to terminal stderr. + pub(crate) fn emit_error_diagnostic(&mut self, diag: Diagnostic) { + self.emitter.emit_diagnostic(&diag); + self.err_count += 1; + } + + /// Emit the diagnostic messages generated from warning to to terminal stderr. + pub(crate) fn emit_warn_diagnostic(&mut self, diag: Diagnostic) { + self.emitter.emit_diagnostic(&diag); + self.warn_count += 1; + } + + /// Emit all the diagnostics messages to to terminal stderr. + /// `DiagnosticHandler` contains a set of `Diagnostic` + pub(crate) fn emit_stashed_diagnostics(&mut self) { + for diag in &self.diagnostics { + self.emitter.emit_diagnostic(&diag) + } + } + + /// If some diagnotsics generated by errors, `has_errors` returns `True`. + #[inline] + pub(crate) fn has_errors(&self) -> bool { + self.err_count > 0 + } + + /// If some diagnotsics generated by warnings, `has_errors` returns `True`. + #[inline] + pub(crate) fn has_warns(&self) -> bool { + self.warn_count > 0 + } + + /// After emitting all the diagnostics, it will panic. + pub(crate) fn abort_if_errors(&mut self) { + self.emit_stashed_diagnostics(); + + if self.has_errors() { + FatalError.raise(); + } + } + + /// Get the message string from "*.ftl" file by `index`, `sub_index` and `MessageArgs`. + /// "*.ftl" file looks like, e.g. './src/diagnostic/locales/en-US/default.ftl' : + pub(crate) fn get_diagnostic_msg( + &self, + index: &str, + sub_index: Option<&str>, + args: &MessageArgs, + ) -> Result { + self.template_loader.get_msg_to_str(index, sub_index, &args) + } +} + + +#[cfg(test)] +mod test{ + use std::cell::RefCell; + + struct Test{ + s: String, + } + + // impl Test{ + // fn ttt(&mut self, a: &mut String){ + // self.ttt(&mut self.s); + // } + // } + + struct Test1{ + s: RefCell, + } + + struct TestInner{ + s: String + } + + impl Test1{ + fn ttt(&self, a: &mut String){ + self.ttt(&mut self.s.borrow_mut().s); + } + } + +} \ No newline at end of file diff --git a/kclvm/compiler_base/error/src/diagnostic/diagnostic_message.rs b/kclvm/compiler_base/error/src/diagnostic/diagnostic_message.rs index 5f00ad0f4..abadba7dc 100644 --- a/kclvm/compiler_base/error/src/diagnostic/diagnostic_message.rs +++ b/kclvm/compiler_base/error/src/diagnostic/diagnostic_message.rs @@ -1,39 +1,33 @@ //! The crate provides `TemplateLoader` to load the diagnositc message displayed in diagnostics from "*.ftl" files, +//! `TemplateLoader` relies on 'fluent0.16.0' to support loading diagnositc message from "*.ftl" files. //! +//! 'fluent0.16.0' is used to support diagnostic text template. +//! For more information about 'fluent0.16.0', see https://projectfluent.org/. + use anyhow::{bail, Context, Result}; -use fluent::{FluentArgs, FluentBundle, FluentResource}; +use fluent::{FluentBundle, FluentResource}; use std::{fs, sync::Arc}; use unic_langid::langid; use walkdir::{DirEntry, WalkDir}; +use crate::diagnostic_handler::MessageArgs; /// Struct `TemplateLoader` load template contents from "*.ftl" file. -/// /// `TemplateLoader` will operate on files locally. -/// -/// In order to avoid the performance loss and thread safety problems that -/// may occur during the constructing the `TemplateLoader`, we close the constructor of `TemplateLoader`. -/// -/// You only need to pass the path of the "*.ftl" file to `DiagnosticHandler`, -/// and `DiagnosticHandler` will automatically construct `TemplateLoader` and load the template file. -/// -/// `TemplateLoader` is only useful for you, when you want to get message from template file by `get_msg_to_str()`. -/// For more information about how to use `get_msg_to_str()`, see the doc above `get_msg_to_str()`. -pub struct TemplateLoader { +pub(crate) struct TemplateLoader { template_inner: Arc, } impl TemplateLoader { - // Create the `TemplateLoader` with template (*.ftl) files directory. - // `TemplateLoader` will load all the files end with "*.ftl" under the directory recursively. - // - // template_files - // | - // |---- template.ftl - // |---- sub_template_files - // | - // |---- sub_template.ftl - // - // 'template.ftl' and 'sub_template.ftl' can both loaded by the `new_with_template_dir()`. + /// Create the `TemplateLoader` with template (*.ftl) files directory. + /// `TemplateLoader` will load all the files end with "*.ftl" under the directory recursively. + /// template_files + /// | + /// |---- template.ftl + /// |---- sub_template_files + /// | + /// |---- sub_template.ftl + /// + /// 'template.ftl' and 'sub_template.ftl' can both loaded by the `new_with_template_dir()`. pub(crate) fn new_with_template_dir(template_dir: &str) -> Result { let template_inner = TemplateLoaderInner::new_with_template_dir(template_dir) .with_context(|| format!("Failed to load '*.ftl' from '{}'", template_dir))?; @@ -43,78 +37,9 @@ impl TemplateLoader { } /// Get the message string from "*.ftl" file by `index`, `sub_index` and `MessageArgs`. - /// For more information about "*.ftl" file, see the doc above `TemplateLoader`. - /// + /// For more information about "*.ftl" file, see the doc above `DiagnosticHandler`. /// "*.ftl" file looks like, e.g. './src/diagnostic/locales/en-US/default.ftl' : - /// - /// ``` ignore - /// 1. invalid-syntax = Invalid syntax - /// 2. .expected = Expected one of `{$expected_items}` - /// ``` - /// - /// - In line 1, `invalid-syntax` is a `index`, `Invalid syntax` is the `Message String` to this `index`. - /// - In line 2, `.expected` is another `index`, it is a `sub_index` of `invalid-syntax`. - /// - In line 2, `sub_index` must start with a point `.` and it is optional. - /// - In line 2, `Expected one of `{$expected_items}`` is the `Message String` to `.expected`. It is an interpolated string. - /// - In line 2, `{$expected_items}` is a `MessageArgs` of the `Expected one of `{$expected_items}`` - /// and `MessageArgs` can be recognized as a Key-Value entry, it is optional. - /// - /// The pattern of above '*.ftl' file looks like: - /// ``` ignore - /// 1. <'index'> = <'message_string' with optional 'MessageArgs'> - /// 2. = <'message_string' with optional 'MessageArgs'> - /// ``` - /// And for the 'default.ftl' shown above, you can get messages as follow: - /// - /// 1. If you want the message 'Invalid syntax' in line 1. - /// - /// ```ignore rust - /// # use compiler_base_error::diagnostic::diagnostic_message::TemplateLoader; - /// # use compiler_base_error::diagnostic::diagnostic_message::MessageArgs; - /// # use std::borrow::Borrow; - /// - /// // 1. Prepare an empty `MessageArgs`, Message in line 1 is not an interpolated string. - /// let no_args = MessageArgs::new(); - /// - /// // 2. `index` is 'invalid-syntax' and has no `sub_index`. - /// let index = "invalid-syntax"; - /// let sub_index = None; - /// - /// // 3. Create the `TemplateLoader` with template (*.ftl) files directory. - /// // We cloesd the constructor of `TemplateLoader`. - /// // For more information, see the doc above the `TemplateLoader`. - /// let error_message = TemplateLoader::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); - /// let msg_in_line_1 = error_message.get_msg_to_str(index, sub_index, &no_args).unwrap(); - /// - /// assert_eq!(msg_in_line_1, "Invalid syntax"); - /// ``` - /// - /// 2. If you want the message 'Expected one of `{$expected_items}`' in line 2. - /// - /// ```ignore rust - /// # use compiler_base_error::diagnostic::diagnostic_message::TemplateLoader; - /// # use compiler_base_error::diagnostic::diagnostic_message::MessageArgs; - /// # use std::borrow::Borrow; - /// - /// // 1. Prepare the `MessageArgs` for `{$expected_items}`. - /// let mut args = MessageArgs::new(); - /// args.set("expected_items", "I am an expected item"); - /// - /// // 2. `index` is 'invalid-syntax'. - /// let index = "invalid-syntax"; - /// - /// // 3. `sub_index` is 'expected'. - /// let sub_index = "expected"; - /// - /// // 4. With the help of `TemplateLoader`, you can get the message in 'default.ftl'. - /// // We cloesd the constructor of `TemplateLoader`. - /// // For more information, see the doc above the `TemplateLoader`. - /// let error_message = TemplateLoader::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); - /// let msg_in_line_2 = error_message.get_msg_to_str(index, Some(sub_index), &args).unwrap(); - /// - /// assert_eq!(msg_in_line_2, "Expected one of `\u{2068}I am an expected item\u{2069}`"); - /// ``` - pub fn get_msg_to_str( + pub(crate) fn get_msg_to_str( &self, index: &str, sub_index: Option<&str>, @@ -146,47 +71,7 @@ impl TemplateLoader { } } -/// `MessageArgs` is the arguments of the interpolated string. -/// -/// `MessageArgs` is a Key-Value entry which only supports "set" and without "get". -/// You need getting nothing from `MessageArgs`. Only setting it and senting it to `TemplateLoader` is enough. -/// -/// Note: Currently both `Key` and `Value` of `MessageArgs` types only support string (&str). -/// -/// # Examples -/// -/// ```ignore rust -/// # use compiler_base_error::diagnostic::diagnostic_message::MessageArgs; -/// # use compiler_base_error::diagnostic::diagnostic_message::TemplateLoader; -/// # use std::borrow::Borrow; -/// -/// let index = "invalid-syntax"; -/// let sub_index = Some("expected"); -/// let mut msg_args = MessageArgs::new(); -/// // You only need "set()". -/// msg_args.set("This is Key", "This is Value"); -/// -/// // We cloesd the constructor of `TemplateLoader`. -/// // For more information, see the doc above the `TemplateLoader`. -/// let error_message = TemplateLoader::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); -/// -/// // When you use it, just sent it to `TemplateLoader`. -/// let msg_in_line_1 = error_message.get_msg_to_str(index, sub_index, &msg_args); -/// ``` -/// -/// For more information about the `TemplateLoader` see the doc above struct `TemplateLoader`. -pub struct MessageArgs<'a>(FluentArgs<'a>); -impl<'a> MessageArgs<'a> { - pub fn new() -> Self { - Self(FluentArgs::new()) - } - - pub fn set(&mut self, k: &'a str, v: &'a str) { - self.0.set(k, v); - } -} - -// `TemplateLoaderInner` is used to privatize the default constructor of `TemplateLoader`. +/// `TemplateLoaderInner` is used to privatize the default constructor of `TemplateLoader`. struct TemplateLoaderInner { template_bunder: FluentBundle, } diff --git a/kclvm/compiler_base/error/src/diagnostic/mod.rs b/kclvm/compiler_base/error/src/diagnostic/mod.rs index e7c11f5a9..ce5e41d07 100644 --- a/kclvm/compiler_base/error/src/diagnostic/mod.rs +++ b/kclvm/compiler_base/error/src/diagnostic/mod.rs @@ -1,8 +1,9 @@ pub use rustc_errors::styled_buffer::StyledBuffer; use rustc_errors::Style; -pub mod diagnostic_message; pub mod components; +pub mod diagnostic_handler; +pub mod diagnostic_message; pub mod style; #[cfg(test)] @@ -22,9 +23,10 @@ where /// # Examples /// /// ```rust - /// # use compiler_base_error::diagnostic::style::DiagnosticStyle; - /// # use compiler_base_error::diagnostic::StyledBuffer; - /// # use compiler_base_error::diagnostic::Component; + /// # use compiler_base_error::Component; + /// # use compiler_base_error::DiagnosticStyle; + /// # use rustc_errors::styled_buffer::StyledBuffer; + /// /// struct ComponentWithStyleLogo { /// text: String /// } @@ -47,7 +49,10 @@ where /// /// ```rust /// # use rustc_errors::styled_buffer::StyledBuffer; -/// # use compiler_base_error::diagnostic::{Diagnostic, components::Label, style::DiagnosticStyle, Component}; +/// # use compiler_base_error::components::Label; +/// # use compiler_base_error::DiagnosticStyle; +/// # use compiler_base_error::Diagnostic; +/// # use compiler_base_error::Component; /// /// // If you want a diagnostic message “error[E3033]: this is an error!”. /// let mut diagnostic = Diagnostic::new(); diff --git a/kclvm/compiler_base/error/src/diagnostic/style.rs b/kclvm/compiler_base/error/src/diagnostic/style.rs index fca8e8985..5ed58af43 100644 --- a/kclvm/compiler_base/error/src/diagnostic/style.rs +++ b/kclvm/compiler_base/error/src/diagnostic/style.rs @@ -55,7 +55,7 @@ impl DiagnosticStyle { /// /// ```rust /// # use rustc_errors::Style; - /// # use compiler_base_error::diagnostic::style::DiagnosticStyle; + /// # use compiler_base_error::DiagnosticStyle; /// /// let mut color_spec = DiagnosticStyle::NeedFix.render_style_to_color_spec(); /// assert!(DiagnosticStyle::NeedFix.check_is_expected_colorspec(&color_spec)); diff --git a/kclvm/compiler_base/error/src/diagnostic/tests.rs b/kclvm/compiler_base/error/src/diagnostic/tests.rs index a651d1f54..b6482865c 100644 --- a/kclvm/compiler_base/error/src/diagnostic/tests.rs +++ b/kclvm/compiler_base/error/src/diagnostic/tests.rs @@ -77,7 +77,7 @@ mod test_components { } mod test_error_message { - use crate::diagnostic::diagnostic_message::{MessageArgs, TemplateLoader}; + use crate::{diagnostic::diagnostic_message::TemplateLoader, diagnostic_handler::MessageArgs}; #[test] fn test_template_message() { diff --git a/kclvm/compiler_base/error/src/emitter.rs b/kclvm/compiler_base/error/src/emitter.rs index 148d80388..ccfdd22a2 100644 --- a/kclvm/compiler_base/error/src/emitter.rs +++ b/kclvm/compiler_base/error/src/emitter.rs @@ -9,7 +9,7 @@ //! //!Besides, it's easy to define your customized `Emitter` by implementing `Emitter` trait. //! For more information about how to define your customized `Emitter`, see the doc above `Emitter` trait. -//! + use crate::diagnostic::{Component, Diagnostic}; use compiler_base_macros::bug; use rustc_errors::{ @@ -107,8 +107,8 @@ where /// ```rust /// # use crate::compiler_base_error::Emitter; /// # use compiler_base_error::TerminalEmitter; -/// # use compiler_base_error::diagnostic::{components::Label, Diagnostic}; -/// # use compiler_base_error::diagnostic::style::DiagnosticStyle; +/// # use compiler_base_error::{components::Label, Diagnostic}; +/// # use compiler_base_error::DiagnosticStyle; /// /// // 1. Create a TerminalEmitter /// let mut term_emitter = TerminalEmitter::default(); diff --git a/kclvm/compiler_base/error/src/lib.rs b/kclvm/compiler_base/error/src/lib.rs index 754d6b3a1..727025121 100644 --- a/kclvm/compiler_base/error/src/lib.rs +++ b/kclvm/compiler_base/error/src/lib.rs @@ -1,5 +1,19 @@ -pub mod diagnostic; +//! Compiler-Base-Error +//! +//! The idea with `Compiler-Base-Error` is to make a reusable library, +//! by separating out error thorwing and diagnostic diaplaying or other error handling procedures. +//! +//! - Compiler-Base-Error provides `DiagnosticHandler` to diaplay diagnostic. +//! For more information about `DiagnosticHandler`, see doc in 'compiler_base/error/diagnostic/diagnostic_handler.rs'. +//! +//! - TODO(zongz): Compiler-Base-Error provides `ErrorRecover` to recover from errors. + +mod diagnostic; mod emitter; +#[cfg(test)] +mod tests; -pub use emitter::Emitter; -pub use emitter::TerminalEmitter; +pub use diagnostic::{ + components, diagnostic_handler, style::DiagnosticStyle, Component, Diagnostic, +}; +pub use emitter::{Emitter, TerminalEmitter}; diff --git a/kclvm/compiler_base/error/src/tests.rs b/kclvm/compiler_base/error/src/tests.rs new file mode 100644 index 000000000..f235fc4e5 --- /dev/null +++ b/kclvm/compiler_base/error/src/tests.rs @@ -0,0 +1,101 @@ +mod test_diagnostic_handler { + use std::panic; + + use crate::{ + diagnostic_handler::{DiagnosticHandler, MessageArgs}, + Diagnostic, DiagnosticStyle, + }; + + #[test] + fn test_diagnostic_handler_new_with_template_dir() { + let diag_handler = + DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/"); + match diag_handler { + Ok(_) => {} + Err(_) => { + panic!("`diag_handler` should be Ok(...)") + } + } + + let diag_handler_invalid = DiagnosticHandler::new_with_template_dir("./invalid_path"); + match diag_handler_invalid { + Ok(_) => { + panic!("`diag_handler_invalid` should be Err(...)") + } + Err(_) => {} + } + } + + #[test] + fn test_diagnostic_handler_add_diagnostic() { + let diag_1 = Diagnostic::::new(); + let diag_handler = + DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + assert_eq!(diag_handler.diagnostics_count().unwrap(), 0); + + diag_handler.add_err_diagnostic(diag_1).unwrap(); + assert_eq!(diag_handler.diagnostics_count().unwrap(), 1); + } + + #[test] + fn test_diagnostic_handler_get_diagnostic_msg() { + let no_args = MessageArgs::new(); + let index = "invalid-syntax"; + let sub_index = None; + let diag_handler = + DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + let msg_in_line_1 = diag_handler + .get_diagnostic_msg(index, sub_index, &no_args) + .unwrap(); + assert_eq!(msg_in_line_1, "Invalid syntax"); + + let mut args = MessageArgs::new(); + args.set("expected_items", "I am an expected item"); + let sub_index = "expected"; + let msg_in_line_2 = diag_handler + .get_diagnostic_msg(index, Some(sub_index), &args) + .unwrap(); + assert_eq!( + msg_in_line_2, + "Expected one of `\u{2068}I am an expected item\u{2069}`" + ); + } + + #[test] + fn test_diagnostic_handler_has() { + let diag_handler = + DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + // test has_errors() + assert_eq!(diag_handler.has_errors().unwrap(), false); + diag_handler + .add_err_diagnostic(Diagnostic::::new()) + .unwrap(); + assert_eq!(diag_handler.has_errors().unwrap(), true); + + // test has_warns() + assert_eq!(diag_handler.has_warns().unwrap(), false); + diag_handler + .add_warn_diagnostic(Diagnostic::::new()) + .unwrap(); + assert_eq!(diag_handler.has_warns().unwrap(), true); + } + + #[test] + fn test_abort_if_errors() { + let diag_handler = + DiagnosticHandler::new_with_template_dir("./src/diagnostic/locales/en-US/").unwrap(); + diag_handler.abort_if_errors().unwrap(); + diag_handler + .add_warn_diagnostic(Diagnostic::::new()) + .unwrap(); + diag_handler.abort_if_errors().unwrap(); + diag_handler + .add_err_diagnostic(Diagnostic::::new()) + .unwrap(); + + let result = panic::catch_unwind(|| { + diag_handler.abort_if_errors().unwrap(); + }); + assert!(result.is_err()); + } +} diff --git a/kclvm/compiler_base/span/src/lib.rs b/kclvm/compiler_base/span/src/lib.rs index 44c2ed650..50c8ae215 100644 --- a/kclvm/compiler_base/span/src/lib.rs +++ b/kclvm/compiler_base/span/src/lib.rs @@ -8,6 +8,7 @@ //! Reference: https://github.com/rust-lang/rust/blob/master/compiler/rustc_span/src/lib.rs pub mod span; +pub use rustc_span::fatal_error; pub use span::{BytePos, Span, DUMMY_SP}; pub type SourceMap = rustc_span::SourceMap;