Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion kclvm/compiler_base/error/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ edition = "2021"
[dependencies]
compiler_base_macros = {path = "../macros", version = "0.1.0"}
rustc_errors = {path="../3rdparty/rustc_errors", version="0.1.0"}
termcolor = "1.0"
unic-langid = {version="0.9.0", features = ["macros"]}

fluent = "0.16.0"
termcolor = "1.0"
walkdir = "2"
anyhow = "1.0"
239 changes: 239 additions & 0 deletions kclvm/compiler_base/error/src/diagnostic/diagnostic_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
//! The crate provides `TemplateLoader` to load the diagnositc message displayed in diagnostics from "*.ftl" files,
//!
use anyhow::{bail, Context, Result};
use fluent::{FluentArgs, FluentBundle, FluentResource};
use std::{fs, sync::Arc};
use unic_langid::langid;
use walkdir::{DirEntry, WalkDir};

/// 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 {
template_inner: Arc<TemplateLoaderInner>,
}

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()`.
pub(crate) fn new_with_template_dir(template_dir: &str) -> Result<Self> {
let template_inner = TemplateLoaderInner::new_with_template_dir(template_dir)
.with_context(|| format!("Failed to load '*.ftl' from '{}'", template_dir))?;
Ok(Self {
template_inner: Arc::new(template_inner),
})
}

/// Get the message string from "*.ftl" file by `index`, `sub_index` and `MessageArgs`.
/// For more information about "*.ftl" file, see the doc above `TemplateLoader`.
///
/// "*.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. <optional 'sub_index' start with point> = <'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(
&self,
index: &str,
sub_index: Option<&str>,
args: &MessageArgs,
) -> Result<String> {
let msg = match self.template_inner.get_template_bunder().get_message(index) {
Some(m) => m,
None => bail!("Message doesn't exist."),
};

let pattern = match sub_index {
Some(s_id) => {
let attr = msg.get_attribute(s_id).unwrap();
attr.value()
}
None => match msg.value() {
Some(v) => v,
None => bail!("Message has no value."),
},
};

let MessageArgs(args) = args;
let value = self.template_inner.get_template_bunder().format_pattern(
pattern,
Some(&args),
&mut vec![],
);
Ok(value.to_string())
}
}

/// `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`.
struct TemplateLoaderInner {
template_bunder: FluentBundle<FluentResource>,
}

impl TemplateLoaderInner {
fn new_with_template_dir(template_dir: &str) -> Result<Self> {
let mut template_bunder = FluentBundle::new(vec![langid!("en-US")]);
load_all_templates_in_dir_to_resources(template_dir, &mut template_bunder)
.with_context(|| format!("Failed to load '*.ftl' from '{}'", template_dir))?;
Ok(Self { template_bunder })
}

fn get_template_bunder(&self) -> &FluentBundle<FluentResource> {
&self.template_bunder
}
}

fn is_ftl_file(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.ends_with(".ftl"))
.unwrap_or(false)
}

fn load_all_templates_in_dir_to_resources(
dir: &str,
fluent_bundle: &mut FluentBundle<FluentResource>,
) -> Result<()> {
if !std::path::Path::new(&dir).exists() {
bail!("Failed to load '*.ftl' dir");
}

for entry in WalkDir::new(dir) {
let entry = entry?;

if is_ftl_file(&entry) {
let resource = fs::read_to_string(entry.path())?;

match FluentResource::try_new(resource) {
Ok(s) => match fluent_bundle.add_resource(s) {
Err(_) => bail!("Failed to parse an FTL string."),
Ok(_) => {}
},
Err(_) => bail!("Failed to add FTL resources to the bundle."),
};
}
}
Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
invalid-syntax =
Invalid syntax
.expected = Expected one of `{$expected_items}`
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
invalid-syntax-1 =
Invalid syntax 1
.expected_1 = Expected one of `{$expected_items}` 1
4 changes: 4 additions & 0 deletions kclvm/compiler_base/error/src/diagnostic/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use rustc_errors::styled_buffer::StyledBuffer;
use rustc_errors::Style;
pub mod diagnostic_message;

pub mod components;
pub mod style;
Expand All @@ -21,6 +22,9 @@ where
/// # Examples
///
/// ```rust
/// # use compiler_base_error::diagnostic::style::DiagnosticStyle;
/// # use compiler_base_error::diagnostic::StyledBuffer;
/// # use compiler_base_error::diagnostic::Component;
/// struct ComponentWithStyleLogo {
/// text: String
/// }
Expand Down
2 changes: 1 addition & 1 deletion kclvm/compiler_base/error/src/diagnostic/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl DiagnosticStyle {
///
/// ```rust
/// # use rustc_errors::Style;
/// # use compiler_base_error::style::DiagnosticStyle;
/// # use compiler_base_error::diagnostic::style::DiagnosticStyle;
///
/// let mut color_spec = DiagnosticStyle::NeedFix.render_style_to_color_spec();
/// assert!(DiagnosticStyle::NeedFix.check_is_expected_colorspec(&color_spec));
Expand Down
48 changes: 48 additions & 0 deletions kclvm/compiler_base/error/src/diagnostic/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,51 @@ mod test_components {
assert_eq!(result.get(0).unwrap().get(0).unwrap().style, None);
}
}

mod test_error_message {
use crate::diagnostic::diagnostic_message::{MessageArgs, TemplateLoader};

#[test]
fn test_template_message() {
let template_dir = "./src/diagnostic/locales/en-US";
let template_loader = TemplateLoader::new_with_template_dir(template_dir).unwrap();

let mut args = MessageArgs::new();
check_template_msg(
"invalid-syntax",
None,
&args,
"Invalid syntax",
&template_loader,
);

args.set("expected_items", "I am an expected item");
check_template_msg(
"invalid-syntax",
Some("expected"),
&args,
"Expected one of `\u{2068}I am an expected item\u{2069}`",
&template_loader,
);

args.set("expected_items", "I am an expected item");
check_template_msg(
"invalid-syntax-1",
Some("expected_1"),
&args,
"Expected one of `\u{2068}I am an expected item\u{2069}` 1",
&template_loader,
);
}

fn check_template_msg(
index: &str,
sub_index: Option<&str>,
args: &MessageArgs,
expected_msg: &str,
template_loader: &TemplateLoader,
) {
let msg_in_line = template_loader.get_msg_to_str(index, sub_index, &args);
assert_eq!(msg_in_line.unwrap(), expected_msg);
}
}
4 changes: 2 additions & 2 deletions kclvm/compiler_base/error/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use termcolor::{Buffer, BufferWriter, ColorChoice, ColorSpec, StandardStream, Wr
///
/// 1. Define your Emitter:
///
/// ```no_run rust
/// ```ignore rust
///
/// // create a new `Emitter`
/// struct DummyEmitter {
Expand Down Expand Up @@ -64,7 +64,7 @@ use termcolor::{Buffer, BufferWriter, ColorChoice, ColorSpec, StandardStream, Wr
///
/// 2. Use your Emitter with diagnostic:
///
/// ```no_run rust
/// ```ignore rust
///
/// // Create a diagnostic for emitting.
/// let mut diagnostic = Diagnostic::<DiagnosticStyle>::new();
Expand Down