diff --git a/crates/analyzer/src/builder.rs b/crates/analyzer/src/builder.rs deleted file mode 100644 index 7f8ecfc..0000000 --- a/crates/analyzer/src/builder.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::{ - collections::HashMap, - env, fs, - path::{MAIN_SEPARATOR, PathBuf}, - process::{Command, Stdio}, -}; - -use cargo_metadata::{CompilerMessage, Message}; -use mist_parser::rev_mapper; - -#[derive(Debug, Clone)] -pub struct MistDiagnosticMessage { - pub message: String, - pub file_path: PathBuf, - pub file_name: String, - pub line: usize, - pub column: usize, -} - -#[derive(Debug, Clone)] -pub enum MistDiagnostic { - Error(MistDiagnosticMessage), - Warning(MistDiagnosticMessage), - Rust(CompilerMessage), -} - -pub fn build(mut args: Vec, root: PathBuf) -> Vec { - args.insert(1, "--message-format=json".to_string()); - - let is_root = root == env::current_dir().expect("Failed getting env"); - - let mut command = Command::new("cargo") - .args(args) - .stdout(Stdio::piped()) - .spawn() - .expect("Failed to run cargo"); - - let mut reader = std::io::BufReader::new(command.stdout.take().expect("Failed to get reader")); - - let mut diagnostics = Vec::new(); - - let mut mapping: HashMap = HashMap::new(); - - let mist_src = format!(".mist{MAIN_SEPARATOR}lsp"); - - for message in cargo_metadata::Message::parse_stream(&mut reader) { - match message { - Ok(Message::CompilerMessage(msg)) => { - for span in &msg.message.spans { - if span.is_primary { - let rust_path = root.join(&span.file_name); - - let mist_file = span - .file_name - .replacen(&mist_src, "src", 1) - .trim_end_matches(".rs") - .to_string() - + ".mist"; - - let mist_path = root.join(&mist_file); - - if !fs::exists(&mist_path).expect("Unable to check if mist file exists") { - diagnostics.push(MistDiagnostic::Rust(msg)); - break; - } - - // let map = mapping.entry(rust_path.clone()).or_insert_with(|| { - // get_mapping( - // &fs::read_to_string(rust_path) - // .expect("Failed to read file for mapping"), - // ) - // }); - - // let mist_span = - // find_mapping(&map, &RustMap(span.line_end, span.column_start)) - // .expect("Unable to find mapping"); - - // let mist_msg = MistDiagnosticMessage { - // message: format!( - // "{}: {}", - // msg.message.message, - // span.label.clone().unwrap_or_default() - // ), - // file_name: if is_root { - // mist_file - // } else { - // mist_path.to_string_lossy().to_string() - // }, - // file_path: mist_path, - // line: mist_span.1.0, - // column: mist_span.1.1, - // }; - - // match msg.message.level { - // cargo_metadata::diagnostic::DiagnosticLevel::Error => { - // diagnostics.push(MistDiagnostic::Error(mist_msg)) - // } - - // cargo_metadata::diagnostic::DiagnosticLevel::Warning => { - // diagnostics.push(MistDiagnostic::Warning(mist_msg)) - // } - - // _ => {} - // } - } - } - } - - Ok(Message::BuildFinished(_)) => { - command.wait().unwrap(); - - return diagnostics; - } - - Ok(Message::TextLine(text)) => println!("{text}"), - _ => {} - } - } - - diagnostics -} - -pub fn get_line( - files: &mut HashMap>, - msg: &MistDiagnosticMessage, -) -> Option { - let src_path = msg.file_path.clone(); - - let lines = files.entry(src_path.clone()).or_insert_with(|| { - fs::read_to_string(src_path) - .expect("Unable to read mist file") - .lines() - .into_iter() - .map(String::from) - .collect() - }); - - lines.get(msg.line - 1).map(|v| v.trim().to_string()) -} diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 5569ad2..f60c225 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -1,21 +1,20 @@ -pub mod builder; pub mod rust_analyzer; pub mod transpiler; -use std::collections::{HashMap, HashSet}; -use std::path::{Component, PathBuf}; +use std::collections::HashMap; +use std::path::{Component, Path, PathBuf}; use std::sync::Arc; -use mist_parser::rev_mapper; +use mist_parser::rev_mapper::{Mapping, MistMap, RustMap}; use ropey::Rope; +use serde::Deserialize; +use serde_json::Value; use tokio::sync::Mutex; -use tower_lsp::jsonrpc::Result; -use tower_lsp::lsp_types::notification::Notification; use tower_lsp::lsp_types::{self, *}; use tower_lsp::{Client, LanguageServer, LspService, Server}; -use crate::builder::MistDiagnostic; use crate::rust_analyzer::RustAnalyzer; +use crate::transpiler::transpile_mist; #[derive(Debug)] struct Backend { @@ -23,23 +22,21 @@ struct Backend { workspace_folder: Arc>>, previous_diagnostics: Arc>>>, rust_analyzer: Arc>, - mapping: Arc>>>, + mapping: Arc>>, documents: Arc>>, + doc_versions: Arc>>, + notification_rx: Arc>>>, } -/// Helper function to force percent-encoding on Windows drive colons -/// so that the URLs exactly match what VS Code/LSP clients send. fn clean_lsp_url(path: &std::path::Path) -> Option { let mut url_str = Url::from_file_path(path).ok()?.to_string(); - // Look for Windows patterns like file:///D: or file:///d: and switch to %3A - if url_str.starts_with("file:///I:") || url_str.starts_with("file:///i:") || // Catch-all or explicit check: - (url_str.len() > 10 && url_str.as_bytes()[11] == b':') + if url_str.starts_with("file:///I:") + || url_str.starts_with("file:///i:") + || (url_str.len() > 10 && url_str.as_bytes()[11] == b':') { - // Safely replace the first colon occurring after "file:///" if let Some(pos) = url_str.find(':') { if pos == 11 { - // Double check it's the drive letter colon url_str.replace_range(pos..=pos, "%3A"); } } @@ -48,48 +45,183 @@ fn clean_lsp_url(path: &std::path::Path) -> Option { Url::parse(&url_str).ok() } +fn lsp_pos_to_rust_map(pos: &Position) -> RustMap { + RustMap(pos.line as usize + 1, pos.character as usize) +} + +fn rust_map_to_lsp_pos(map: &RustMap) -> Position { + Position { + line: map.0.saturating_sub(1) as u32, + character: map.1 as u32, + } +} + +fn lsp_pos_to_mist_map(pos: &Position) -> MistMap { + MistMap(pos.line as usize + 1, pos.character as usize) +} + +fn mist_map_to_lsp_pos(map: &MistMap) -> Position { + Position { + line: map.0.saturating_sub(1) as u32, + character: map.1 as u32, + } +} + +fn mist_to_rust_path(mist_path: &Path) -> PathBuf { + let mut path = mist_path.to_path_buf(); + path.set_extension("rs"); + + let comps: Vec = path.components().collect(); + if let Some(pos) = comps.iter().rposition(|c| c.as_os_str() == "src") { + let replacement = Path::new(".mist/src"); + let mut new_comps: Vec = comps[..pos].to_vec(); + new_comps.extend(replacement.components()); + new_comps.extend(&comps[pos + 1..]); + new_comps.iter().collect() + } else { + path + } +} + +fn rust_to_mist_path(rust_path: &Path) -> PathBuf { + let mut path = rust_path.to_path_buf(); + path.set_extension("mist"); + + let comps: Vec = path.components().collect(); + + let pattern: Vec = Path::new(".mist/src").components().collect(); + + if let Some(pos) = comps + .windows(pattern.len()) + .rposition(|window| window == pattern.as_slice()) + { + let mut new_comps = comps.clone(); + new_comps.splice( + pos..pos + pattern.len(), + std::iter::once(Component::Normal(std::ffi::OsStr::new("src"))), + ); + new_comps.iter().collect() + } else { + path + } +} + +fn mist_uri_to_rust_uri(mist_uri: &Url) -> Option { + let mist_path = mist_uri.to_file_path().ok()?; + let rust_path = mist_to_rust_path(&mist_path); + clean_lsp_url(&rust_path) +} + +fn rust_uri_to_mist_uri(rust_uri: &Url) -> Option { + let rust_path = rust_uri.to_file_path().ok()?; + let mist_path = rust_to_mist_path(&rust_path); + clean_lsp_url(&mist_path) +} + impl Backend { - async fn get_mist_location(&self, rs_loc: Location) -> Location { - let rs_path = match rs_loc.uri.to_file_path() { - Ok(path) => path, - Err(_) => return rs_loc, + async fn map_mist_to_rust_pos( + &self, + mist_uri: &Url, + mist_pos: &Position, + ) -> Option<(Url, Position)> { + let rust_uri = mist_uri_to_rust_uri(mist_uri)?; + let rust_path = rust_uri.to_file_path().ok()?; + + let mist_target = lsp_pos_to_mist_map(mist_pos); + let mapping = self.mapping.lock().await.get(&rust_path)?.clone(); + let (rust_map, _) = mapping.find_by_mist(&mist_target)?; + + let pos = rust_map_to_lsp_pos(&rust_map); + Some((rust_uri, pos)) + } + + async fn map_rust_to_mist_pos( + &self, + rust_uri: &Url, + rust_pos: &Position, + ) -> Option<(Url, Position)> { + let mist_uri = rust_uri_to_mist_uri(rust_uri)?; + let rust_path = rust_uri.to_file_path().ok()?; + + let rust_target = lsp_pos_to_rust_map(rust_pos); + let mapping = self.mapping.lock().await.get(&rust_path)?.clone(); + let (_, mist_map) = mapping.find(&rust_target)?; + + let pos = mist_map_to_lsp_pos(&mist_map); + Some((mist_uri, pos)) + } + + async fn handle_transpile_and_notify(&self, mist_path: &Path, source: &str) { + let transpiled = match transpile_mist(mist_path, source) { + Ok(t) => t, + Err(e) => { + eprintln!("transpile error for {:?}: {e}", mist_path); + let diag = Diagnostic { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 1, + }, + }, + severity: Some(DiagnosticSeverity::ERROR), + source: Some("mist".to_string()), + message: format!("Transpile error: {e}"), + ..Default::default() + }; + if let Some(uri) = clean_lsp_url(mist_path) { + self.publish_diagnostics(uri, vec![diag]).await; + } + return; + } }; - match self.mapping.lock().await.get(&rs_path) { - Some(mapping) => { - rs_loc - // FIX: Match the find_mapping operation instead of calling .expect("Failed to map") - // match rev_mapper::find_mapping( - // mapping, - // &rev_mapper::RustMap( - // rs_loc.range.start.line as usize, - // rs_loc.range.start.character as usize, - // ), - // ) { - // Some((_, rev_mapper::MistMap(line, character))) => Location { - // uri: Url::from_file_path(from_rust_to_mist(rs_path)).unwrap(), - // range: Range { - // start: Position { - // line: line as u32 - 1, - // character: character as u32, - // }, - // end: Position { - // line: line as u32 - 1, - // character: character as u32 + 1, - // }, - // }, - // }, - // None => rs_loc, - // } + if let Some(rust_uri) = clean_lsp_url(&transpiled.rust_path) { + let mut ra = self.rust_analyzer.lock().await; + let mut map = self.mapping.lock().await; + let mut versions = self.doc_versions.lock().await; + + let version = versions.entry(mist_path.to_path_buf()).or_insert(0); + *version += 1; + let current_version = *version; + + if map.contains_key(&transpiled.rust_path) { + let _ = ra + .did_change(rust_uri, &transpiled.rust_content, current_version) + .await; + } else { + let _ = ra.did_open(rust_uri, &transpiled.rust_content).await; } - None => rs_loc, + + map.insert(transpiled.rust_path, transpiled.mapping); + } + } + + async fn publish_diagnostics(&self, uri: Url, diagnostics: Vec) { + let key = uri.clone(); + let prev = self.previous_diagnostics.lock().await.get(&key).cloned(); + + if prev.as_ref() != Some(&diagnostics) { + self.previous_diagnostics + .lock() + .await + .insert(key, diagnostics.clone()); + self.client + .publish_diagnostics(uri, diagnostics, None) + .await; } } } #[tower_lsp::async_trait] impl LanguageServer for Backend { - async fn initialize(&self, params: InitializeParams) -> Result { + async fn initialize( + &self, + params: InitializeParams, + ) -> tower_lsp::jsonrpc::Result { let mut res = InitializeResult::default(); res.capabilities.text_document_sync = Some(TextDocumentSyncCapability::Options( @@ -103,7 +235,7 @@ impl LanguageServer for Backend { )); res.capabilities.completion_provider = Some(CompletionOptions { - resolve_provider: Some(true), + resolve_provider: Some(false), trigger_characters: Some(vec![ ":".to_owned(), ".".to_owned(), @@ -120,6 +252,7 @@ impl LanguageServer for Backend { }); res.capabilities.definition_provider = Some(OneOf::Left(true)); + res.capabilities.hover_provider = Some(HoverProviderCapability::Simple(true)); let folder_path = params .workspace_folders @@ -131,51 +264,81 @@ impl LanguageServer for Backend { *self.workspace_folder.lock().await = Some(path.clone()); } - let workspace_folder = self.workspace_folder.clone(); - - let analyzer = self.rust_analyzer.clone(); - - let documents = self.documents.clone(); - + let ws = self.workspace_folder.clone(); + let ra = self.rust_analyzer.clone(); let mapping = self.mapping.clone(); + let documents = self.documents.clone(); + let client = self.client.clone(); + let previous_diagnostics = self.previous_diagnostics.clone(); + let notification_rx = self.notification_rx.clone(); tokio::spawn(async move { - if let Some(root) = &*workspace_folder.lock().await { + if let Some(root) = &*ws.lock().await { let src_root = root.join("src"); - transpiler::build(root); + if let Err(e) = ra.lock().await.initialize(root).await { + eprintln!("Failed to initialize rust-analyzer: {e}"); + return; + } - analyzer - .lock() - .await - .initialize(root) - .await - .expect("Failed to initialize rust analyzer"); + if let Err(e) = ra.lock().await.initialized().await { + eprintln!("rust-analyzer initialized failed: {e}"); + return; + } - // ---- LOAD ALL .MIST FILES ---- let mut files = Vec::new(); collect_mist_files(&src_root, &mut files); - for file in files { - if let Ok(text) = std::fs::read_to_string(&file) { - if let Ok(transpiled) = transpiler::transpile_text(&text) { - let rust_path = from_mist_to_rust(file.clone()); + for file in &files { + if let Ok(text) = std::fs::read_to_string(file) { + documents + .lock() + .await + .insert(file.clone(), Rope::from_str(&text)); + } + } - // store document - documents + eprintln!("Loaded {} mist files", files.len()); + + let mapping_c = mapping.clone(); + let documents_c = documents.clone(); + let ra_c = ra.clone(); + + for file in &files { + if let Some(source) = documents_c.lock().await.get(file).map(|r| r.to_string()) + { + let transpiled = match transpile_mist(file, &source) { + Ok(t) => t, + Err(e) => { + eprintln!("transpile error for {:?}: {e}", file); + continue; + } + }; + + if let Some(rust_uri) = clean_lsp_url(&transpiled.rust_path) { + let _ = ra_c .lock() .await - .insert(file.clone(), Rope::from_str(&text)); - - // store mapping - // mapping - // .lock() - // .await - // .insert(rust_path, rev_mapper::get_mapping(&transpiled)); + .did_open(rust_uri, &transpiled.rust_content) + .await; + mapping_c + .lock() + .await + .insert(transpiled.rust_path, transpiled.mapping); } } } + if let Some(rx) = notification_rx.lock().await.take() { + tokio::spawn(handle_ra_notifications( + rx, + client, + mapping, + documents, + previous_diagnostics, + )); + } + eprintln!("Ready to use"); } }); @@ -184,368 +347,385 @@ impl LanguageServer for Backend { } async fn initialized(&self, _: InitializedParams) { - self.rust_analyzer - .lock() - .await - .initialized() - .await - .expect("Failed to initialize rust analyzer"); - self.client .log_message(MessageType::INFO, "server initialized!") .await; } - async fn shutdown(&self) -> Result<()> { + async fn shutdown(&self) -> tower_lsp::jsonrpc::Result<()> { Ok(()) } - async fn did_save(&self, _: DidSaveTextDocumentParams) { - self.client - .log_message(MessageType::INFO, "Processing did_save event") - .await; + async fn did_open(&self, params: DidOpenTextDocumentParams) { + let uri = params.text_document.uri; + let mist_path = match uri.to_file_path() { + Ok(p) => p, + Err(_) => { + return; + } + }; + let source = params.text_document.text; - if let Some(root) = &*self.workspace_folder.lock().await { - transpiler::build(root); - } + self.documents + .lock() + .await + .insert(mist_path.clone(), Rope::from_str(&source)); - let workspace_root = match self.workspace_folder.lock().await.clone() { - Some(root) => root, - None => { - self.client - .log_message( - MessageType::WARNING, - "Skipping diagnostics: No active workspace folder found", - ) - .await; + self.handle_transpile_and_notify(&mist_path, &source).await; + } + + async fn did_change(&self, params: DidChangeTextDocumentParams) { + let uri = params.text_document.uri; + let mist_path = match uri.to_file_path() { + Ok(p) => p, + Err(_) => { return; } }; - let diagnostics_raw = builder::build( - vec![ - "check".to_string(), - "--workspace".to_string(), - "--all-targets".to_string(), - ], - workspace_root, - ); + if let Some(change) = params.content_changes.into_iter().last() { + let source = change.text; - let mut diagnostics = HashMap::new(); - - for diag in diagnostics_raw.iter() { - let (msg, severity) = match diag { - MistDiagnostic::Error(msg) => (msg, DiagnosticSeverity::ERROR), - MistDiagnostic::Warning(msg) => (msg, DiagnosticSeverity::WARNING), - MistDiagnostic::Rust(_) => { - continue; - } - }; - - let line = (msg.line as u32).saturating_sub(1); - let column = (msg.column as u32).saturating_sub(1); - - let url = match clean_lsp_url(&msg.file_path) { - Some(u) => u, - None => continue, - }; - - let diagnostic_item = Diagnostic { - range: Range { - start: Position { - line, - character: column, - }, - end: Position { - line, - character: u32::MAX, - }, - }, - severity: Some(severity), - code: None, - source: Some("mist-analyzer".to_string()), - message: msg.message.clone(), - related_information: None, - tags: None, - data: None, - code_description: None, - }; - - diagnostics - .entry(url) - .or_insert_with(Vec::new) - .push(diagnostic_item); - } - - for (file, _) in self.previous_diagnostics.lock().await.iter() { - self.client - .publish_diagnostics(file.clone(), Vec::new(), None) - .await; - } - - for (file, diag) in &diagnostics { - self.client - .publish_diagnostics(file.clone(), diag.clone(), None) - .await; - } - - *self.previous_diagnostics.lock().await = diagnostics; - } - - async fn did_open(&self, mut params: DidOpenTextDocumentParams) { - if params.text_document.language_id == "mist" { - let original_text = params.text_document.text.clone(); - - self.documents.lock().await.insert( - params.text_document.uri.to_file_path().unwrap(), - Rope::from_str(&original_text), - ); - - params.text_document.language_id = "rust".to_string(); - - match transpiler::transpile_text(&original_text) { - Ok(transpiled_text) => { - params.text_document.text = transpiled_text; - - let rust_path = - from_mist_to_rust(params.text_document.uri.to_file_path().unwrap()); - - params.text_document.uri = Url::from_file_path(&rust_path).unwrap(); - - // self.mapping.lock().await.insert( - // rust_path, - // rev_mapper::get_mapping(¶ms.text_document.text), - // ); - } - Err(e) => { - self.client - .log_message( - MessageType::WARNING, - format!("MIST-LSP: Syntax invalid during open/change: {:?}", e), - ) - .await; - - return; - } - } - } - - if let Ok(mut ra) = self.rust_analyzer.try_lock() { - let _ = ra - .notify(notification::DidOpenTextDocument::METHOD, params) - .await; - } - } - - async fn did_change(&self, mut params: DidChangeTextDocumentParams) { - let mist_path = params.text_document.uri.to_file_path().unwrap(); - - let rust_path = from_mist_to_rust(mist_path.clone()); - - let rust_uri = Url::from_file_path(&rust_path).unwrap(); - - if let Some(change) = params.content_changes.first_mut() { self.documents .lock() .await - .insert(mist_path, Rope::from_str(&change.text)); + .insert(mist_path.clone(), Rope::from_str(&source)); - match transpiler::transpile_text(&change.text) { - Ok(transpiled_text) => { - change.text = transpiled_text; - - // self.mapping - // .lock() - // .await - // .insert(rust_path, rev_mapper::get_mapping(&change.text)); - } - Err(e) => { - self.client - .log_message( - MessageType::WARNING, - format!("MIST-LSP: Syntax invalid during change: {:?}", e), - ) - .await; - - return; - } - } - } - - params.text_document.uri = rust_uri; - - if let Ok(mut ra) = self.rust_analyzer.try_lock() { - let _ = ra - .notify(notification::DidChangeTextDocument::METHOD, params) - .await; + self.handle_transpile_and_notify(&mist_path, &source).await; } } - async fn did_close(&self, mut params: DidCloseTextDocumentParams) { - self.client - .log_message(MessageType::INFO, "MIST-LSP: Processing did_close event") - .await; + async fn did_save(&self, params: DidSaveTextDocumentParams) { + let uri = params.text_document.uri; + let mist_path = match uri.to_file_path() { + Ok(p) => p, + Err(_) => { + return; + } + }; - let rust_path = from_mist_to_rust(params.text_document.uri.to_file_path().unwrap()); + if let Some(text) = params.text { + self.documents + .lock() + .await + .insert(mist_path.clone(), Rope::from_str(&text)); - let mist_path = params.text_document.uri.to_file_path().unwrap(); + self.handle_transpile_and_notify(&mist_path, &text).await; + } + } + + async fn did_close(&self, params: DidCloseTextDocumentParams) { + let uri = params.text_document.uri; + let mist_path = match uri.to_file_path() { + Ok(p) => p, + Err(_) => { + return; + } + }; self.documents.lock().await.remove(&mist_path); + let rust_path = mist_to_rust_path(&mist_path); self.mapping.lock().await.remove(&rust_path); - params.text_document.uri = Url::from_file_path(&rust_path).unwrap(); + if let Some(rust_uri) = clean_lsp_url(&rust_path) { + let _ = self.rust_analyzer.lock().await.did_close(rust_uri).await; + } - if let Ok(mut ra) = self.rust_analyzer.try_lock() { - let _ = ra - .notify(notification::DidCloseTextDocument::METHOD, params) - .await; + self.publish_diagnostics(uri, Vec::new()).await; + } + + async fn completion( + &self, + params: CompletionParams, + ) -> tower_lsp::jsonrpc::Result> { + let mist_uri = params.text_document_position.text_document.uri.clone(); + let mist_pos = params.text_document_position.position; + + let Some((rust_uri, rust_pos)) = self.map_mist_to_rust_pos(&mist_uri, &mist_pos).await + else { + return Ok(None); + }; + + let comp_params = CompletionParams { + text_document_position: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: rust_uri }, + position: rust_pos, + }, + work_done_progress_params: WorkDoneProgressParams { + work_done_token: None, + }, + partial_result_params: PartialResultParams { + partial_result_token: None, + }, + context: params.context, + }; + + match self + .rust_analyzer + .lock() + .await + .request::(comp_params) + .await + { + Ok(Some(CompletionResponse::Array(items))) => { + let cleaned: Vec = + items.into_iter().map(clean_completion_item).collect(); + Ok(Some(CompletionResponse::Array(cleaned))) + } + Ok(Some(CompletionResponse::List(list))) => { + let cleaned: Vec = + list.items.into_iter().map(clean_completion_item).collect(); + Ok(Some(CompletionResponse::List(CompletionList { + is_incomplete: list.is_incomplete, + items: cleaned, + }))) + } + Ok(None) => Ok(None), + Err(e) => { + eprintln!("completion error: {e}"); + Ok(None) + } } } async fn goto_definition( &self, params: GotoDefinitionParams, - ) -> Result> { - let file_path = params - .text_document_position_params - .text_document - .uri - .to_file_path() - .unwrap(); + ) -> tower_lsp::jsonrpc::Result> { + let mist_uri = params.text_document_position_params.text_document.uri; + let mist_pos = params.text_document_position_params.position; - let source = match self.documents.lock().await.get(&file_path) { - Some(doc) => doc.clone(), - None => return Ok(None), + let Some((rust_uri, rust_pos)) = self.map_mist_to_rust_pos(&mist_uri, &mist_pos).await + else { + return Ok(None); }; - let inject = "__mist_23"; - let injected_source = insert_at_position( - &source, - params.text_document_position_params.position.line as usize + 1, - params.text_document_position_params.position.character as usize, - &inject, - ); - - let output = Rope::from_str( - &match transpiler::transpile_text(&injected_source.to_string()) { - Ok(out) => out, - Err(_) => return Ok(None), + let gd_params = GotoDefinitionParams { + text_document_position_params: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: rust_uri }, + position: rust_pos, }, - ); - - let (line, character) = match find_row_col(&output, inject) { - Some(coords) => coords, - None => return Ok(None), - }; - - let uri = - Url::from_file_path(from_mist_to_rust(file_path)).expect("failed to generate rs url"); - - let rs_res = self - .rust_analyzer - .lock() - .await - .request::(lsp_types::GotoDefinitionParams { - text_document_position_params: lsp_types::TextDocumentPositionParams { - position: lsp_types::Position { - line: line as u32, - character: character as u32, - }, - text_document: lsp_types::TextDocumentIdentifier { uri }, - }, - partial_result_params: lsp_types::PartialResultParams::default(), - work_done_progress_params: lsp_types::WorkDoneProgressParams::default(), - }) - .await - .expect("Failed to send to rust"); - - Ok(match rs_res { - Some(GotoDefinitionResponse::Array(arr)) => { - let mut mapped_arr = Vec::new(); - for rs_loc in arr { - let mapped_loc = self.get_mist_location(rs_loc).await; - mapped_arr.push(mapped_loc); - } - Some(GotoDefinitionResponse::Array(mapped_arr)) - } - _ => rs_res, - }) - } - - async fn completion(&self, mut params: CompletionParams) -> Result> { - self.client - .log_message(MessageType::INFO, "COMPLETEING!") - .await; - - let file_path = params - .text_document_position - .text_document - .uri - .to_file_path() - .unwrap(); - - let source = match self.documents.lock().await.get(&file_path) { - Some(doc) => doc.clone(), - None => return Ok(None), - }; - - let inject = "__mist_23"; - let injected_source = insert_at_position( - &source, - params.text_document_position.position.line as usize + 1, - params.text_document_position.position.character as usize, - &inject, - ); - - let output = Rope::from_str( - &match transpiler::transpile_text(&injected_source.to_string()) { - Ok(out) => out, - Err(_) => return Ok(None), + work_done_progress_params: WorkDoneProgressParams { + work_done_token: None, + }, + partial_result_params: PartialResultParams { + partial_result_token: None, }, - ); - - let (line, character) = match find_row_col(&output, inject) { - Some(coords) => coords, - None => return Ok(None), }; - let uri = - Url::from_file_path(from_mist_to_rust(file_path)).expect("failed to generate rs url"); - - params.text_document_position.text_document.uri = uri; - params.text_document_position.position.line = line as u32; - params.text_document_position.position.character = character as u32; - - let rs_res = self - .rust_analyzer - .lock() - .await - .request::(params) - .await - .expect("Failed to send to rust"); - - Ok(rs_res.map(|rs_res| match rs_res { - CompletionResponse::Array(items) => { - CompletionResponse::Array(items.into_iter().map(simplify_item).collect()) - } - - CompletionResponse::List(list) => { - CompletionResponse::Array(list.items.into_iter().map(simplify_item).collect()) - } - })) - } - - async fn completion_resolve(&self, params: CompletionItem) -> Result { match self .rust_analyzer .lock() .await - .request::(params.clone()) + .request::(gd_params) .await { - Ok(o) => Ok(o), - _ => Ok(params), + Ok(Some(GotoDefinitionResponse::Scalar(loc))) => { + let mapped = self.map_rust_to_mist_pos(&loc.uri, &loc.range.start).await; + match mapped { + Some((mist_uri, mist_start)) => { + let mist_range = Range { + start: mist_start, + end: Position { + line: mist_start.line, + character: mist_start.character + 1, + }, + }; + Ok(Some(GotoDefinitionResponse::Scalar(Location { + uri: mist_uri, + range: mist_range, + }))) + } + None => Ok(Some(GotoDefinitionResponse::Scalar(loc))), + } + } + Ok(Some(GotoDefinitionResponse::Array(locs))) => { + let mut mapped = Vec::new(); + for loc in locs { + if let Some((mist_uri, mist_start)) = + self.map_rust_to_mist_pos(&loc.uri, &loc.range.start).await + { + mapped.push(Location { + uri: mist_uri, + range: Range { + start: mist_start, + end: Position { + line: mist_start.line, + character: mist_start.character + 1, + }, + }, + }); + } + } + Ok(Some(GotoDefinitionResponse::Array(mapped))) + } + Ok(Some(GotoDefinitionResponse::Link(links))) => { + let mut mapped = Vec::new(); + for link in links { + if let Some((mist_uri, mist_start)) = self + .map_rust_to_mist_pos(&link.target_uri, &link.target_selection_range.start) + .await + { + mapped.push(LocationLink { + origin_selection_range: link.origin_selection_range, + target_uri: mist_uri, + target_range: Range { + start: mist_start, + end: Position { + line: mist_start.line, + character: mist_start.character + 1, + }, + }, + target_selection_range: Range { + start: mist_start, + end: Position { + line: mist_start.line, + character: mist_start.character + 1, + }, + }, + }); + } + } + Ok(Some(GotoDefinitionResponse::Link(mapped))) + } + Ok(None) => Ok(None), + Err(e) => { + eprintln!("goto_definition error: {e}"); + Ok(None) + } + } + } + + async fn hover(&self, params: HoverParams) -> tower_lsp::jsonrpc::Result> { + let mist_uri = params.text_document_position_params.text_document.uri; + let mist_pos = params.text_document_position_params.position; + + let Some((rust_uri, rust_pos)) = self.map_mist_to_rust_pos(&mist_uri, &mist_pos).await + else { + return Ok(None); + }; + + let h_params = HoverParams { + text_document_position_params: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: rust_uri }, + position: rust_pos, + }, + work_done_progress_params: WorkDoneProgressParams { + work_done_token: None, + }, + }; + + match self + .rust_analyzer + .lock() + .await + .request::(h_params) + .await + { + Ok(hover) => Ok(hover), + Err(e) => { + eprintln!("hover error: {e}"); + Ok(None) + } + } + } +} + +async fn handle_ra_notifications( + mut rx: tokio::sync::mpsc::UnboundedReceiver, + client: Client, + mapping: Arc>>, + _documents: Arc>>, + previous_diagnostics: Arc>>>, +) { + #[derive(Deserialize)] + struct PublishDiagnosticsParams { + uri: Url, + diagnostics: Vec, + } + + while let Some(notification) = rx.recv().await { + let method = notification + .get("method") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); + + match method.as_deref() { + Some("textDocument/publishDiagnostics") => { + if let Ok(params) = serde_json::from_value::( + notification["params"].clone(), + ) { + let rust_uri = params.uri; + let diagnostics = params.diagnostics; + + let mist_uri = rust_uri_to_mist_uri(&rust_uri); + + let mapped_diagnostics: Vec = if let Some(ref _mist_uri) = mist_uri + { + let rust_path = rust_uri.to_file_path().ok(); + let map_data = + rust_path.and_then(|p| mapping.blocking_lock().get(&p).cloned()); + + diagnostics + .into_iter() + .filter_map(|diag| { + let map = map_data.clone()?; + + let rust_start = lsp_pos_to_rust_map(&diag.range.start); + let rust_end = lsp_pos_to_rust_map(&diag.range.end); + + let (_, mist_start) = map.find(&rust_start)?; + let (_, mist_end) = map.find(&rust_end)?; + + let mist_start_pos = mist_map_to_lsp_pos(&mist_start); + let mist_end_pos = mist_map_to_lsp_pos(&mist_end); + + let final_end = Position { + line: mist_end_pos.line.max(mist_start_pos.line), + character: if mist_end_pos.line == mist_start_pos.line { + mist_end_pos.character.max(mist_start_pos.character + 1) + } else { + mist_end_pos.character.max(1) + }, + }; + + Some(Diagnostic { + range: Range { + start: mist_start_pos, + end: final_end, + }, + severity: diag.severity, + code: diag.code, + code_description: diag.code_description, + source: diag.source, + message: diag.message, + related_information: diag.related_information, + tags: diag.tags, + data: diag.data, + }) + }) + .collect() + } else { + diagnostics + }; + + if let Some(mist_uri) = mist_uri.or(Some(rust_uri.clone())) { + let prev = previous_diagnostics.lock().await.get(&mist_uri).cloned(); + + if prev.as_ref() != Some(&mapped_diagnostics) { + previous_diagnostics + .lock() + .await + .insert(mist_uri.clone(), mapped_diagnostics.clone()); + client + .publish_diagnostics(mist_uri, mapped_diagnostics, None) + .await; + } + } + } + } + _ => {} } } } @@ -555,103 +735,37 @@ pub async fn start() { let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); - let (service, socket) = LspService::new(|client| Backend { - client, - workspace_folder: Arc::new(Mutex::new(None)), - previous_diagnostics: Arc::new(Mutex::new(HashMap::new())), - mapping: Arc::new(Mutex::new(HashMap::new())), - documents: Arc::new(Mutex::new(HashMap::new())), - rust_analyzer: Arc::new(Mutex::new( - RustAnalyzer::new().expect("Failed to create rust analyzer"), - )), + let (service, socket) = LspService::new(|client| { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + + Backend { + client, + workspace_folder: Arc::new(Mutex::new(None)), + previous_diagnostics: Arc::new(Mutex::new(HashMap::new())), + mapping: Arc::new(Mutex::new(HashMap::new())), + documents: Arc::new(Mutex::new(HashMap::new())), + doc_versions: Arc::new(Mutex::new(HashMap::new())), + rust_analyzer: Arc::new(Mutex::new( + RustAnalyzer::new(tx).expect("Failed to create rust analyzer"), + )), + notification_rx: Arc::new(Mutex::new(Some(rx))), + } }); Server::new(stdin, stdout, socket).serve(service).await; } -pub fn from_mist_to_rust(mut path: PathBuf) -> PathBuf { - path.set_extension("rs"); - - let mut comps: Vec = path.components().collect(); - - if let Some(pos) = comps.iter().rposition(|c| c.as_os_str() == "src") { - let replacement = std::path::Path::new(".mist/src"); - comps.splice(pos..=pos, replacement.components()); - comps.iter().collect() - } else { - path - } +pub fn from_mist_to_rust(path: PathBuf) -> PathBuf { + mist_to_rust_path(&path) } -pub fn from_rust_to_mist(mut path: PathBuf) -> PathBuf { - // reverse extension - path.set_extension("mist"); - - let comps: Vec = path.components().collect(); - - let pattern: Vec = std::path::Path::new(".mist/src").components().collect(); - - // find the last occurrence of the pattern - if let Some(pos) = comps - .windows(pattern.len()) - .rposition(|window| window == pattern.as_slice()) - { - let mut new_comps = comps.clone(); - - // replace the matched range with "src" - new_comps.splice( - pos..pos + pattern.len(), - std::iter::once(Component::Normal(std::ffi::OsStr::new("src"))), - ); - - new_comps.iter().collect() - } else { - path - } +pub fn from_rust_to_mist(path: PathBuf) -> PathBuf { + rust_to_mist_path(&path) } -fn insert_at_position(rope: &Rope, line: usize, col: usize, insert: &str) -> Rope { - let mut rope = rope.clone(); - - let line_idx = line.saturating_sub(1); - let col_idx = col.saturating_sub(1); - - let line_idx = line_idx.min(rope.len_lines().saturating_sub(1)); - - let line_start = rope.line_to_char(line_idx); - - let line_slice = rope.line(line_idx); - let line_len = line_slice.len_chars(); - - let col_idx = col_idx.min(line_len); - - let idx = line_start + col_idx; - - rope.insert(idx, insert); - - rope -} - -fn find_row_col(rope: &Rope, needle: &str) -> Option<(usize, usize)> { - let text = rope.to_string(); - - let byte_idx = text.find(needle)?; - - let char_idx = text[..byte_idx].chars().count(); - - let line_idx = rope.char_to_line(char_idx); - - let line_start = rope.line_to_char(line_idx); - - let col_idx = char_idx - line_start; - - Some((line_idx, col_idx + 1)) -} - -fn simplify_item(mut item: CompletionItem) -> CompletionItem { +fn clean_completion_item(mut item: CompletionItem) -> CompletionItem { item.text_edit = None; item.additional_text_edits = None; item.command = None; - item } diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs index ec92b8e..56450cb 100644 --- a/crates/analyzer/src/rust_analyzer.rs +++ b/crates/analyzer/src/rust_analyzer.rs @@ -35,11 +35,9 @@ type PendingMap = Arc #[derive(Debug)] pub struct RustAnalyzer { - // Wrapped in a Mutex to support safe concurrent sharing if the design expands stdin: Arc>, pending: PendingMap, id: usize, - // Keep child handler to explicitly manage child process lifecycle and prevent zombie processes _child: tokio::process::Child, } @@ -60,7 +58,6 @@ async fn read_lsp_message( let mut line = String::new(); let mut content_length = 0; - // Guard against infinite header reading attacks/bugs (Max 100 headers) for _ in 0..100 { line.clear(); let bytes_read = reader.read_line(&mut line).await?; @@ -74,7 +71,6 @@ async fn read_lsp_message( } } - // Explode early if payload size violates strict guard rails to prevent memory-exhaustion (OOM) if content_length == 0 { return Err("Missing, invalid, or zero Content-Length header".into()); } @@ -86,7 +82,6 @@ async fn read_lsp_message( .into()); } - // Explicitly secure pre-allocation limit let mut buffer = vec![0u8; content_length]; reader.read_exact(&mut buffer).await?; @@ -94,7 +89,9 @@ async fn read_lsp_message( } impl RustAnalyzer { - pub fn new() -> Result> { + pub fn new( + notification_tx: tokio::sync::mpsc::UnboundedSender, + ) -> Result> { let mut child = tokio::process::Command::new("rust-analyzer") .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -113,7 +110,6 @@ impl RustAnalyzer { let pending: PendingMap = Arc::new(Mutex::new(HashMap::new())); let pending_clone = pending.clone(); - // Background supervisor task loop tokio::spawn(async move { let mut stdout = BufReader::new(stdout); @@ -122,7 +118,6 @@ impl RustAnalyzer { Ok(v) => v, Err(err) => { eprintln!("LSP fatal stream read failure: {err}"); - // CRITICAL: Notify all pending channels that the bridge broke down let mut lock = pending_clone.lock().await; for (_, tx) in lock.drain() { let _ = tx.send(Err(format!("LSP reader task dropped: {}", err))); @@ -135,12 +130,14 @@ impl RustAnalyzer { Ok(v) => v, Err(err) => { eprintln!("Corrupted JSON received: {err}"); - continue; // Keep the connection running despite malformed frame + continue; } }; - // Filter server notification frames - if value.get("method").is_some() && value.get("id").is_none() { + let is_notification = value.get("method").is_some() && value.get("id").is_none(); + + if is_notification { + let _ = notification_tx.send(value); continue; } @@ -181,7 +178,6 @@ impl RustAnalyzer { let (tx, rx) = oneshot::channel(); - // Scope the lock allocation tightly { self.pending.lock().await.insert(id, tx); } @@ -193,19 +189,15 @@ impl RustAnalyzer { "params": params, }); - // Acquire lock on writing stream to ensure thread safety let mut stdin_lock = self.stdin.lock().await; if let Err(err) = send_lsp_message(&mut *stdin_lock, &payload).await { - // Rollback the pending map operation to avoid internal memory memory-leaks if serialization/IO errors trigger self.pending.lock().await.remove(&id); return Err(Box::new(err)); } - // Explicit drop of write lock early so other operations can pipe messages synchronously drop(stdin_lock); - // Enforce an absolute time constraint limit to break free from hanging processes let response_payload = match timeout(REQUEST_TIMEOUT, rx).await { Ok(Ok(Ok(value))) => value, Ok(Ok(Err(task_err))) => return Err(task_err.into()), @@ -215,7 +207,6 @@ impl RustAnalyzer { ); } Err(_timeout_elapsed) => { - // Clear state tracking entries dynamically upon expiration failure self.pending.lock().await.remove(&id); return Err( format!("Request ID {} timed out after {:?}", id, REQUEST_TIMEOUT).into(), @@ -290,8 +281,68 @@ impl RustAnalyzer { } pub async fn initialized(&mut self) -> Result<(), Box> { - self.notify(Initialized::METHOD, InitializedParams {}) - .await?; + self.notify( + ::METHOD, + InitializedParams {}, + ) + .await?; Ok(()) } } + +impl RustAnalyzer { + pub async fn did_open( + &mut self, + uri: Url, + text: &str, + ) -> Result<(), Box> { + self.notify( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": uri, + "languageId": "rust", + "version": 1, + "text": text, + } + }), + ) + .await + } + + pub async fn did_change( + &mut self, + uri: Url, + text: &str, + version: i32, + ) -> Result<(), Box> { + self.notify( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": uri, + "version": version, + }, + "contentChanges": [{ + "text": text, + }], + }), + ) + .await + } + + pub async fn did_close( + &mut self, + uri: Url, + ) -> Result<(), Box> { + self.notify( + "textDocument/didClose", + json!({ + "textDocument": { + "uri": uri, + } + }), + ) + .await + } +} diff --git a/crates/analyzer/src/transpiler.rs b/crates/analyzer/src/transpiler.rs index e3baed3..adf25fd 100644 --- a/crates/analyzer/src/transpiler.rs +++ b/crates/analyzer/src/transpiler.rs @@ -1,163 +1,41 @@ -use std::{ - fs, - path::{Path, PathBuf}, -}; +use std::path::{Path, PathBuf}; -use mist_parser::{ast::TopLevel, error::ParseError}; +use mist_codegen::{GetRust, RustCodegen}; +use mist_parser::rev_mapper::Mapping; +use mist_parser::{parse, parse_module}; -pub fn build(root: &PathBuf) { - let src_dir = root.join("src"); - let out_dir = root.join(".mist/src"); - - if let Err(e) = build_dir(root, &src_dir, &src_dir, &out_dir) { - eprintln!("Warning: Build directory run aborted safely: {e}"); - } +pub struct TranspiledFile { + pub mist_path: PathBuf, + pub rust_path: PathBuf, + pub rust_content: String, + pub mapping: Mapping, } -fn build_dir( - root: &Path, - base_src: &Path, - current_dir: &Path, - out_dir: &Path, -) -> Result<(), String> { - let entries = fs::read_dir(current_dir) - .map_err(|e| format!("failed to read directory {}: {}", current_dir.display(), e))?; +pub fn transpile_mist(mist_path: &Path, source: &str) -> Result { + let module_decl = parse_module(source).map_err(|e| format!("parse module error: {e:?}"))?; + let rust_path = crate::from_mist_to_rust(mist_path.to_path_buf()); - for entry in entries { - let entry = match entry { - Ok(entry) => entry, - Err(e) => { - eprintln!("Warning: Skipping invalid directory entry: {e}"); - continue; // Skip corrupted entry instead of crashing - } - }; + let parsed = parse(source).map_err(|e| format!("parse error: {e:?}"))?; - let path = entry.path(); - - // Recurse into nested directories - if path.is_dir() { - if let Err(e) = build_dir(root, base_src, &path, out_dir) { - eprintln!("Warning: Nested build directory failed: {e}"); - } - continue; - } - - // Safe prefix stripping fallback - let relative = match path.strip_prefix(base_src) { - Ok(p) => p, - Err(_) => { - eprintln!( - "Warning: Path {} is outside base source directory", - path.display() - ); - continue; - } - }; - - // Handle non-mist files with a cache check - if path.extension().and_then(|e| e.to_str()) != Some("mist") { - let dest_path = out_dir.join(relative); - - // Create parent directories for static assets if needed - if let Some(parent) = dest_path.parent() { - let _ = fs::create_dir_all(parent); - } - - if should_skip(&path, &dest_path) { - continue; - } - - if let Err(e) = fs::copy(&path, &dest_path) { - eprintln!( - "Warning: Failed to copy non-mist file {}: {}", - path.display(), - e - ); - } - continue; - } - - let output_path = out_dir.join(relative).with_extension("rs"); - - // Cache layer: Skip if the generated .rs file is newer than the .mist source - if should_skip(&path, &output_path) { - continue; - } - - if let Err(e) = transpile_file(&path, &output_path) { - eprintln!( - "Warning: Transpilation failed for {}: {}", - path.display(), - e - ); - } + for item in &parsed { + mist_parser::semantics::check_class_semantics(item) + .map_err(|e| format!("semantic error: {}", e[0].error_message))?; } - Ok(()) -} - -pub fn transpile_file(path: &Path, output_path: &Path) -> Result<(), String> { - // Create parent directories - if let Some(parent) = output_path.parent() { - fs::create_dir_all(parent).map_err(|e| { - format!( - "failed to create output directory {}: {}", - parent.display(), - e - ) - })?; - } - - // Read source - let source = fs::read_to_string(path) - .map_err(|e| format!("failed to read file {}: {}", path.display(), e))?; - - let parser_result = mist_parser::parse(&source).map_err(|e| match e { - ParseError::Ast(e) => { - // Using components carefully to prevent out-of-bounds or zero layout crashes - let start_pos = e.span.start_pos().line_col(); - let span = e.span.as_str(); - - format!( - "\n{}:{}:{}\n Error: {}\n\t{}{}\t{}", - path.display(), - start_pos.0, - start_pos.1, - e.error_message, - span, - if span.ends_with('\n') { "" } else { "\n" }, - "^".repeat(span.trim().len()), - ) - } - ParseError::PreAst(e) => format!("{e}"), - }); - - // If parsing fails, return the error string back gracefully so the LSP can show it - let ast = parser_result.map_err(|e| format!("parse failed in {}:\n{}", path.display(), e))?; - - panic!(); - - // let mut gc = mist_codegen::RustCodegen::new(); - // let output = gc.generate(ast); - - // fs::write(output_path, output) - // .map_err(|e| format!("failed to write output {}: {}", output_path.display(), e))?; - - // Ok(()) -} - -pub fn transpile_text<'a>(source: &'a str) -> Result>> { - panic!(); - // let mut gc = mist_codegen::RustCodegen::new(); - - // Ok(gc.generate(mist_parser::parse(&source)?)) -} - -fn should_skip(source: &Path, output: &Path) -> bool { - if let (Ok(src_meta), Ok(out_meta)) = (fs::metadata(source), fs::metadata(output)) { - if let (Ok(src_time), Ok(out_time)) = (src_meta.modified(), out_meta.modified()) { - return out_time >= src_time; - } - } - false + let mut codegen = RustCodegen::new(mist_path.to_path_buf()); + let output = codegen.generate(parsed); + + let mod_prefix = module_decl + .as_ref() + .map(|(vis, name)| format!("{}mod {};\n", vis.get_rust(), name.get_rust())) + .unwrap_or_default(); + + codegen.mapping.shift_rust(mod_prefix.lines().count() as isize, 0); + + Ok(TranspiledFile { + mist_path: mist_path.to_path_buf(), + rust_path, + rust_content: format!("{}{}", mod_prefix, output), + mapping: codegen.mapping, + }) } diff --git a/crates/codegen/src/class_decl.rs b/crates/codegen/src/class_decl.rs index 584d5e4..fa913d0 100644 --- a/crates/codegen/src/class_decl.rs +++ b/crates/codegen/src/class_decl.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use mist_parser::ast::*; -use crate::{Context, GenRust, GetRust, RustCodegen}; +use crate::{Context, GenRust, GenSpanTranslation, GetRust, RustCodegen}; pub struct ClassProcessedData { visibility: Visibility, @@ -117,7 +117,7 @@ impl ClassProcessedData { } for field in &self.fields { - cg.add_indentedln(&field.get_comment()); + field.gen_span(cg); cg.add_indentedln(&field.item.decl.get_rust()); } @@ -269,7 +269,7 @@ impl ClassProcessedData { } if let TypeExpr::Ref { mutable, .. } = params.remove(0) { - cg.add_indentedln(&method.get_comment()); + method.gen_span(cg); cg.add_indented(&format!("{}::__m_", target_rust_path)); cg.add(&method.item.name.get_rust()); cg.add(" as "); @@ -302,7 +302,7 @@ impl ClassProcessedData { for (override_tier, v) in &self.override_v_table { if let Some(path) = &override_tier.0 { - cg.add_indentedln(&v.get_comment()); + v.gen_span(cg); // This forces the compiler to statically verify that &Self can Deref into &Target cg.add_indentedln(&format!("let _: &{} = this;", path.get_rust())); } @@ -319,10 +319,8 @@ impl ClassProcessedData { ctx: &mut Context, cg: &mut RustCodegen, ) { - let constructor_comment = constructor.get_comment(); - cg.add_indentedln("#[allow(invalid_value)]"); - cg.add_indentedln(&constructor_comment); + constructor.gen_span(cg); cg.add_indented(&format!( "{}fn new{}(", @@ -357,10 +355,8 @@ impl ClassProcessedData { cg.add_indentedln("this._vptr = &Self::__V_TABLE;"); for field in &self.fields { - let comment = field.get_comment(); - if let Some(init) = &field.item.init { - cg.add_indentedln(&comment); + field.gen_span(cg); cg.add_indentedln(&format!("this.{} = ", field.item.decl.name.get_rust())); init.gen_rust(ctx, cg); } @@ -378,7 +374,7 @@ impl ClassProcessedData { cg.add_indentedln("this._vptr = &Self::__V_TABLE;"); - cg.add_indentedln(&constructor_comment); + constructor.gen_span(cg); cg.add_indentedln("this"); cg.indent -= 1; diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs index 1d926e1..7c806f4 100644 --- a/crates/codegen/src/lib.rs +++ b/crates/codegen/src/lib.rs @@ -142,6 +142,18 @@ impl GenRust for Attribute { } } +pub trait GenSpanTranslation { + fn gen_span(&self, _cg: &mut RustCodegen); +} + +impl GenSpanTranslation for Spanned { + fn gen_span(&self, cg: &mut RustCodegen) { + cg.mapping + .map + .insert((cg.position, MistMap(self.line, self.column))); + } +} + impl GenRust for T { fn gen_rust(&self, _: &mut Context, cg: &mut RustCodegen) { cg.add(&self.get_rust()); @@ -150,10 +162,7 @@ impl GenRust for T { impl GenRust for Spanned { fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) { - cg.mapping - .map - .insert((cg.position, MistMap(self.line, self.column))); - + self.gen_span(cg); self.item.gen_rust(ctx, cg); } } diff --git a/crates/codegen/src/statement.rs b/crates/codegen/src/statement.rs index f1596ee..9a94947 100644 --- a/crates/codegen/src/statement.rs +++ b/crates/codegen/src/statement.rs @@ -1,6 +1,6 @@ use mist_parser::ast::*; -use crate::Context; +use crate::{Context, GenSpanTranslation}; use crate::{GenRust, GetRust, RustCodegen}; @@ -55,7 +55,7 @@ impl GenRust for Statement { cg.indent += 1; for match_item in match_items { - cg.add_indentedln(&match_item.get_comment()); + match_item.gen_span(cg); let MatchItem(pat, body) = &match_item.item; diff --git a/crates/parser/src/ast/mod.rs b/crates/parser/src/ast/mod.rs index 04e6d4b..7de3984 100644 --- a/crates/parser/src/ast/mod.rs +++ b/crates/parser/src/ast/mod.rs @@ -72,9 +72,3 @@ impl From for Generics { Self(value.0.into_iter().map(Generic::from).collect()) } } - -impl Spanned { - pub fn get_comment(&self) -> String { - format!("/* {}:{} */", self.line, self.column) - } -} diff --git a/crates/parser/src/rev_mapper.rs b/crates/parser/src/rev_mapper.rs index e943792..2b75e2f 100644 --- a/crates/parser/src/rev_mapper.rs +++ b/crates/parser/src/rev_mapper.rs @@ -34,6 +34,14 @@ impl Mapping { .max_by_key(|(rust, _)| *rust) } + pub fn find_by_mist(&self, target: &MistMap) -> Option<(RustMap, MistMap)> { + self.map + .iter() + .copied() + .filter(|(_, mist)| mist <= target) + .max_by_key(|(_, mist)| *mist) + } + pub fn shift_rust(&mut self, lines: isize, cols: isize) { self.map = self .map