use std::sync::Arc;
use crate::base::FileId;
use crate::syntax::SyntaxFile;
use crate::syntax::file::FileExtension;
use super::input::SourceRoot;
use super::symbols::{HirSymbol, extract_symbols_unified};
#[salsa::input]
pub struct FileText {
pub file: FileId,
#[return_ref]
pub text: String,
}
#[salsa::input]
pub struct SourceRootInput {
#[return_ref]
pub root: SourceRoot,
}
#[salsa::db]
#[derive(Default, Clone)]
pub struct RootDatabase {
storage: salsa::Storage<Self>,
}
#[salsa::db]
impl salsa::Database for RootDatabase {
fn salsa_event(&self, _event: &dyn Fn() -> salsa::Event) {
}
}
impl RootDatabase {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ParseResult {
pub success: bool,
pub errors: Vec<String>,
pub syntax_file: Option<Arc<SyntaxFile>>,
}
impl Eq for ParseResult {}
impl ParseResult {
pub fn ok(syntax_file: SyntaxFile) -> Self {
Self {
success: true,
errors: Vec::new(),
syntax_file: Some(Arc::new(syntax_file)),
}
}
pub fn ok_with_errors(syntax_file: SyntaxFile, errors: Vec<String>) -> Self {
Self {
success: true,
errors,
syntax_file: Some(Arc::new(syntax_file)),
}
}
pub fn err(errors: Vec<String>) -> Self {
Self {
success: false,
errors,
syntax_file: None,
}
}
pub fn is_ok(&self) -> bool {
self.success
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn get_syntax_file(&self) -> Option<&SyntaxFile> {
self.syntax_file.as_deref()
}
}
#[salsa::tracked]
pub fn parse_file(db: &dyn salsa::Database, file_text: FileText) -> ParseResult {
let text = file_text.text(db);
let syntax_file = SyntaxFile::new(text, FileExtension::SysML);
if syntax_file.has_errors() {
let errors: Vec<String> = syntax_file
.errors()
.iter()
.map(|e| e.message.clone())
.collect();
ParseResult::ok_with_errors(syntax_file, errors)
} else {
ParseResult::ok(syntax_file)
}
}
pub fn file_symbols(file: FileId, syntax_file: &SyntaxFile) -> Vec<HirSymbol> {
extract_symbols_unified(file, syntax_file)
}
#[salsa::tracked]
pub fn file_symbols_from_text(db: &dyn salsa::Database, file_text: FileText) -> Vec<HirSymbol> {
let file = file_text.file(db);
let result = parse_file(db, file_text);
match result.syntax_file {
Some(ref syntax_file) => extract_symbols_unified(file, syntax_file),
None => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hir::symbols::SymbolKind;
#[test]
fn test_database_creation() {
let _db = RootDatabase::new();
}
#[test]
fn test_parse_result() {
let syntax_file = SyntaxFile::sysml("part def Test;");
let ok = ParseResult::ok(syntax_file.clone());
assert!(ok.is_ok());
assert!(!ok.has_errors());
assert!(ok.get_syntax_file().is_some());
let err = ParseResult::err(vec!["error".to_string()]);
assert!(!err.is_ok());
assert!(err.has_errors());
assert!(err.get_syntax_file().is_none());
}
#[test]
fn test_file_symbols_empty() {
let syntax_file = SyntaxFile::sysml("");
let file = FileId::new(0);
let symbols = file_symbols(file, &syntax_file);
assert!(symbols.is_empty());
}
#[test]
fn test_file_symbols_from_real_sysml() {
let sysml = r#"
package Vehicle {
part def Car {
attribute mass : Real;
part engine : Engine;
}
part def Engine {
attribute power : Real;
}
}
"#;
let syntax_file = SyntaxFile::sysml(sysml);
let file = FileId::new(1);
let symbols = file_symbols(file, &syntax_file);
assert!(!symbols.is_empty(), "Expected symbols, got empty");
let vehicle = symbols.iter().find(|s| s.name.as_ref() == "Vehicle");
assert!(vehicle.is_some(), "Vehicle package not found");
assert_eq!(vehicle.unwrap().kind, SymbolKind::Package);
}
#[test]
fn test_salsa_tracked_parse_query() {
let db = RootDatabase::new();
let sysml = "part def Car;";
let file_text = FileText::new(&db, FileId::new(0), sysml.to_string());
let result = parse_file(&db, file_text);
assert!(
result.is_ok(),
"Parse failed with errors: {:?}",
result.errors
);
assert!(result.get_syntax_file().is_some());
}
#[test]
fn test_salsa_tracked_symbols_query() {
let db = RootDatabase::new();
let sysml = r#"
package Test {
part def Widget;
}
"#;
let file_text = FileText::new(&db, FileId::new(0), sysml.to_string());
let symbols = file_symbols_from_text(&db, file_text);
assert!(!symbols.is_empty());
let widget = symbols.iter().find(|s| s.name.as_ref() == "Widget");
assert!(widget.is_some(), "Widget not found in symbols");
assert_eq!(widget.unwrap().kind, SymbolKind::PartDefinition);
}
#[test]
fn test_salsa_memoization() {
let db = RootDatabase::new();
let sysml = "part def MemoTest;";
let file_text = FileText::new(&db, FileId::new(0), sysml.to_string());
let symbols1 = file_symbols_from_text(&db, file_text);
let symbols2 = file_symbols_from_text(&db, file_text);
assert_eq!(symbols1, symbols2);
}
}