From 409669d23cc80d9502b5a27554de1275638ee9f3 Mon Sep 17 00:00:00 2001 From: WillLillis Date: Mon, 1 Jan 2024 16:06:14 -0500 Subject: [PATCH] feat: Hover and Autocomplete for gas directives Clean up generic stuff for enums Add uppercase support for directives Fix as decoration in .file directive xml entry --- directives/gas_directives.xml | 852 ++++++++++++++++++++++++++++++++++ registers/x86_64.xml | 2 +- src/bin/main.rs | 45 +- src/lib.rs | 4 +- src/lsp.rs | 124 ++++- src/types.rs | 114 ++++- src/x86_parser.rs | 133 +++++- 7 files changed, 1233 insertions(+), 41 deletions(-) create mode 100644 directives/gas_directives.xml diff --git a/directives/gas_directives.xml b/directives/gas_directives.xml new file mode 100644 index 00000000..bfda4f77 --- /dev/null +++ b/directives/gas_directives.xml @@ -0,0 +1,852 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/registers/x86_64.xml b/registers/x86_64.xml index 5f17e35f..fb7e73c4 100644 --- a/registers/x86_64.xml +++ b/registers/x86_64.xml @@ -1,5 +1,5 @@ - + diff --git a/src/bin/main.rs b/src/bin/main.rs index 9dc8abb9..18c6a5e3 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -36,7 +36,7 @@ pub fn main() -> anyhow::Result<()> { completion_item: Some(CompletionOptionsCompletionItem { label_details_support: Some(true), }), - trigger_characters: Some(vec![String::from("%")]), + trigger_characters: Some(vec![String::from("%"), String::from(".")]), ..Default::default() }); @@ -78,10 +78,6 @@ pub fn main() -> anyhow::Result<()> { let xml_conts_x86 = include_str!("../../opcodes/x86.xml"); populate_instructions(xml_conts_x86)? .into_iter() - .map(|mut instruction| { - instruction.arch = Some(Arch::X86); - instruction - }) .map(|instruction| { // filter out assemblers by user config instr_filter_targets(&instruction, &target_config) @@ -97,10 +93,6 @@ pub fn main() -> anyhow::Result<()> { let xml_conts_x86_64 = include_str!("../../opcodes/x86_64.xml"); populate_instructions(xml_conts_x86_64)? .into_iter() - .map(|mut instruction| { - instruction.arch = Some(Arch::X86_64); - instruction - }) .map(|instruction| { // filter out assemblers by user config instr_filter_targets(&instruction, &target_config) @@ -127,10 +119,6 @@ pub fn main() -> anyhow::Result<()> { let xml_conts_regs_x86 = include_str!("../../registers/x86.xml"); populate_registers(xml_conts_regs_x86)? .into_iter() - .map(|mut reg| { - reg.arch = Some(Arch::X86); - reg - }) .collect() } else { Vec::new() @@ -141,10 +129,6 @@ pub fn main() -> anyhow::Result<()> { let xml_conts_regs_x86_64 = include_str!("../../registers/x86_64.xml"); populate_registers(xml_conts_regs_x86_64)? .into_iter() - .map(|mut reg| { - reg.arch = Some(Arch::X86_64); - reg - }) .collect() } else { Vec::new() @@ -154,17 +138,32 @@ pub fn main() -> anyhow::Result<()> { populate_name_to_register_map(Arch::X86, &x86_registers, &mut names_to_registers); populate_name_to_register_map(Arch::X86_64, &x86_64_registers, &mut names_to_registers); + let gas_directives = if target_config.assemblers.gas { + info!("Populating directive set -> Gas..."); + let xml_conts_gas = include_str!("../../directives/gas_directives.xml"); + populate_directives(xml_conts_gas)?.into_iter().collect() + } else { + Vec::new() + }; + + let mut names_to_directives = NameToDirectiveMap::new(); + populate_name_to_directive_map(Assembler::Gas, &gas_directives, &mut names_to_directives); + let instr_completion_items = get_completes(&names_to_instructions, Some(CompletionItemKind::OPERATOR)); let reg_completion_items = get_completes(&names_to_registers, Some(CompletionItemKind::VARIABLE)); + let directive_completion_items = + get_completes(&names_to_directives, Some(CompletionItemKind::OPERATOR)); main_loop( &connection, initialization_params, &names_to_instructions, + &names_to_directives, &names_to_registers, &instr_completion_items, + &directive_completion_items, ®_completion_items, )?; io_threads.join()?; @@ -174,12 +173,15 @@ pub fn main() -> anyhow::Result<()> { Ok(()) } +#[allow(clippy::too_many_arguments)] fn main_loop( connection: &Connection, params: serde_json::Value, names_to_instructions: &NameToInstructionMap, + names_to_directives: &NameToDirectiveMap, names_to_registers: &NameToRegisterMap, instruction_completion_items: &[CompletionItem], + directive_completion_items: &[CompletionItem], register_completion_items: &[CompletionItem], ) -> anyhow::Result<()> { let _params: InitializeParams = serde_json::from_value(params).unwrap(); @@ -210,8 +212,12 @@ fn main_loop( // format response match word { Ok(word) => { - let hover_res = - get_hover_resp(&word, names_to_instructions, names_to_registers); + let hover_res = get_hover_resp( + &word, + names_to_instructions, + names_to_directives, + names_to_registers, + ); match hover_res { Some(_) => { let result = serde_json::to_value(&hover_res).unwrap(); @@ -248,6 +254,7 @@ fn main_loop( &mut tree, ¶ms, instruction_completion_items, + directive_completion_items, register_completion_items, ); match comp_res { diff --git a/src/lib.rs b/src/lib.rs index c5506bd6..ce9d43e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,6 @@ pub mod x86_parser; pub use lsp::*; pub use types::*; pub use x86_parser::{ - populate_instructions, populate_name_to_instruction_map, populate_name_to_register_map, - populate_registers, + populate_directives, populate_instructions, populate_name_to_directive_map, + populate_name_to_instruction_map, populate_name_to_register_map, populate_registers, }; diff --git a/src/lsp.rs b/src/lsp.rs index 9f977581..4f200b44 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -1,5 +1,8 @@ use crate::types::Column; -use crate::{Arch, Completable, Hoverable, Instruction, NameToInstructionMap, TargetConfig}; +use crate::{ + Arch, ArchOrAssembler, Assembler, Completable, Hoverable, Instruction, NameToInstructionMap, + TargetConfig, +}; use dirs::config_dir; use log::{error, info, log, log_enabled}; use lsp_textdocument::FullTextDocument; @@ -129,12 +132,12 @@ pub fn text_doc_change_to_ts_edit( /// Given a NameTo_SomeItem_ map, returns a `Vec` for the items /// contained within the map -pub fn get_completes( - map: &HashMap<(Arch, &str), T>, +pub fn get_completes( + map: &HashMap<(U, &str), T>, kind: Option, ) -> Vec { map.iter() - .map(|((_arch, name), item_info)| { + .map(|((_arch_or_asm, name), item_info)| { let value = format!("{}", item_info); CompletionItem { @@ -150,17 +153,23 @@ pub fn get_completes( .collect() } -pub fn get_hover_resp( +pub fn get_hover_resp( word: &str, instruction_map: &HashMap<(Arch, &str), T>, - register_map: &HashMap<(Arch, &str), S>, + directive_map: &HashMap<(Assembler, &str), U>, + register_map: &HashMap<(Arch, &str), V>, ) -> Option { - let instr_lookup = lookup_hover_resp(word, instruction_map); + let instr_lookup = lookup_hover_resp_by_arch(word, instruction_map); if instr_lookup.is_some() { return instr_lookup; } - let reg_lookup = lookup_hover_resp(word, register_map); + let directive_lookup = lookup_hover_resp_by_assembler(word, directive_map); + if directive_lookup.is_some() { + return directive_lookup; + } + + let reg_lookup = lookup_hover_resp_by_arch(word, register_map); if reg_lookup.is_some() { return reg_lookup; } @@ -174,8 +183,11 @@ pub fn get_hover_resp( None } -fn lookup_hover_resp(word: &str, map: &HashMap<(Arch, &str), T>) -> Option { - let (x86_res, x86_64_res) = search_for_hoverable(word, map); +fn lookup_hover_resp_by_arch( + word: &str, + map: &HashMap<(Arch, &str), T>, +) -> Option { + let (x86_res, x86_64_res) = search_for_hoverable_by_arch(word, map); match (x86_res.is_some(), x86_64_res.is_some()) { (true, _) | (_, true) => { @@ -205,6 +217,36 @@ fn lookup_hover_resp(word: &str, map: &HashMap<(Arch, &str), T>) - } } +fn lookup_hover_resp_by_assembler( + word: &str, + map: &HashMap<(Assembler, &str), T>, +) -> Option { + let (gas_res, go_res) = search_for_hoverable_by_assembler(word, map); + + match (gas_res.is_some(), go_res.is_some()) { + (true, _) | (_, true) => { + let mut value = String::new(); + if let Some(gas_res) = gas_res { + value += &format!("{}", gas_res); + } + if let Some(go_res) = go_res { + value += &format!("{}{}", if gas_res.is_some() { "\n\n" } else { "" }, go_res); + } + Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value, + }), + range: None, + }) + } + _ => { + // don't know of this word + None + } + } +} + fn get_demangle_resp(word: &str) -> Option { let name = Name::new(word, NameMangling::Mangled, Language::Unknown); let demangled = name.demangle(DemangleOptions::complete()); @@ -255,18 +297,31 @@ pub fn get_comp_resp( curr_tree: &mut Option, params: &CompletionParams, instr_comps: &[CompletionItem], + dir_comps: &[CompletionItem], reg_comps: &[CompletionItem], ) -> Option { let cursor_line = params.text_document_position.position.line as usize; let cursor_char = params.text_document_position.position.character as usize; - // prepend register names with "%" in GAS if let Some(ctx) = params.context.as_ref() { if ctx.trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER { - return Some(CompletionList { - is_incomplete: true, - items: filtered_comp_list(reg_comps), - }); + match ctx.trigger_character.as_ref().map(|s| s.as_ref()) { + // prepend GAS registers with "%" + Some("%") => { + return Some(CompletionList { + is_incomplete: true, + items: filtered_comp_list(reg_comps), + }); + } + // prepend GAS directives with "." + Some(".") => { + return Some(CompletionList { + is_incomplete: true, + items: filtered_comp_list(dir_comps), + }); + } + _ => {} + } } } @@ -286,6 +341,30 @@ pub fn get_comp_resp( }); let curr_doc = curr_doc.as_bytes(); + static QUERY_DIRECTIVE: Lazy = Lazy::new(|| { + tree_sitter::Query::new( + tree_sitter_asm::language(), + "(meta kind: (meta_ident) @directive)", + ) + .unwrap() + }); + let matches_iter = cursor.matches(&QUERY_DIRECTIVE, tree.root_node(), curr_doc); + + for match_ in matches_iter { + let caps = match_.captures; + for cap in caps.iter() { + let arg_start = cap.node.range().start_point; + let arg_end = cap.node.range().end_point; + if cursor_matches!(cursor_line, cursor_char, arg_start, arg_end) { + let items = filtered_comp_list(dir_comps); + return Some(CompletionList { + is_incomplete: true, + items, + }); + } + } + } + static QUERY_INSTR_ANY: Lazy = Lazy::new(|| { tree_sitter::Query::new( tree_sitter_asm::language(), @@ -485,7 +564,8 @@ pub fn get_sig_help_resp( let mut value = String::new(); let mut has_x86 = false; let mut has_x86_64 = false; - let (x86_info, x86_64_info) = search_for_hoverable(instr_name, instr_info); + let (x86_info, x86_64_info) = + search_for_hoverable_by_arch(instr_name, instr_info); if let Some(sig) = x86_info { for form in sig.forms.iter() { if let Some(ref gas_name) = form.gas_name { @@ -607,7 +687,7 @@ pub fn get_goto_def_resp( // If issue is resolved, can add a separate lifetime "'b" to "word" // parameter such that 'a: 'b // For now, using 'a for both isn't strictly necessary, but fits our use case -fn search_for_hoverable<'a, T: Hoverable>( +fn search_for_hoverable_by_arch<'a, T: Hoverable>( word: &'a str, map: &'a HashMap<(Arch, &str), T>, ) -> (Option<&'a T>, Option<&'a T>) { @@ -617,6 +697,16 @@ fn search_for_hoverable<'a, T: Hoverable>( (x86_res, x86_64_res) } +fn search_for_hoverable_by_assembler<'a, T: Hoverable>( + word: &'a str, + map: &'a HashMap<(Assembler, &str), T>, +) -> (Option<&'a T>, Option<&'a T>) { + let gas_res = map.get(&(Assembler::Gas, word)); + let go_res = map.get(&(Assembler::Go, word)); + + (gas_res, go_res) +} + /// Searches for global config in ~/.config/asm-lsp, then the project's directory /// Project specific configs will override global configs pub fn get_target_config(params: &InitializeParams) -> TargetConfig { diff --git a/src/types.rs b/src/types.rs index 9603d3fb..20c1c2a8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -167,6 +167,101 @@ impl std::fmt::Display for InstructionForm { } } +// Directive ------------------------------------------------------------------------------------ +#[derive(Debug, Clone)] +pub struct Directive { + pub name: String, + pub alt_names: Vec, + pub signatures: Vec, + pub description: String, + pub deprecated: bool, + pub url: Option, + pub assembler: Option, +} + +impl Hoverable for &Directive {} +impl Completable for &Directive {} + +impl Default for Directive { + fn default() -> Self { + let name = String::new(); + let alt_names = vec![]; + let signatures = vec![]; + let description = String::new(); + let deprecated = false; + let url = None; + let assembler = None; + + Self { + name, + alt_names, + signatures, + description, + deprecated, + url, + assembler, + } + } +} + +impl std::fmt::Display for Directive { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // basic fields + let header: String; + if let Some(assembler) = &self.assembler { + header = format!( + ".{} [{}]{}", + &self.name, + assembler.as_ref(), + if self.deprecated { + "\n**DEPRECATED**" + } else { + "" + } + ); + } else { + header = self.name.clone(); + } + + let mut v: Vec<&str> = vec![&header, &self.description, "\n"]; + + // signature(s) + let mut sigs = String::new(); + for sig in self.signatures.iter() { + sigs += &format!("- {}\n", sig); + } + v.push(&sigs); + + // url + let more_info: String; + match &self.url { + None => {} + Some(url_) => { + more_info = format!("\nMore info: {}", url_); + v.push(&more_info); + } + } + + let s = v.join("\n"); + write!(f, "{}", s)?; + Ok(()) + } +} + +impl<'own> Directive { + /// get the names of all the associated directives + pub fn get_associated_names(&'own self) -> Vec<&'own str> { + let mut names = Vec::<&'own str>::new(); + names.push(&self.name); + + for name in &self.alt_names { + names.push(name); + } + + names + } +} + // Register --------------------------------------------------------------------------------------- #[derive(Debug, Clone)] pub struct Register { @@ -287,10 +382,15 @@ pub type NameToInstructionMap<'instruction> = pub type NameToRegisterMap<'register> = HashMap<(Arch, &'register str), &'register Register>; +pub type NameToDirectiveMap<'directive> = + HashMap<(Assembler, &'directive str), &'directive Directive>; + // Define a trait for types we display on Hover Requests so we can avoid some duplicate code pub trait Hoverable: Display + Clone + Copy {} // Define a trait for types we display on Completion Requests so we can avoid some duplicate code pub trait Completable: Display {} +// Define a trait for the enums we use to distinguish between different Architectures and Assemblers +pub trait ArchOrAssembler {} #[derive(Debug, Clone, EnumString, AsRefStr)] pub enum XMMMode { @@ -304,12 +404,24 @@ pub enum MMXMode { MMX, } -#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, EnumString, AsRefStr)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Display, EnumString, AsRefStr)] pub enum Arch { + #[strum(serialize = "x86")] X86, + #[strum(serialize = "x86-64")] X86_64, } +impl ArchOrAssembler for Arch {} + +#[derive(Debug, Display, Hash, PartialEq, Eq, Clone, Copy, EnumString, AsRefStr)] +pub enum Assembler { + Gas, + Go, +} + +impl ArchOrAssembler for Assembler {} + #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, EnumString, AsRefStr, Display)] pub enum RegisterType { #[strum(serialize = "General Purpose Register")] diff --git a/src/x86_parser.rs b/src/x86_parser.rs index cb109970..9a1e4cf3 100644 --- a/src/x86_parser.rs +++ b/src/x86_parser.rs @@ -2,6 +2,7 @@ use crate::types::*; use anyhow::anyhow; use log::{debug, error, info, warn}; +use quick_xml::escape::unescape; use quick_xml::events::attributes::Attribute; use quick_xml::events::Event; use quick_xml::name::QName; @@ -32,6 +33,7 @@ pub fn populate_instructions(xml_contents: &str) -> anyhow::Result = None; debug!("Parsing XML contents..."); loop { @@ -39,9 +41,19 @@ pub fn populate_instructions(xml_contents: &str) -> anyhow::Result { match e.name() { + QName(b"InstructionSet") => { + for attr in e.attributes() { + let Attribute { key, value } = attr.unwrap(); + if let Ok("name") = str::from_utf8(key.into_inner()) { + arch = Arch::from_str(unsafe { str::from_utf8_unchecked(&value) }) + .ok(); + } + } + } QName(b"Instruction") => { // start of a new instruction curr_instruction = Instruction::default(); + curr_instruction.arch = arch; // iterate over the attributes for attr in e.attributes() { @@ -322,6 +334,7 @@ pub fn populate_registers(xml_contents: &str) -> anyhow::Result> { // ref to the register that's currently under construction let mut curr_register = Register::default(); let mut curr_bit_flag = RegisterBitInfo::default(); + let mut arch: Option = None; debug!("Parsing XML contents..."); loop { @@ -329,9 +342,19 @@ pub fn populate_registers(xml_contents: &str) -> anyhow::Result> { // start event ------------------------------------------------------------------------ Ok(Event::Start(ref e)) => { match e.name() { + QName(b"InstructionSet") => { + for attr in e.attributes() { + let Attribute { key, value } = attr.unwrap(); + if let Ok("name") = str::from_utf8(key.into_inner()) { + arch = Arch::from_str(unsafe { str::from_utf8_unchecked(&value) }) + .ok(); + } + } + } QName(b"Register") => { // start of a new register curr_register = Register::default(); + curr_register.arch = arch; // iterate over the attributes for attr in e.attributes() { @@ -410,7 +433,7 @@ pub fn populate_registers(xml_contents: &str) -> anyhow::Result> { Ok(Event::End(ref e)) => { match e.name() { QName(b"Register") => { - // finish instruction + // finish register registers_map.insert(curr_register.name.clone(), curr_register.clone()); } QName(b"Flag") => { @@ -444,6 +467,114 @@ pub fn populate_name_to_register_map<'register>( } } +pub fn populate_directives(xml_contents: &str) -> anyhow::Result> { + let mut directives_map = HashMap::::new(); + + // iterate through the XML -------------------------------------------------------------------- + let mut reader = Reader::from_str(xml_contents); + reader.trim_text(true); + + // ref to the assembler directive that's currently under construction + let mut curr_directive = Directive::default(); + let mut assembler: Option = None; + + debug!("Parsing XML contents..."); + loop { + match reader.read_event() { + // start event ------------------------------------------------------------------------ + Ok(Event::Start(ref e)) => { + match e.name() { + QName(b"Assembler") => { + for attr in e.attributes() { + let Attribute { key, value } = attr.unwrap(); + if let Ok("name") = str::from_utf8(key.into_inner()) { + assembler = Assembler::from_str(unsafe { + str::from_utf8_unchecked(&value) + }) + .ok(); + } + } + } + QName(b"Directive") => { + // start of a new directive + curr_directive = Directive::default(); + curr_directive.assembler = assembler; + + // iterate over the attributes + for attr in e.attributes() { + let Attribute { key, value } = attr.unwrap(); + match str::from_utf8(key.into_inner()).unwrap() { + "name" => { + let name = + String::from(unsafe { str::from_utf8_unchecked(&value) }); + curr_directive.alt_names.push(name.to_uppercase()); + curr_directive.name = name; + } + "md_description" => { + let description = + String::from(unsafe { str::from_utf8_unchecked(&value) }); + curr_directive.description = + unescape(&description).unwrap().to_string(); + } + "deprecated" => { + curr_directive.deprecated = FromStr::from_str(unsafe { + str::from_utf8_unchecked(&value) + }) + .unwrap(); + } + "url_fragment" => { + curr_directive.url = Some(format!( + "https://sourceware.org/binutils/docs-2.41/as/{}.html", + unsafe { str::from_utf8_unchecked(&value) } + )); + } + _ => {} + } + } + } + QName(b"Signatures") => {} // it's just a wrapper... + QName(b"Signature") => { + for attr in e.attributes() { + let Attribute { key, value } = attr.unwrap(); + if let Ok("sig") = str::from_utf8(key.into_inner()) { + let sig = String::from(unsafe { str::from_utf8_unchecked(&value) }); + curr_directive + .signatures + .push(unescape(&sig).unwrap().to_string()); + } + } + } + _ => {} // unknown event + } + } + // end event -------------------------------------------------------------------------- + Ok(Event::End(ref e)) => { + if let QName(b"Directive") = e.name() { + // finish directive + directives_map.insert(curr_directive.name.clone(), curr_directive.clone()); + } + } + Ok(Event::Eof) => break, + Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), + _ => (), // rest of events that we don't consider + } + } + + Ok(directives_map.into_values().collect()) +} + +pub fn populate_name_to_directive_map<'directive>( + assem: Assembler, + directives: &'directive Vec, + names_to_directives: &mut NameToDirectiveMap<'directive>, +) { + for register in directives { + for name in ®ister.get_associated_names() { + names_to_directives.insert((assem, name), register); + } + } +} + fn get_docs_body(x86_online_docs: &str) -> Option { // provide a URL example page ----------------------------------------------------------------- // 1. If the cache refresh option is enabled or the cache doesn't exist, attempt to fetch the