use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use crate::clang::clang_codecomplete_x86::CompletionContext;
use crate::clang::diagnostics::ClangSourceLocation;
use crate::clang::token::{Token, TokenKind};
use crate::clang::{ClangDriver, ClangOptions, Diagnostics, Lexer, Parser, Preprocessor, Sema};
use crate::x86::{X86TargetMachine, X86_64_REG_COUNT};
const DEFAULT_MAX_COMPLETIONS: usize = 100;
const DEFAULT_DAEMON_CACHE_SIZE: usize = 256;
const DAEMON_HEALTH_CHECK_INTERVAL_SECS: u64 = 30;
const DAEMON_IDLE_TIMEOUT_SECS: u64 = 600;
const MAX_BATCH_FILES: usize = 1024;
const DEFAULT_BUILD_JOBS: usize = 8;
const INDEX_DB_VERSION: u32 = 1;
const MAX_DIAG_CACHE_ENTRIES: usize = 64;
#[derive(Debug, Clone)]
pub struct CompilationResult {
pub success: bool,
pub output_file: Option<String>,
pub assembly: Option<String>,
pub ir: Option<String>,
pub preprocessed: Option<String>,
pub diagnostics: Vec<CompilerDiagnostic>,
pub compile_time_ms: u64,
pub input_file: String,
pub warning_count: usize,
pub error_count: usize,
pub dependencies: Vec<String>,
pub object_size_bytes: Option<u64>,
}
impl CompilationResult {
pub fn new(input_file: &str) -> Self {
Self {
success: false,
output_file: None,
assembly: None,
ir: None,
preprocessed: None,
diagnostics: Vec::new(),
compile_time_ms: 0,
input_file: input_file.to_string(),
warning_count: 0,
error_count: 0,
dependencies: Vec::new(),
object_size_bytes: None,
}
}
pub fn has_errors(&self) -> bool {
self.error_count > 0
}
pub fn has_warnings(&self) -> bool {
self.warning_count > 0
}
pub fn summary(&self) -> String {
if self.success {
format!(
"Compiled {} in {}ms ({} warnings)",
self.input_file, self.compile_time_ms, self.warning_count
)
} else {
format!(
"Failed to compile {}: {} errors, {} warnings",
self.input_file, self.error_count, self.warning_count
)
}
}
}
#[derive(Debug, Clone)]
pub struct CompilerDiagnostic {
pub severity: DiagnosticSeverity,
pub message: String,
pub file: String,
pub line: usize,
pub column: usize,
pub end_line: Option<usize>,
pub end_column: Option<usize>,
pub category: String,
pub code: Option<String>,
pub fixits: Vec<FixItSuggestion>,
pub notes: Vec<DiagnosticNote>,
pub source_text: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DiagnosticSeverity {
Ignored = 0,
Note = 1,
Warning = 2,
Error = 3,
Fatal = 4,
}
impl fmt::Display for DiagnosticSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ignored => write!(f, "ignored"),
Self::Note => write!(f, "note"),
Self::Warning => write!(f, "warning"),
Self::Error => write!(f, "error"),
Self::Fatal => write!(f, "fatal error"),
}
}
}
impl DiagnosticSeverity {
pub fn is_warning_or_worse(&self) -> bool {
*self >= Self::Warning
}
pub fn is_error_or_worse(&self) -> bool {
*self >= Self::Error
}
}
#[derive(Debug, Clone)]
pub struct FixItSuggestion {
pub description: String,
pub replacement: String,
pub start_line: usize,
pub start_column: usize,
pub end_line: usize,
pub end_column: usize,
}
impl FixItSuggestion {
pub fn new(
description: &str,
replacement: &str,
start_line: usize,
start_column: usize,
end_line: usize,
end_column: usize,
) -> Self {
Self {
description: description.to_string(),
replacement: replacement.to_string(),
start_line,
start_column,
end_line,
end_column,
}
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticNote {
pub message: String,
pub file: String,
pub line: usize,
pub column: usize,
}
impl DiagnosticNote {
pub fn new(message: &str, file: &str, line: usize, column: usize) -> Self {
Self {
message: message.to_string(),
file: file.to_string(),
line,
column,
}
}
}
#[derive(Debug, Clone)]
pub struct PreprocessedSource {
pub text: String,
pub input_file: String,
pub included_files: Vec<String>,
pub line_markers: Vec<LineMarker>,
pub macros: Vec<(String, Option<String>)>,
pub undef_macros: Vec<String>,
pub diagnostics: Vec<CompilerDiagnostic>,
}
#[derive(Debug, Clone)]
pub struct LineMarker {
pub source_line: usize,
pub source_file: String,
pub is_file_start: bool,
pub is_return: bool,
pub is_system_header: bool,
pub output_line: usize,
}
#[derive(Debug, Clone)]
pub struct AstNode {
pub kind: AstNodeKind,
pub location: SourcePosition,
pub end_location: SourcePosition,
pub children: Vec<AstNode>,
pub text: Option<String>,
pub type_annotation: Option<String>,
pub name: Option<String>,
pub symbol_ref: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AstNodeKind {
TranslationUnit,
FunctionDecl,
FunctionDef,
VarDecl,
TypeDecl,
EnumDecl,
StructDecl,
UnionDecl,
CompoundStmt,
IfStmt,
ForStmt,
WhileStmt,
DoWhileStmt,
SwitchStmt,
ReturnStmt,
BreakStmt,
ContinueStmt,
DeclStmt,
ExprStmt,
CallExpr,
BinaryExpr,
UnaryExpr,
MemberExpr,
ArraySubscript,
ConditionalExpr,
CastExpr,
IntegerLiteral,
FloatLiteral,
StringLiteral,
CharLiteral,
Identifier,
ParameterDecl,
FieldDecl,
EnumConstant,
LabelStmt,
GotoStmt,
TypedefDecl,
NamespaceDecl,
UsingDecl,
TemplateDecl,
Unknown,
}
impl fmt::Display for AstNodeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TranslationUnit => write!(f, "TranslationUnit"),
Self::FunctionDecl => write!(f, "FunctionDecl"),
Self::FunctionDef => write!(f, "FunctionDef"),
Self::VarDecl => write!(f, "VarDecl"),
Self::TypeDecl => write!(f, "TypeDecl"),
Self::EnumDecl => write!(f, "EnumDecl"),
Self::StructDecl => write!(f, "StructDecl"),
Self::UnionDecl => write!(f, "UnionDecl"),
Self::CompoundStmt => write!(f, "CompoundStmt"),
Self::IfStmt => write!(f, "IfStmt"),
Self::ForStmt => write!(f, "ForStmt"),
Self::WhileStmt => write!(f, "WhileStmt"),
Self::DoWhileStmt => write!(f, "DoWhileStmt"),
Self::SwitchStmt => write!(f, "SwitchStmt"),
Self::ReturnStmt => write!(f, "ReturnStmt"),
Self::BreakStmt => write!(f, "BreakStmt"),
Self::ContinueStmt => write!(f, "ContinueStmt"),
Self::DeclStmt => write!(f, "DeclStmt"),
Self::ExprStmt => write!(f, "ExprStmt"),
Self::CallExpr => write!(f, "CallExpr"),
Self::BinaryExpr => write!(f, "BinaryExpr"),
Self::UnaryExpr => write!(f, "UnaryExpr"),
Self::MemberExpr => write!(f, "MemberExpr"),
Self::ArraySubscript => write!(f, "ArraySubscript"),
Self::ConditionalExpr => write!(f, "ConditionalExpr"),
Self::CastExpr => write!(f, "CastExpr"),
Self::IntegerLiteral => write!(f, "IntegerLiteral"),
Self::FloatLiteral => write!(f, "FloatLiteral"),
Self::StringLiteral => write!(f, "StringLiteral"),
Self::CharLiteral => write!(f, "CharLiteral"),
Self::Identifier => write!(f, "Identifier"),
Self::ParameterDecl => write!(f, "ParameterDecl"),
Self::FieldDecl => write!(f, "FieldDecl"),
Self::EnumConstant => write!(f, "EnumConstant"),
Self::LabelStmt => write!(f, "LabelStmt"),
Self::GotoStmt => write!(f, "GotoStmt"),
Self::TypedefDecl => write!(f, "TypedefDecl"),
Self::NamespaceDecl => write!(f, "NamespaceDecl"),
Self::UsingDecl => write!(f, "UsingDecl"),
Self::TemplateDecl => write!(f, "TemplateDecl"),
Self::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct SourcePosition {
pub line: usize,
pub column: usize,
}
impl SourcePosition {
pub fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
pub fn start() -> Self {
Self { line: 1, column: 1 }
}
pub fn advance(&self, source: &str, chars: usize) -> Self {
let mut line = self.line;
let mut col = self.column;
let mut remaining = chars;
let text = &source[self.offset_in_source(source)..];
for ch in text.chars() {
if remaining == 0 {
break;
}
if ch == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
remaining -= 1;
}
Self { line, column: col }
}
fn offset_in_source(&self, source: &str) -> usize {
let mut offset = 0;
let mut current_line = 1;
for (i, ch) in source.char_indices() {
if current_line == self.line {
offset = i;
let mut col = 1;
for c in source[i..].chars() {
if col == self.column {
return offset;
}
offset += c.len_utf8();
col += 1;
}
break;
}
if ch == '\n' {
current_line += 1;
}
}
offset
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourceRange {
pub start: SourcePosition,
pub end: SourcePosition,
}
impl SourceRange {
pub fn new(start: SourcePosition, end: SourcePosition) -> Self {
Self { start, end }
}
pub fn point(pos: SourcePosition) -> Self {
Self {
start: pos,
end: pos,
}
}
}
#[derive(Debug, Clone)]
pub struct CompletionItem {
pub label: String,
pub insert_text: String,
pub detail: Option<String>,
pub documentation: Option<String>,
pub kind: CompletionItemKind,
pub sort_priority: u32,
pub deprecated: bool,
pub filter_text: Option<String>,
pub is_snippet: bool,
pub additional_edits: Vec<TextEdit>,
pub command: Option<String>,
pub data: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompletionItemKind {
Text = 1,
Method = 2,
Function = 3,
Constructor = 4,
Field = 5,
Variable = 6,
Class = 7,
Interface = 8,
Module = 9,
Property = 10,
Unit = 11,
Value = 12,
Enum = 13,
Keyword = 14,
Snippet = 15,
Color = 16,
File = 17,
Reference = 18,
Folder = 19,
EnumMember = 20,
Constant = 21,
Struct = 22,
Event = 23,
Operator = 24,
TypeParameter = 25,
}
impl CompletionItemKind {
pub fn as_lsp_kind(&self) -> u32 {
*self as u32
}
pub fn icon(&self) -> &'static str {
match self {
Self::Text => "T",
Self::Method => "m",
Self::Function => "f",
Self::Constructor => "C",
Self::Field => "F",
Self::Variable => "v",
Self::Class => "c",
Self::Interface => "I",
Self::Module => "M",
Self::Property => "p",
Self::Unit => "U",
Self::Value => "V",
Self::Enum => "E",
Self::Keyword => "k",
Self::Snippet => "S",
Self::Color => "C",
Self::File => "F",
Self::Reference => "r",
Self::Folder => "D",
Self::EnumMember => "e",
Self::Constant => "K",
Self::Struct => "s",
Self::Event => "E",
Self::Operator => "o",
Self::TypeParameter => "t",
}
}
}
#[derive(Debug, Clone)]
pub struct TextEdit {
pub range: SourceRange,
pub new_text: String,
}
impl TextEdit {
pub fn new(range: SourceRange, new_text: &str) -> Self {
Self {
range,
new_text: new_text.to_string(),
}
}
pub fn insert(position: SourcePosition, text: &str) -> Self {
Self {
range: SourceRange::point(position),
new_text: text.to_string(),
}
}
pub fn delete(range: SourceRange) -> Self {
Self {
range,
new_text: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct SignatureHelp {
pub signatures: Vec<SignatureInformation>,
pub active_signature: usize,
pub active_parameter: usize,
}
#[derive(Debug, Clone)]
pub struct SignatureInformation {
pub label: String,
pub documentation: Option<String>,
pub parameters: Vec<ParameterInformation>,
}
#[derive(Debug, Clone)]
pub struct ParameterInformation {
pub label: String,
pub documentation: Option<String>,
}
#[derive(Debug, Clone)]
pub struct HoverInfo {
pub contents: HoverContents,
pub range: Option<SourceRange>,
}
#[derive(Debug, Clone)]
pub enum HoverContents {
PlainText(String),
Markdown(String),
MarkedStrings(Vec<MarkedString>),
}
#[derive(Debug, Clone)]
pub struct MarkedString {
pub language: String,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct Location {
pub uri: String,
pub range: SourceRange,
}
impl Location {
pub fn new(uri: &str, range: SourceRange) -> Self {
Self {
uri: uri.to_string(),
range,
}
}
}
#[derive(Debug, Clone)]
pub struct DocumentSymbol {
pub name: String,
pub detail: Option<String>,
pub kind: SymbolKind,
pub deprecated: bool,
pub range: SourceRange,
pub selection_range: SourceRange,
pub children: Vec<DocumentSymbol>,
pub tags: Vec<SymbolTag>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolKind {
File = 1,
Module = 2,
Namespace = 3,
Package = 4,
Class = 5,
Method = 6,
Property = 7,
Field = 8,
Constructor = 9,
Enum = 10,
Interface = 11,
Function = 12,
Variable = 13,
Constant = 14,
String = 15,
Number = 16,
Boolean = 17,
Array = 18,
Object = 19,
Key = 20,
Null = 21,
EnumMember = 22,
Struct = 23,
Event = 24,
Operator = 25,
TypeParameter = 26,
}
impl SymbolKind {
pub fn as_lsp_kind(&self) -> u32 {
*self as u32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolTag {
Deprecated = 1,
}
#[derive(Debug, Clone)]
pub struct SemanticTokens {
pub result_id: Option<String>,
pub data: Vec<u32>,
}
#[derive(Debug, Clone)]
pub struct SemanticToken {
pub line: u32,
pub start_char: u32,
pub length: u32,
pub token_type: u32,
pub token_modifiers: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemanticTokenType {
Namespace = 0,
Type = 1,
Class = 2,
Enum = 3,
Interface = 4,
Struct = 5,
TypeParameter = 6,
Parameter = 7,
Variable = 8,
Property = 9,
EnumMember = 10,
Event = 11,
Function = 12,
Method = 13,
Macro = 14,
Keyword = 15,
Modifier = 16,
Comment = 17,
String = 18,
Number = 19,
Regexp = 20,
Operator = 21,
Decorator = 22,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemanticTokenModifier {
Declaration = 0,
Definition = 1,
Readonly = 2,
Static = 3,
Deprecated = 4,
Abstract = 5,
Async = 6,
Modification = 7,
Documentation = 8,
DefaultLibrary = 9,
}
#[derive(Debug, Clone)]
pub struct CodeAction {
pub title: String,
pub kind: Option<CodeActionKind>,
pub diagnostics: Vec<CompilerDiagnostic>,
pub is_preferred: bool,
pub disabled: Option<CodeActionDisabled>,
pub edit: Option<WorkspaceEdit>,
pub command: Option<CodeActionCommand>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodeActionKind {
QuickFix,
Refactor,
RefactorExtract,
RefactorInline,
RefactorRewrite,
Source,
SourceOrganizeImports,
SourceFixAll,
}
impl CodeActionKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::QuickFix => "quickfix",
Self::Refactor => "refactor",
Self::RefactorExtract => "refactor.extract",
Self::RefactorInline => "refactor.inline",
Self::RefactorRewrite => "refactor.rewrite",
Self::Source => "source",
Self::SourceOrganizeImports => "source.organizeImports",
Self::SourceFixAll => "source.fixAll",
}
}
}
#[derive(Debug, Clone)]
pub struct CodeActionDisabled {
pub reason: String,
}
#[derive(Debug, Clone)]
pub struct CodeActionCommand {
pub command: String,
pub title: String,
pub arguments: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct WorkspaceEdit {
pub changes: HashMap<String, Vec<TextEdit>>,
pub document_changes: Vec<DocumentChange>,
}
#[derive(Debug, Clone)]
pub struct DocumentChange {
pub edits: Vec<TextEdit>,
pub uri: Option<String>,
}
impl WorkspaceEdit {
pub fn new() -> Self {
Self {
changes: HashMap::new(),
document_changes: Vec::new(),
}
}
pub fn add_edit(&mut self, uri: &str, edit: TextEdit) {
self.changes.entry(uri.to_string()).or_default().push(edit);
}
}
impl Default for WorkspaceEdit {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CodeLens {
pub range: SourceRange,
pub command: Option<CodeActionCommand>,
pub data: Option<String>,
}
impl CodeLens {
pub fn new(range: SourceRange) -> Self {
Self {
range,
command: None,
data: None,
}
}
}
#[derive(Debug, Clone)]
pub struct FormattedSource {
pub text: String,
pub file: String,
pub was_changed: bool,
pub edit_count: usize,
}
impl FormattedSource {
pub fn new(file: &str, text: &str, was_changed: bool, edit_count: usize) -> Self {
Self {
text: text.to_string(),
file: file.to_string(),
was_changed,
edit_count,
}
}
}
#[derive(Debug, Clone)]
pub struct FormattingOptions {
pub tab_size: usize,
pub insert_spaces: bool,
pub column_limit: usize,
pub brace_style: BraceStyle,
pub trim_trailing_whitespace: bool,
pub insert_final_newline: bool,
pub max_empty_lines: usize,
}
impl Default for FormattingOptions {
fn default() -> Self {
Self {
tab_size: 4,
insert_spaces: true,
column_limit: 80,
brace_style: BraceStyle::Attach,
trim_trailing_whitespace: true,
insert_final_newline: true,
max_empty_lines: 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BraceStyle {
Attach,
Break,
Linux,
Stroustrup,
}
#[derive(Debug, Clone)]
pub struct CompilationDatabaseEntry {
pub directory: String,
pub command: Option<String>,
pub arguments: Option<Vec<String>>,
pub file: String,
pub output: Option<String>,
}
impl CompilationDatabaseEntry {
pub fn new(directory: &str, file: &str) -> Self {
Self {
directory: directory.to_string(),
command: None,
arguments: None,
file: file.to_string(),
output: None,
}
}
}
#[derive(Debug, Clone)]
pub struct CompilationDatabase {
pub entries: BTreeMap<String, CompilationDatabaseEntry>,
pub directory: String,
pub valid: bool,
pub version: u32,
}
impl CompilationDatabase {
pub fn new(directory: &str) -> Self {
Self {
entries: BTreeMap::new(),
directory: directory.to_string(),
valid: true,
version: 1,
}
}
pub fn add_entry(&mut self, entry: CompilationDatabaseEntry) {
self.entries.insert(entry.file.clone(), entry);
}
pub fn get_compile_args(&self, file: &str) -> Option<Vec<String>> {
self.entries.get(file).and_then(|e| {
e.arguments
.clone()
.or_else(|| e.command.as_ref().map(|c| shell_split(c)))
})
}
pub fn files(&self) -> Vec<&String> {
self.entries.keys().collect()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn to_json_value(&self) -> Vec<serde_json::Value> {
self.entries
.values()
.map(|e| {
let mut map = serde_json::Map::new();
map.insert(
"directory".to_string(),
serde_json::Value::String(e.directory.clone()),
);
map.insert(
"file".to_string(),
serde_json::Value::String(e.file.clone()),
);
if let Some(ref args) = e.arguments {
map.insert(
"arguments".to_string(),
serde_json::Value::Array(
args.iter()
.map(|a| serde_json::Value::String(a.clone()))
.collect(),
),
);
}
if let Some(ref cmd) = e.command {
map.insert(
"command".to_string(),
serde_json::Value::String(cmd.clone()),
);
}
if let Some(ref out) = e.output {
map.insert("output".to_string(), serde_json::Value::String(out.clone()));
}
serde_json::Value::Object(map)
})
.collect()
}
}
fn shell_split(command: &str) -> Vec<String> {
let mut args = Vec::new();
let mut current = String::new();
let mut in_single_quote = false;
let mut in_double_quote = false;
let mut escaping = false;
for ch in command.chars() {
if escaping {
current.push(ch);
escaping = false;
continue;
}
match ch {
'\\' => {
escaping = true;
}
'\'' if !in_double_quote => {
in_single_quote = !in_single_quote;
}
'"' if !in_single_quote => {
in_double_quote = !in_double_quote;
}
' ' | '\t' if !in_single_quote && !in_double_quote => {
if !current.is_empty() {
args.push(current.clone());
current.clear();
}
}
_ => {
current.push(ch);
}
}
}
if !current.is_empty() {
args.push(current);
}
args
}
#[derive(Debug, Clone)]
pub struct BatchCompilationRequest {
pub files: Vec<CompilationUnit>,
pub common_options: CompileOptions,
pub stop_on_error: bool,
pub parallel_jobs: usize,
pub link: bool,
pub output_name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct CompilationUnit {
pub file: String,
pub overrides: Option<CompileOptions>,
}
#[derive(Debug, Clone)]
pub struct CompileOptions {
pub optimization: String,
pub target: String,
pub includes: Vec<String>,
pub defines: Vec<(String, Option<String>)>,
pub warnings: Vec<String>,
pub c_standard: String,
pub cxx_standard: Option<String>,
pub extra_flags: Vec<String>,
pub debug_info: bool,
pub lto: bool,
pub pic: bool,
pub sanitizer: Option<String>,
pub output_dir: Option<String>,
pub language: String,
}
impl Default for CompileOptions {
fn default() -> Self {
Self {
optimization: "2".to_string(),
target: "x86_64-unknown-linux-gnu".to_string(),
includes: Vec::new(),
defines: Vec::new(),
warnings: vec!["-Wall".to_string(), "-Wextra".to_string()],
c_standard: "c17".to_string(),
cxx_standard: Some("c++17".to_string()),
extra_flags: Vec::new(),
debug_info: false,
lto: false,
pic: true,
sanitizer: None,
output_dir: None,
language: "c".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct BatchCompilationResult {
pub file_results: Vec<CompilationResult>,
pub total_time_ms: u64,
pub succeeded: usize,
pub failed: usize,
pub link_success: Option<bool>,
pub output_binary: Option<String>,
pub all_diagnostics: Vec<CompilerDiagnostic>,
}
impl BatchCompilationResult {
pub fn new() -> Self {
Self {
file_results: Vec::new(),
total_time_ms: 0,
succeeded: 0,
failed: 0,
link_success: None,
output_binary: None,
all_diagnostics: Vec::new(),
}
}
pub fn is_success(&self) -> bool {
self.failed == 0 && self.link_success != Some(false)
}
pub fn summary(&self) -> String {
format!(
"Batch: {} succeeded, {} failed in {}ms{}",
self.succeeded,
self.failed,
self.total_time_ms,
if let Some(ref bin) = self.output_binary {
format!(" → {}", bin)
} else {
String::new()
}
)
}
}
impl Default for BatchCompilationResult {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GrpcCompileRequest {
pub source: GrpcSource,
pub options: CompileOptions,
pub request_id: String,
pub stream_diagnostics: bool,
}
#[derive(Debug, Clone)]
pub enum GrpcSource {
Inline {
text: String,
filename: String,
},
FilePath(String),
MultipleFiles(Vec<(String, String)>),
}
#[derive(Debug, Clone)]
pub struct GrpcCompileResponse {
pub result: CompilationResult,
pub request_id: String,
pub timestamp: u64,
}
#[derive(Debug, Clone)]
pub struct GrpcPreprocessRequest {
pub source: GrpcSource,
pub defines: Vec<(String, Option<String>)>,
pub includes: Vec<String>,
pub request_id: String,
}
#[derive(Debug, Clone)]
pub struct GrpcPreprocessResponse {
pub result: PreprocessedSource,
pub request_id: String,
}
#[derive(Debug, Clone)]
pub struct GrpcParseRequest {
pub source: GrpcSource,
pub options: CompileOptions,
pub request_id: String,
}
#[derive(Debug, Clone)]
pub struct GrpcParseResponse {
pub ast: AstNode,
pub diagnostics: Vec<CompilerDiagnostic>,
pub request_id: String,
}
#[derive(Debug, Clone)]
pub struct GrpcCompleteRequest {
pub source: GrpcSource,
pub position: SourcePosition,
pub max_results: usize,
pub request_id: String,
}
#[derive(Debug, Clone)]
pub struct GrpcCompleteResponse {
pub items: Vec<CompletionItem>,
pub is_incomplete: bool,
pub request_id: String,
}
#[derive(Debug, Clone)]
pub struct GrpcFormatRequest {
pub source: GrpcSource,
pub options: FormattingOptions,
pub request_id: String,
}
#[derive(Debug, Clone)]
pub struct GrpcFormatResponse {
pub result: FormattedSource,
pub request_id: String,
}
#[derive(Debug, Clone)]
pub struct GrpcDiagnosticUpdate {
pub request_id: String,
pub diagnostics: Vec<CompilerDiagnostic>,
pub is_done: bool,
pub progress: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestCompileRequest {
pub source: Option<String>,
pub file: Option<String>,
pub options: Option<RestCompileOptions>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestCompileOptions {
pub optimization: Option<String>,
pub target: Option<String>,
pub includes: Option<Vec<String>>,
pub defines: Option<Vec<String>>,
pub c_standard: Option<String>,
pub debug_info: Option<bool>,
pub output_format: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestCompileResponse {
pub success: bool,
pub output: Option<String>,
pub assembly: Option<String>,
pub diagnostics: Vec<RestDiagnostic>,
pub compile_time_ms: u64,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestDiagnostic {
pub severity: String,
pub message: String,
pub file: String,
pub line: usize,
pub column: usize,
pub code: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestPreprocessRequest {
pub source: Option<String>,
pub file: Option<String>,
pub defines: Option<Vec<String>>,
pub includes: Option<Vec<String>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestPreprocessResponse {
pub text: String,
pub included_files: Vec<String>,
pub macros: Vec<Vec<String>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestParseRequest {
pub source: Option<String>,
pub file: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestParseResponse {
pub ast: String,
pub diagnostics: Vec<RestDiagnostic>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestCompleteRequest {
pub source: Option<String>,
pub file: Option<String>,
pub line: usize,
pub column: usize,
pub max_results: Option<usize>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestCompleteResponse {
pub items: Vec<RestCompletionItem>,
pub is_incomplete: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestCompletionItem {
pub label: String,
pub insert_text: String,
pub detail: Option<String>,
pub kind: u32,
pub sort_priority: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestFormatRequest {
pub source: Option<String>,
pub file: Option<String>,
pub options: Option<RestFormatOptions>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestFormatOptions {
pub tab_size: Option<usize>,
pub insert_spaces: Option<bool>,
pub column_limit: Option<usize>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RestFormatResponse {
pub text: String,
pub was_changed: bool,
}
#[derive(Debug, Clone)]
pub struct LspCompletionParams {
pub text_document: LspTextDocumentIdentifier,
pub position: LspPosition,
pub context: Option<LspCompletionContext>,
}
#[derive(Debug, Clone)]
pub struct LspTextDocumentIdentifier {
pub uri: String,
}
#[derive(Debug, Clone)]
pub struct LspPosition {
pub line: u32,
pub character: u32,
}
impl LspPosition {
pub fn to_source_position(&self) -> SourcePosition {
SourcePosition {
line: (self.line + 1) as usize,
column: (self.character + 1) as usize,
}
}
}
#[derive(Debug, Clone)]
pub struct LspCompletionContext {
pub trigger_kind: u32,
pub trigger_character: Option<String>,
}
#[derive(Debug, Clone)]
pub struct LspCompletionList {
pub is_incomplete: bool,
pub items: Vec<LspCompletionItem>,
}
#[derive(Debug, Clone)]
pub struct LspCompletionItem {
pub label: String,
pub kind: Option<u32>,
pub detail: Option<String>,
pub documentation: Option<String>,
pub deprecated: Option<bool>,
pub preselect: Option<bool>,
pub sort_text: Option<String>,
pub filter_text: Option<String>,
pub insert_text: Option<String>,
pub insert_text_format: Option<u32>,
pub text_edit: Option<LspTextEdit>,
pub additional_text_edits: Option<Vec<LspTextEdit>>,
pub commit_characters: Option<Vec<String>>,
pub command: Option<LspCommand>,
pub data: Option<String>,
}
#[derive(Debug, Clone)]
pub struct LspTextEdit {
pub range: LspRange,
pub new_text: String,
}
#[derive(Debug, Clone)]
pub struct LspRange {
pub start: LspPosition,
pub end: LspPosition,
}
#[derive(Debug, Clone)]
pub struct LspCommand {
pub title: String,
pub command: String,
pub arguments: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct LspSignatureHelpParams {
pub text_document: LspTextDocumentIdentifier,
pub position: LspPosition,
pub context: Option<LspSignatureHelpContext>,
}
#[derive(Debug, Clone)]
pub struct LspSignatureHelpContext {
pub trigger_kind: u32,
pub trigger_character: Option<String>,
pub is_retrigger: bool,
pub active_signature_help: Option<LspSignatureHelp>,
}
#[derive(Debug, Clone)]
pub struct LspSignatureHelp {
pub signatures: Vec<LspSignatureInformation>,
pub active_signature: Option<u32>,
pub active_parameter: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct LspSignatureInformation {
pub label: String,
pub documentation: Option<String>,
pub parameters: Option<Vec<LspParameterInformation>>,
pub active_parameter: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct LspParameterInformation {
pub label: LspParameterLabel,
pub documentation: Option<String>,
}
#[derive(Debug, Clone)]
pub enum LspParameterLabel {
String(String),
Offsets(u32, u32),
}
#[derive(Debug, Clone)]
pub struct LspHoverParams {
pub text_document: LspTextDocumentIdentifier,
pub position: LspPosition,
}
#[derive(Debug, Clone)]
pub struct LspHover {
pub contents: LspHoverContents,
pub range: Option<LspRange>,
}
#[derive(Debug, Clone)]
pub enum LspHoverContents {
String(String),
MarkupContent { kind: String, value: String },
Array(Vec<LspHoverContents>),
}
#[derive(Debug, Clone)]
pub struct LspDefinitionParams {
pub text_document: LspTextDocumentIdentifier,
pub position: LspPosition,
}
#[derive(Debug, Clone)]
pub struct LspLocationLink {
pub origin_selection_range: Option<LspRange>,
pub target_uri: String,
pub target_range: LspRange,
pub target_selection_range: LspRange,
}
#[derive(Debug, Clone)]
pub struct LspReferenceParams {
pub text_document: LspTextDocumentIdentifier,
pub position: LspPosition,
pub context: LspReferenceContext,
}
#[derive(Debug, Clone)]
pub struct LspReferenceContext {
pub include_declaration: bool,
}
#[derive(Debug, Clone)]
pub struct LspDocumentSymbolParams {
pub text_document: LspTextDocumentIdentifier,
}
#[derive(Debug, Clone)]
pub enum LspDocumentSymbolResult {
Flat(Vec<LspSymbolInformation>),
Nested(Vec<LspDocumentSymbol>),
}
#[derive(Debug, Clone)]
pub struct LspSymbolInformation {
pub name: String,
pub kind: u32,
pub deprecated: Option<bool>,
pub location: LspLocation,
pub container_name: Option<String>,
pub tags: Option<Vec<u32>>,
}
#[derive(Debug, Clone)]
pub struct LspDocumentSymbol {
pub name: String,
pub detail: Option<String>,
pub kind: u32,
pub tags: Option<Vec<u32>>,
pub deprecated: Option<bool>,
pub range: LspRange,
pub selection_range: LspRange,
pub children: Option<Vec<LspDocumentSymbol>>,
}
#[derive(Debug, Clone)]
pub struct LspLocation {
pub uri: String,
pub range: LspRange,
}
#[derive(Debug, Clone)]
pub struct LspWorkspaceSymbolParams {
pub query: String,
}
#[derive(Debug, Clone)]
pub struct LspSemanticTokensParams {
pub text_document: LspTextDocumentIdentifier,
}
#[derive(Debug, Clone)]
pub struct LspSemanticTokens {
pub result_id: Option<String>,
pub data: Vec<u32>,
}
#[derive(Debug, Clone)]
pub struct LspCodeActionParams {
pub text_document: LspTextDocumentIdentifier,
pub range: LspRange,
pub context: LspCodeActionContext,
}
#[derive(Debug, Clone)]
pub struct LspCodeActionContext {
pub diagnostics: Vec<LspDiagnostic>,
pub only: Option<Vec<String>>,
pub trigger_kind: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct LspDiagnostic {
pub range: LspRange,
pub severity: Option<u32>,
pub code: Option<String>,
pub source: Option<String>,
pub message: String,
pub tags: Option<Vec<u32>>,
pub related_information: Option<Vec<LspDiagnosticRelatedInformation>>,
pub data: Option<String>,
}
#[derive(Debug, Clone)]
pub struct LspDiagnosticRelatedInformation {
pub location: LspLocation,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct LspCodeLensParams {
pub text_document: LspTextDocumentIdentifier,
}
#[derive(Debug, Clone)]
pub struct LspCodeLens {
pub range: LspRange,
pub command: Option<LspCommand>,
pub data: Option<String>,
}
#[derive(Debug, Clone)]
pub struct LspRenameParams {
pub text_document: LspTextDocumentIdentifier,
pub position: LspPosition,
pub new_name: String,
}
#[derive(Debug, Clone)]
pub struct LspFormattingParams {
pub text_document: LspTextDocumentIdentifier,
pub options: LspFormattingOptions,
}
#[derive(Debug, Clone)]
pub struct LspFormattingOptions {
pub tab_size: u32,
pub insert_spaces: bool,
pub trim_trailing_whitespace: Option<bool>,
pub insert_final_newline: Option<bool>,
pub trim_final_newlines: Option<bool>,
}
#[derive(Debug, Clone)]
pub struct LspPublishDiagnosticsParams {
pub uri: String,
pub version: Option<u32>,
pub diagnostics: Vec<LspDiagnostic>,
}
#[derive(Debug, Clone)]
pub enum JsonRpcMessage {
Request {
jsonrpc: String,
id: serde_json::Value,
method: String,
params: Option<serde_json::Value>,
},
Response {
jsonrpc: String,
id: serde_json::Value,
result: Option<serde_json::Value>,
error: Option<JsonRpcError>,
},
Notification {
jsonrpc: String,
method: String,
params: Option<serde_json::Value>,
},
}
#[derive(Debug, Clone)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
pub data: Option<serde_json::Value>,
}
pub mod jsonrpc_error_codes {
pub const PARSE_ERROR: i32 = -32700;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
pub const SERVER_NOT_INITIALIZED: i32 = -32002;
pub const UNKNOWN_ERROR_CODE: i32 = -32001;
}
pub struct X86CompilerService {
pub server: X86CompilationServer,
pub completion_service: X86CodeCompletionService,
pub index_service: X86IndexService,
pub diagnostic_service: X86DiagnosticService,
pub build_service: X86BuildService,
pub daemon: Option<X86DaemonMode>,
pub api: X86CompilerAPI,
pub config: CompilerServiceConfig,
pub running: AtomicBool,
pub started_at: Option<SystemTime>,
}
#[derive(Debug, Clone)]
pub struct CompilerServiceConfig {
pub grpc_enabled: bool,
pub grpc_bind_address: String,
pub rest_enabled: bool,
pub rest_bind_address: String,
pub daemon_enabled: bool,
pub daemon_cache_size: usize,
pub daemon_idle_timeout_secs: u64,
pub max_concurrent_compilations: usize,
pub build_service_enabled: bool,
pub default_build_jobs: usize,
pub distributed_compilation: bool,
pub distcc_scheduler: Option<String>,
pub index_db_path: Option<String>,
pub diagnostic_monitoring: bool,
pub default_target: String,
pub log_level: String,
}
impl Default for CompilerServiceConfig {
fn default() -> Self {
Self {
grpc_enabled: true,
grpc_bind_address: "0.0.0.0:50051".to_string(),
rest_enabled: true,
rest_bind_address: "0.0.0.0:8080".to_string(),
daemon_enabled: false,
daemon_cache_size: DEFAULT_DAEMON_CACHE_SIZE,
daemon_idle_timeout_secs: DAEMON_IDLE_TIMEOUT_SECS,
max_concurrent_compilations: 8,
build_service_enabled: true,
default_build_jobs: DEFAULT_BUILD_JOBS,
distributed_compilation: false,
distcc_scheduler: None,
index_db_path: None,
diagnostic_monitoring: true,
default_target: "x86_64-unknown-linux-gnu".to_string(),
log_level: "info".to_string(),
}
}
}
impl X86CompilerService {
pub fn new() -> Self {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let completion_service = X86CodeCompletionService::new();
let index_service = X86IndexService::new(config.index_db_path.as_deref());
let diagnostic_service = X86DiagnosticService::new(config.diagnostic_monitoring);
let build_service = X86BuildService::new(&config);
let api = X86CompilerAPI::new(&config);
Self {
server,
completion_service,
index_service,
diagnostic_service,
build_service,
daemon: None,
api,
config,
running: AtomicBool::new(false),
started_at: None,
}
}
pub fn with_config(config: CompilerServiceConfig) -> Self {
let mut service = Self {
server: X86CompilationServer::new(&config),
completion_service: X86CodeCompletionService::new(),
index_service: X86IndexService::new(config.index_db_path.as_deref()),
diagnostic_service: X86DiagnosticService::new(config.diagnostic_monitoring),
build_service: X86BuildService::new(&config),
daemon: None,
api: X86CompilerAPI::new(&config),
config,
running: AtomicBool::new(false),
started_at: None,
};
if service.config.daemon_enabled {
service.daemon = Some(X86DaemonMode::new(&service.config));
}
service
}
pub fn start(&mut self) -> Result<(), CompilerServiceError> {
if self.running.load(Ordering::SeqCst) {
return Err(CompilerServiceError::AlreadyRunning);
}
self.running.store(true, Ordering::SeqCst);
self.started_at = Some(SystemTime::now());
if self.config.daemon_enabled {
if let Some(ref mut daemon) = self.daemon {
daemon.start()?;
}
}
if self.config.grpc_enabled {
self.server.start_grpc()?;
}
if self.config.rest_enabled {
self.server.start_rest()?;
}
Ok(())
}
pub fn stop(&mut self) -> Result<(), CompilerServiceError> {
if !self.running.load(Ordering::SeqCst) {
return Err(CompilerServiceError::NotRunning);
}
if let Some(ref mut daemon) = self.daemon {
daemon.shutdown(true)?;
}
self.server.shutdown()?;
self.running.store(false, Ordering::SeqCst);
Ok(())
}
pub fn is_running(&self) -> bool {
self.running.load(Ordering::SeqCst)
}
pub fn uptime_secs(&self) -> Option<u64> {
self.started_at.map(|t| {
SystemTime::now()
.duration_since(t)
.unwrap_or_default()
.as_secs()
})
}
pub fn health_check(&self) -> ServiceHealth {
let mut health = ServiceHealth::new();
health.running = self.is_running();
health.uptime_secs = self.uptime_secs();
if let Some(ref daemon) = self.daemon {
health.daemon_healthy = daemon.is_healthy();
health.daemon_cache_entries = Some(daemon.cache_size());
health.daemon_memory_usage_mb = Some(daemon.memory_usage_mb());
}
health.server_active = self.server.is_active();
health.active_compilations = self.server.active_compilation_count();
health.total_compilations = self.server.total_compilation_count();
health.index_db_size_bytes = self.index_service.database_size_bytes();
health
}
pub fn compile(
&mut self,
source: &str,
options: &CompileOptions,
) -> Result<CompilationResult, CompilerServiceError> {
self.server.compile(source, options)
}
pub fn preprocess(
&mut self,
source: &str,
options: &CompileOptions,
) -> Result<PreprocessedSource, CompilerServiceError> {
self.server.preprocess(source, options)
}
pub fn parse(
&mut self,
source: &str,
options: &CompileOptions,
) -> Result<AstNode, CompilerServiceError> {
self.server.parse(source, options)
}
pub fn complete(
&mut self,
source: &str,
position: SourcePosition,
) -> Result<Vec<CompletionItem>, CompilerServiceError> {
self.server.complete(source, position)
}
pub fn format(
&mut self,
source: &str,
fmt_options: &FormattingOptions,
) -> Result<FormattedSource, CompilerServiceError> {
self.server.format(source, fmt_options)
}
pub fn statistics(&self) -> CompilerServiceStats {
CompilerServiceStats {
total_compilations: self.server.total_compilation_count(),
active_compilations: self.server.active_compilation_count(),
cached_entries: self.daemon.as_ref().map(|d| d.cache_size()).unwrap_or(0),
index_symbols: self.index_service.total_symbols(),
index_files: self.index_service.total_files(),
diagnostic_files: self.diagnostic_service.monitored_file_count(),
build_tasks_completed: self.build_service.tasks_completed(),
uptime_secs: self.uptime_secs(),
}
}
}
impl Default for X86CompilerService {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompilerServiceError {
AlreadyRunning,
NotRunning,
CompilationFailed(String),
PreprocessFailed(String),
ParseFailed(String),
FileNotFound(String),
InvalidOptions(String),
ServerError(String),
Timeout,
Internal(String),
GrpcError(String),
RestError(String),
DaemonError(String),
BuildError(String),
IndexError(String),
}
impl fmt::Display for CompilerServiceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AlreadyRunning => write!(f, "Service is already running"),
Self::NotRunning => write!(f, "Service is not running"),
Self::CompilationFailed(msg) => write!(f, "Compilation failed: {}", msg),
Self::PreprocessFailed(msg) => write!(f, "Preprocessing failed: {}", msg),
Self::ParseFailed(msg) => write!(f, "Parse failed: {}", msg),
Self::FileNotFound(path) => write!(f, "File not found: {}", path),
Self::InvalidOptions(msg) => write!(f, "Invalid options: {}", msg),
Self::ServerError(msg) => write!(f, "Server error: {}", msg),
Self::Timeout => write!(f, "Operation timed out"),
Self::Internal(msg) => write!(f, "Internal error: {}", msg),
Self::GrpcError(msg) => write!(f, "gRPC error: {}", msg),
Self::RestError(msg) => write!(f, "REST error: {}", msg),
Self::DaemonError(msg) => write!(f, "Daemon error: {}", msg),
Self::BuildError(msg) => write!(f, "Build error: {}", msg),
Self::IndexError(msg) => write!(f, "Index error: {}", msg),
}
}
}
#[derive(Debug, Clone)]
pub struct ServiceHealth {
pub running: bool,
pub uptime_secs: Option<u64>,
pub daemon_healthy: bool,
pub daemon_cache_entries: Option<usize>,
pub daemon_memory_usage_mb: Option<f64>,
pub server_active: bool,
pub active_compilations: usize,
pub total_compilations: u64,
pub index_db_size_bytes: Option<u64>,
}
impl ServiceHealth {
fn new() -> Self {
Self {
running: false,
uptime_secs: None,
daemon_healthy: false,
daemon_cache_entries: None,
daemon_memory_usage_mb: None,
server_active: false,
active_compilations: 0,
total_compilations: 0,
index_db_size_bytes: None,
}
}
pub fn is_healthy(&self) -> bool {
self.running
&& self.server_active
&& (self
.daemon_cache_entries
.map_or(true, |_| self.daemon_healthy))
}
pub fn report(&self) -> String {
format!(
"Service: {} | Uptime: {}s | Compilations: {} ({} active) | Daemon: {} | Cache: {}",
if self.running { "RUNNING" } else { "STOPPED" },
self.uptime_secs.unwrap_or(0),
self.total_compilations,
self.active_compilations,
if self.daemon_healthy {
"healthy"
} else {
"N/A"
},
self.daemon_cache_entries
.map(|n| n.to_string())
.unwrap_or_else(|| String::from("-")),
)
}
}
#[derive(Debug, Clone)]
pub struct CompilerServiceStats {
pub total_compilations: u64,
pub active_compilations: usize,
pub cached_entries: usize,
pub index_symbols: usize,
pub index_files: usize,
pub diagnostic_files: usize,
pub build_tasks_completed: u64,
pub uptime_secs: Option<u64>,
}
pub struct X86CompilationServer {
grpc_server: Arc<Mutex<GrpcServerState>>,
rest_server: Arc<Mutex<RestServerState>>,
compilation_db: Arc<RwLock<CompilationDatabase>>,
total_compilations: AtomicU64,
active_compilations: AtomicU64,
active: AtomicBool,
build_dir: Option<String>,
default_options: CompileOptions,
}
struct GrpcServerState {
bind_address: String,
running: bool,
handlers: Vec<String>,
active_streams: usize,
}
struct RestServerState {
bind_address: String,
running: bool,
routes: Vec<String>,
request_count: u64,
}
impl X86CompilationServer {
pub fn new(config: &CompilerServiceConfig) -> Self {
Self {
grpc_server: Arc::new(Mutex::new(GrpcServerState {
bind_address: config.grpc_bind_address.clone(),
running: false,
handlers: vec![
"Compile".to_string(),
"Preprocess".to_string(),
"Parse".to_string(),
"CodeComplete".to_string(),
"Format".to_string(),
],
active_streams: 0,
})),
rest_server: Arc::new(Mutex::new(RestServerState {
bind_address: config.rest_bind_address.clone(),
running: false,
routes: vec![
"/compile".to_string(),
"/preprocess".to_string(),
"/parse".to_string(),
"/complete".to_string(),
"/format".to_string(),
"/health".to_string(),
"/stats".to_string(),
],
request_count: 0,
})),
compilation_db: Arc::new(RwLock::new(CompilationDatabase::new("."))),
total_compilations: AtomicU64::new(0),
active_compilations: AtomicU64::new(0),
active: AtomicBool::new(false),
build_dir: None,
default_options: CompileOptions::default(),
}
}
pub fn start_grpc(&mut self) -> Result<(), CompilerServiceError> {
let mut state = self.grpc_server.lock().unwrap();
if state.running {
return Err(CompilerServiceError::GrpcError(
"gRPC server already running".to_string(),
));
}
state.running = true;
self.active.store(true, Ordering::SeqCst);
Ok(())
}
pub fn start_rest(&mut self) -> Result<(), CompilerServiceError> {
let mut state = self.rest_server.lock().unwrap();
if state.running {
return Err(CompilerServiceError::RestError(
"REST server already running".to_string(),
));
}
state.running = true;
self.active.store(true, Ordering::SeqCst);
Ok(())
}
pub fn shutdown(&mut self) -> Result<(), CompilerServiceError> {
{
let mut grpc = self.grpc_server.lock().unwrap();
grpc.running = false;
grpc.active_streams = 0;
}
{
let mut rest = self.rest_server.lock().unwrap();
rest.running = false;
}
self.active.store(false, Ordering::SeqCst);
Ok(())
}
pub fn is_active(&self) -> bool {
self.active.load(Ordering::SeqCst)
}
pub fn active_compilation_count(&self) -> usize {
self.active_compilations.load(Ordering::SeqCst) as usize
}
pub fn total_compilation_count(&self) -> u64 {
self.total_compilations.load(Ordering::SeqCst)
}
pub fn grpc_compile(
&self,
request: GrpcCompileRequest,
) -> Result<GrpcCompileResponse, CompilerServiceError> {
self.active_compilations.fetch_add(1, Ordering::SeqCst);
let start = Instant::now();
let source_text = match &request.source {
GrpcSource::Inline { text, filename: _ } => text.clone(),
GrpcSource::FilePath(path) => std::fs::read_to_string(path)
.map_err(|e| CompilerServiceError::FileNotFound(e.to_string()))?,
GrpcSource::MultipleFiles(files) => files
.iter()
.map(|(_, content)| content.clone())
.collect::<Vec<_>>()
.join("\n"),
};
let _options = ClangOptions {
target_triple: request.options.target.clone(),
optimize: request.options.optimization != "0",
includes: request.options.includes.clone(),
defines: request.options.defines.clone(),
..Default::default()
};
let mut result = CompilationResult::new("grpc-source.c");
result.compile_time_ms = start.elapsed().as_millis() as u64;
result.success = true;
result.assembly = Some(format!(
"; Compiled: {} | Opt: {} | Target: {}\n; Assembly output placeholder",
source_text.len(),
request.options.optimization,
request.options.target
));
self.active_compilations.fetch_sub(1, Ordering::SeqCst);
self.total_compilations.fetch_add(1, Ordering::SeqCst);
Ok(GrpcCompileResponse {
result,
request_id: request.request_id,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
})
}
pub fn grpc_preprocess(
&self,
request: GrpcPreprocessRequest,
) -> Result<GrpcPreprocessResponse, CompilerServiceError> {
let source_text = self.resolve_source(&request.source)?;
let result = PreprocessedSource {
text: source_text,
input_file: "grpc-source.c".to_string(),
included_files: Vec::new(),
line_markers: Vec::new(),
macros: request
.defines
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
undef_macros: Vec::new(),
diagnostics: Vec::new(),
};
Ok(GrpcPreprocessResponse {
result,
request_id: request.request_id,
})
}
pub fn grpc_parse(
&self,
request: GrpcParseRequest,
) -> Result<GrpcParseResponse, CompilerServiceError> {
let _source_text = self.resolve_source(&request.source)?;
let ast = AstNode {
kind: AstNodeKind::TranslationUnit,
location: SourcePosition::start(),
end_location: SourcePosition { line: 1, column: 1 },
children: Vec::new(),
text: Some("/* AST placeholder */".to_string()),
type_annotation: None,
name: Some("translation_unit".to_string()),
symbol_ref: None,
};
Ok(GrpcParseResponse {
ast,
diagnostics: Vec::new(),
request_id: request.request_id,
})
}
pub fn grpc_complete(
&self,
request: GrpcCompleteRequest,
) -> Result<GrpcCompleteResponse, CompilerServiceError> {
let items = vec![
CompletionItem {
label: "main".to_string(),
insert_text: "main".to_string(),
detail: Some("int main(int argc, char **argv)".to_string()),
documentation: Some("Program entry point".to_string()),
kind: CompletionItemKind::Function,
sort_priority: 0,
deprecated: false,
filter_text: None,
is_snippet: false,
additional_edits: Vec::new(),
command: None,
data: None,
},
CompletionItem {
label: "printf".to_string(),
insert_text: "printf".to_string(),
detail: Some("int printf(const char *fmt, ...)".to_string()),
documentation: Some("Print formatted output".to_string()),
kind: CompletionItemKind::Function,
sort_priority: 1,
deprecated: false,
filter_text: None,
is_snippet: false,
additional_edits: Vec::new(),
command: None,
data: None,
},
];
Ok(GrpcCompleteResponse {
items,
is_incomplete: false,
request_id: request.request_id,
})
}
pub fn grpc_format(
&self,
request: GrpcFormatRequest,
) -> Result<GrpcFormatResponse, CompilerServiceError> {
let source_text = self.resolve_source(&request.source)?;
let formatted = self.format_source_internal(&source_text, &request.options);
Ok(GrpcFormatResponse {
result: formatted,
request_id: request.request_id,
})
}
pub fn rest_compile(
&self,
request: RestCompileRequest,
) -> Result<RestCompileResponse, CompilerServiceError> {
self.active_compilations.fetch_add(1, Ordering::SeqCst);
let start = Instant::now();
let source = if let Some(ref text) = request.source {
text.clone()
} else if let Some(ref path) = request.file {
std::fs::read_to_string(path)
.map_err(|e| CompilerServiceError::FileNotFound(e.to_string()))?
} else {
return Err(CompilerServiceError::InvalidOptions(
"Either source or file must be provided".to_string(),
));
};
let mut diagnostics = Vec::new();
if source.trim().is_empty() {
diagnostics.push(RestDiagnostic {
severity: "warning".to_string(),
message: "Empty source file".to_string(),
file: "input.c".to_string(),
line: 1,
column: 1,
code: Some("W0001".to_string()),
});
}
{
let mut rest = self.rest_server.lock().unwrap();
rest.request_count += 1;
}
self.active_compilations.fetch_sub(1, Ordering::SeqCst);
self.total_compilations.fetch_add(1, Ordering::SeqCst);
Ok(RestCompileResponse {
success: true,
output: Some(format!(
"; Compiled {} bytes | {}ms",
source.len(),
start.elapsed().as_millis()
)),
assembly: Some(format!(
"; Assembly for {} bytes of source\n; REST compilation",
source.len()
)),
diagnostics,
compile_time_ms: start.elapsed().as_millis() as u64,
})
}
pub fn rest_preprocess(
&self,
request: RestPreprocessRequest,
) -> Result<RestPreprocessResponse, CompilerServiceError> {
let source = if let Some(ref text) = request.source {
text.clone()
} else if let Some(ref path) = request.file {
std::fs::read_to_string(path)
.map_err(|e| CompilerServiceError::FileNotFound(e.to_string()))?
} else {
return Err(CompilerServiceError::InvalidOptions(
"Either source or file must be provided".to_string(),
));
};
let macros: Vec<Vec<String>> = request
.defines
.unwrap_or_default()
.iter()
.map(|d| {
let parts: Vec<&str> = d.splitn(2, '=').collect();
if parts.len() == 2 {
vec![parts[0].to_string(), parts[1].to_string()]
} else {
vec![parts[0].to_string()]
}
})
.collect();
Ok(RestPreprocessResponse {
text: source,
included_files: Vec::new(),
macros,
})
}
pub fn rest_parse(
&self,
request: RestParseRequest,
) -> Result<RestParseResponse, CompilerServiceError> {
let source = if let Some(ref text) = request.source {
text.clone()
} else if let Some(ref path) = request.file {
std::fs::read_to_string(path)
.map_err(|e| CompilerServiceError::FileNotFound(e.to_string()))?
} else {
return Err(CompilerServiceError::InvalidOptions(
"Either source or file must be provided".to_string(),
));
};
Ok(RestParseResponse {
ast: format!(
"TranslationUnit {{\n // AST for {} bytes\n}}",
source.len()
),
diagnostics: Vec::new(),
})
}
pub fn rest_complete(
&self,
request: RestCompleteRequest,
) -> Result<RestCompleteResponse, CompilerServiceError> {
let max_results = request.max_results.unwrap_or(DEFAULT_MAX_COMPLETIONS);
let items = vec![
RestCompletionItem {
label: "int".to_string(),
insert_text: "int ".to_string(),
detail: Some("Integer type".to_string()),
kind: CompletionItemKind::Keyword.as_lsp_kind(),
sort_priority: 0,
},
RestCompletionItem {
label: "void".to_string(),
insert_text: "void ".to_string(),
detail: Some("Void type".to_string()),
kind: CompletionItemKind::Keyword.as_lsp_kind(),
sort_priority: 1,
},
RestCompletionItem {
label: "return".to_string(),
insert_text: "return ".to_string(),
detail: Some("Return statement".to_string()),
kind: CompletionItemKind::Keyword.as_lsp_kind(),
sort_priority: 2,
},
];
let truncated = items.into_iter().take(max_results).collect();
Ok(RestCompleteResponse {
items: truncated,
is_incomplete: false,
})
}
pub fn rest_format(
&self,
request: RestFormatRequest,
) -> Result<RestFormatResponse, CompilerServiceError> {
let source = if let Some(ref text) = request.source {
text.clone()
} else if let Some(ref path) = request.file {
std::fs::read_to_string(path)
.map_err(|e| CompilerServiceError::FileNotFound(e.to_string()))?
} else {
return Err(CompilerServiceError::InvalidOptions(
"Either source or file must be provided".to_string(),
));
};
let formatted = source.trim_end().to_string() + "\n";
let was_changed = formatted != source;
Ok(RestFormatResponse {
text: formatted,
was_changed,
})
}
pub fn stream_compile(&self) -> StreamingCompileSession {
StreamingCompileSession::new()
}
pub fn stream_compile_step(
&self,
session: &mut StreamingCompileSession,
source_chunk: &str,
) -> Result<Vec<CompilerDiagnostic>, CompilerServiceError> {
session.append_source(source_chunk);
Ok(session.diagnostics().to_vec())
}
pub fn stream_compile_finish(
&self,
session: StreamingCompileSession,
) -> Result<CompilationResult, CompilerServiceError> {
let start = Instant::now();
let mut result = CompilationResult::new("stream-input.c");
result.diagnostics = session.diagnostics().to_vec();
result.compile_time_ms = start.elapsed().as_millis() as u64;
result.success = result.error_count == 0;
self.total_compilations.fetch_add(1, Ordering::SeqCst);
Ok(result)
}
pub fn batch_compile(
&self,
request: BatchCompilationRequest,
) -> Result<BatchCompilationResult, CompilerServiceError> {
if request.files.len() > MAX_BATCH_FILES {
return Err(CompilerServiceError::InvalidOptions(format!(
"Batch too large: {} files (max {})",
request.files.len(),
MAX_BATCH_FILES
)));
}
let start = Instant::now();
let mut batch_result = BatchCompilationResult::new();
let mut total_diagnostics = Vec::new();
for unit in &request.files {
let options = match &unit.overrides {
Some(overrides) => {
let mut merged = request.common_options.clone();
if !overrides.optimization.is_empty() {
merged.optimization = overrides.optimization.clone();
}
if !overrides.target.is_empty() {
merged.target = overrides.target.clone();
}
merged.includes.extend(overrides.includes.clone());
merged.defines.extend(overrides.defines.clone());
merged.extra_flags.extend(overrides.extra_flags.clone());
merged
}
None => request.common_options.clone(),
};
match self.compile(&unit.file, &options) {
Ok(mut result) => {
result.diagnostics.iter().for_each(|d| {
if d.severity == DiagnosticSeverity::Error {
result.error_count += 1;
} else if d.severity == DiagnosticSeverity::Warning {
result.warning_count += 1;
}
});
total_diagnostics.append(&mut result.diagnostics.clone());
if result.success {
batch_result.succeeded += 1;
} else {
batch_result.failed += 1;
if request.stop_on_error {
break;
}
}
batch_result.file_results.push(result);
}
Err(e) => {
let mut result = CompilationResult::new(&unit.file);
result.success = false;
result.error_count = 1;
result.diagnostics.push(CompilerDiagnostic {
severity: DiagnosticSeverity::Error,
message: format!("{}", e),
file: unit.file.clone(),
line: 0,
column: 0,
end_line: None,
end_column: None,
category: "compilation".to_string(),
code: None,
fixits: Vec::new(),
notes: Vec::new(),
source_text: None,
});
total_diagnostics.push(result.diagnostics[0].clone());
batch_result.failed += 1;
batch_result.file_results.push(result);
if request.stop_on_error {
break;
}
}
}
}
batch_result.all_diagnostics = total_diagnostics;
batch_result.total_time_ms = start.elapsed().as_millis() as u64;
Ok(batch_result)
}
pub fn load_compilation_db(&self, path: &str) -> Result<(), CompilerServiceError> {
let mut db = self.compilation_db.write().unwrap();
*db = CompilationDatabase::new(path);
db.valid = true;
Ok(())
}
pub fn get_compilation_db(&self) -> CompilationDatabase {
self.compilation_db.read().unwrap().clone()
}
pub fn add_to_compilation_db(&self, entry: CompilationDatabaseEntry) {
let mut db = self.compilation_db.write().unwrap();
db.add_entry(entry);
}
pub fn get_compile_args(&self, file: &str) -> Option<Vec<String>> {
let db = self.compilation_db.read().unwrap();
db.get_compile_args(file)
}
fn resolve_source(&self, source: &GrpcSource) -> Result<String, CompilerServiceError> {
match source {
GrpcSource::Inline { text, .. } => Ok(text.clone()),
GrpcSource::FilePath(path) => std::fs::read_to_string(path)
.map_err(|e| CompilerServiceError::FileNotFound(e.to_string())),
GrpcSource::MultipleFiles(files) => Ok(files
.iter()
.map(|(_, c)| c.clone())
.collect::<Vec<_>>()
.join("\n")),
}
}
fn format_source_internal(&self, source: &str, options: &FormattingOptions) -> FormattedSource {
let lines: Vec<String> = source
.lines()
.map(|line| {
let trimmed = line.trim_end();
if options.trim_trailing_whitespace {
trimmed.to_string()
} else {
line.to_string()
}
})
.collect();
let mut result = String::new();
let mut blank_count = 0;
for line in lines {
if line.trim().is_empty() {
blank_count += 1;
if blank_count <= options.max_empty_lines {
result.push('\n');
}
} else {
blank_count = 0;
result.push_str(&line);
result.push('\n');
}
}
if options.insert_final_newline && !result.ends_with('\n') {
result.push('\n');
}
let was_changed = result != source;
FormattedSource::new(
"formatted.c",
&result,
was_changed,
if was_changed { 1 } else { 0 },
)
}
fn compile(
&self,
source: &str,
options: &CompileOptions,
) -> Result<CompilationResult, CompilerServiceError> {
let start = Instant::now();
let mut result = CompilationResult::new("compile.c");
let clang_options = ClangOptions {
target_triple: options.target.clone(),
optimize: options.optimization != "0",
includes: options.includes.clone(),
defines: options.defines.clone(),
..Default::default()
};
result.assembly = Some(format!(
"; Compiled with target={} opt={} | {} bytes\n",
options.target,
options.optimization,
source.len(),
));
result.compile_time_ms = start.elapsed().as_millis() as u64;
result.success = true;
self.total_compilations.fetch_add(1, Ordering::SeqCst);
Ok(result)
}
fn preprocess(
&self,
source: &str,
_options: &CompileOptions,
) -> Result<PreprocessedSource, CompilerServiceError> {
Ok(PreprocessedSource {
text: source.to_string(),
input_file: "preprocess.c".to_string(),
included_files: Vec::new(),
line_markers: Vec::new(),
macros: Vec::new(),
undef_macros: Vec::new(),
diagnostics: Vec::new(),
})
}
fn parse(
&self,
source: &str,
_options: &CompileOptions,
) -> Result<AstNode, CompilerServiceError> {
Ok(AstNode {
kind: AstNodeKind::TranslationUnit,
location: SourcePosition::start(),
end_location: SourcePosition { line: 1, column: 1 },
children: Vec::new(),
text: Some(format!("/* AST for {} bytes */", source.len())),
type_annotation: None,
name: Some("translation_unit".to_string()),
symbol_ref: None,
})
}
fn complete(
&self,
_source: &str,
_position: SourcePosition,
) -> Result<Vec<CompletionItem>, CompilerServiceError> {
Ok(vec![
CompletionItem {
label: "int".to_string(),
insert_text: "int ".to_string(),
detail: Some("Integer type".to_string()),
documentation: Some("32-bit signed integer type".to_string()),
kind: CompletionItemKind::Keyword,
sort_priority: 0,
deprecated: false,
filter_text: None,
is_snippet: false,
additional_edits: Vec::new(),
command: None,
data: None,
},
CompletionItem {
label: "for".to_string(),
insert_text: "for".to_string(),
detail: Some("For loop".to_string()),
documentation: Some("Iteration statement".to_string()),
kind: CompletionItemKind::Keyword,
sort_priority: 1,
deprecated: false,
filter_text: None,
is_snippet: false,
additional_edits: Vec::new(),
command: None,
data: None,
},
])
}
fn format(
&self,
source: &str,
options: &FormattingOptions,
) -> Result<FormattedSource, CompilerServiceError> {
Ok(self.format_source_internal(source, options))
}
}
pub struct StreamingCompileSession {
source_buffer: String,
diagnostics: Vec<CompilerDiagnostic>,
line_count: usize,
finalized: bool,
session_id: String,
created_at: Instant,
}
impl StreamingCompileSession {
pub fn new() -> Self {
Self {
source_buffer: String::new(),
diagnostics: Vec::new(),
line_count: 0,
finalized: false,
session_id: format!("stream-{}", rand_id()),
created_at: Instant::now(),
}
}
pub fn append_source(&mut self, chunk: &str) {
self.source_buffer.push_str(chunk);
self.line_count += chunk.lines().count();
}
pub fn diagnostics(&self) -> &[CompilerDiagnostic] {
&self.diagnostics
}
pub fn source(&self) -> &str {
&self.source_buffer
}
pub fn id(&self) -> &str {
&self.session_id
}
pub fn elapsed_ms(&self) -> u64 {
self.created_at.elapsed().as_millis() as u64
}
}
impl Default for StreamingCompileSession {
fn default() -> Self {
Self::new()
}
}
fn rand_id() -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
SystemTime::now().hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
pub struct X86CodeCompletionService {
symbol_db: Arc<RwLock<HashMap<String, Vec<CompletionItem>>>>,
file_cache: Arc<RwLock<HashMap<String, String>>>,
ast_cache: Arc<RwLock<HashMap<String, AstNode>>>,
context_cache: Arc<Mutex<HashMap<String, CompletionContext>>>,
capabilities: LspServerCapabilities,
document_symbols: Arc<RwLock<HashMap<String, Vec<DocumentSymbol>>>>,
workspace_symbols: Arc<RwLock<HashMap<String, Vec<SymbolInformation>>>>,
semantic_tokens_legend: SemanticTokensLegend,
config: CompletionServiceConfig,
code_action_providers: Vec<Box<dyn CodeActionProvider>>,
code_lens_providers: Vec<Box<dyn CodeLensProvider>>,
pending_diagnostics: Arc<Mutex<Vec<(String, Vec<LspDiagnostic>)>>>,
}
#[derive(Debug, Clone)]
pub struct CompletionServiceConfig {
pub max_completions: usize,
pub snippet_support: bool,
pub commit_characters_support: bool,
pub deprecated_support: bool,
pub tag_support: Option<Vec<u32>>,
pub resolve_support: Option<Vec<String>>,
pub insert_replace_support: bool,
pub label_details_support: bool,
}
impl Default for CompletionServiceConfig {
fn default() -> Self {
Self {
max_completions: DEFAULT_MAX_COMPLETIONS,
snippet_support: true,
commit_characters_support: true,
deprecated_support: true,
tag_support: Some(vec![1]), resolve_support: Some(vec!["documentation".to_string(), "detail".to_string()]),
insert_replace_support: false,
label_details_support: false,
}
}
}
#[derive(Debug, Clone)]
pub struct LspServerCapabilities {
pub text_document_sync: u32, pub completion_provider: Option<CompletionProviderOptions>,
pub hover_provider: bool,
pub signature_help_provider: Option<SignatureHelpProviderOptions>,
pub definition_provider: bool,
pub declaration_provider: bool,
pub references_provider: bool,
pub document_symbol_provider: bool,
pub workspace_symbol_provider: bool,
pub semantic_tokens_provider: Option<SemanticTokensProviderOptions>,
pub code_action_provider: bool,
pub code_lens_provider: Option<CodeLensProviderOptions>,
pub rename_provider: bool,
pub document_formatting_provider: bool,
}
impl Default for LspServerCapabilities {
fn default() -> Self {
Self {
text_document_sync: 1, completion_provider: Some(CompletionProviderOptions {
trigger_characters: vec![
".".to_string(),
"->".to_string(),
"::".to_string(),
"#".to_string(),
],
resolve_provider: Some(true),
completion_item: Some(CompletionItemOptions {
label_details_support: Some(false),
}),
}),
hover_provider: true,
signature_help_provider: Some(SignatureHelpProviderOptions {
trigger_characters: vec!["(".to_string(), ",".to_string()],
retrigger_characters: Some(vec![")".to_string()]),
}),
definition_provider: true,
declaration_provider: true,
references_provider: true,
document_symbol_provider: true,
workspace_symbol_provider: true,
semantic_tokens_provider: Some(SemanticTokensProviderOptions {
legend: SemanticTokensLegend::default(),
range: Some(false),
full: Some(SemanticTokensFullOptions { delta: Some(false) }),
}),
code_action_provider: true,
code_lens_provider: Some(CodeLensProviderOptions {
resolve_provider: Some(false),
}),
rename_provider: true,
document_formatting_provider: true,
}
}
}
#[derive(Debug, Clone)]
pub struct CompletionProviderOptions {
pub trigger_characters: Vec<String>,
pub resolve_provider: Option<bool>,
pub completion_item: Option<CompletionItemOptions>,
}
#[derive(Debug, Clone)]
pub struct CompletionItemOptions {
pub label_details_support: Option<bool>,
}
#[derive(Debug, Clone)]
pub struct SignatureHelpProviderOptions {
pub trigger_characters: Vec<String>,
pub retrigger_characters: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct SemanticTokensProviderOptions {
pub legend: SemanticTokensLegend,
pub range: Option<bool>,
pub full: Option<SemanticTokensFullOptions>,
}
#[derive(Debug, Clone)]
pub struct SemanticTokensFullOptions {
pub delta: Option<bool>,
}
#[derive(Debug, Clone)]
pub struct SemanticTokensLegend {
pub token_types: Vec<String>,
pub token_modifiers: Vec<String>,
}
impl Default for SemanticTokensLegend {
fn default() -> Self {
Self {
token_types: vec![
"namespace".to_string(),
"type".to_string(),
"class".to_string(),
"enum".to_string(),
"interface".to_string(),
"struct".to_string(),
"typeParameter".to_string(),
"parameter".to_string(),
"variable".to_string(),
"property".to_string(),
"enumMember".to_string(),
"event".to_string(),
"function".to_string(),
"method".to_string(),
"macro".to_string(),
"keyword".to_string(),
"modifier".to_string(),
"comment".to_string(),
"string".to_string(),
"number".to_string(),
"regexp".to_string(),
"operator".to_string(),
"decorator".to_string(),
],
token_modifiers: vec![
"declaration".to_string(),
"definition".to_string(),
"readonly".to_string(),
"static".to_string(),
"deprecated".to_string(),
"abstract".to_string(),
"async".to_string(),
"modification".to_string(),
"documentation".to_string(),
"defaultLibrary".to_string(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct CodeLensProviderOptions {
pub resolve_provider: Option<bool>,
}
pub trait CodeActionProvider: Send + Sync {
fn provide_code_actions(&self, params: &LspCodeActionParams, document: &str)
-> Vec<CodeAction>;
}
pub trait CodeLensProvider: Send + Sync {
fn provide_code_lens(&self, uri: &str, document: &str) -> Vec<CodeLens>;
}
pub struct DefaultCodeActionProvider;
impl CodeActionProvider for DefaultCodeActionProvider {
fn provide_code_actions(
&self,
params: &LspCodeActionParams,
_document: &str,
) -> Vec<CodeAction> {
let mut actions = Vec::new();
for diag in ¶ms.context.diagnostics {
if let Some(ref code) = diag.code {
actions.push(CodeAction {
title: format!("Fix: {}", diag.message),
kind: Some(CodeActionKind::QuickFix),
diagnostics: Vec::new(),
is_preferred: false,
disabled: None,
edit: None,
command: Some(CodeActionCommand {
command: format!("clang.applyFix.{}", code),
title: format!("Apply fix for {}", code),
arguments: vec![diag.message.clone()],
}),
});
}
}
actions
}
}
pub struct DefaultCodeLensProvider;
impl CodeLensProvider for DefaultCodeLensProvider {
fn provide_code_lens(&self, _uri: &str, document: &str) -> Vec<CodeLens> {
let mut lenses = Vec::new();
let mut line = 1u32;
let mut col = 1u32;
for doc_line in document.lines() {
if doc_line.contains("int main") || doc_line.contains("void main") {
lenses.push(CodeLens {
range: SourceRange::new(
SourcePosition::new(line as usize, col as usize),
SourcePosition::new(line as usize, col as usize + doc_line.len()),
),
command: Some(CodeActionCommand {
command: "clang.runTests".to_string(),
title: "▶ Run Tests".to_string(),
arguments: Vec::new(),
}),
data: None,
});
}
if doc_line.trim().starts_with("// TODO") || doc_line.trim().starts_with("// FIXME") {
lenses.push(CodeLens {
range: SourceRange::new(
SourcePosition::new(line as usize, col as usize),
SourcePosition::new(line as usize, col as usize + doc_line.len()),
),
command: Some(CodeActionCommand {
command: "clang.showReferences".to_string(),
title: format!("📋 {} references", 0),
arguments: Vec::new(),
}),
data: None,
});
}
line += 1;
}
lenses
}
}
#[derive(Debug, Clone)]
pub struct SymbolInformation {
pub name: String,
pub kind: SymbolKind,
pub location: Location,
pub container_name: Option<String>,
pub deprecated: bool,
}
impl X86CodeCompletionService {
pub fn new() -> Self {
Self {
symbol_db: Arc::new(RwLock::new(HashMap::new())),
file_cache: Arc::new(RwLock::new(HashMap::new())),
ast_cache: Arc::new(RwLock::new(HashMap::new())),
context_cache: Arc::new(Mutex::new(HashMap::new())),
capabilities: LspServerCapabilities::default(),
document_symbols: Arc::new(RwLock::new(HashMap::new())),
workspace_symbols: Arc::new(RwLock::new(HashMap::new())),
semantic_tokens_legend: SemanticTokensLegend::default(),
config: CompletionServiceConfig::default(),
code_action_providers: vec![Box::new(DefaultCodeActionProvider)],
code_lens_providers: vec![Box::new(DefaultCodeLensProvider)],
pending_diagnostics: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn with_config(config: CompletionServiceConfig) -> Self {
Self {
config,
..Self::new()
}
}
pub fn get_capabilities(&self) -> &LspServerCapabilities {
&self.capabilities
}
pub fn handle_completion(&self, params: LspCompletionParams) -> LspCompletionList {
let uri = ¶ms.text_document.uri;
let position = ¶ms.position;
let source = self.get_cached_document(uri);
let source_pos = position.to_source_position();
let items = self.compute_completions(&source, source_pos, uri);
let is_incomplete = items.len() >= self.config.max_completions;
let lsp_items: Vec<LspCompletionItem> = items
.into_iter()
.map(|item| self.to_lsp_completion_item(&item))
.collect();
LspCompletionList {
is_incomplete,
items: lsp_items,
}
}
pub fn handle_signature_help(
&self,
params: LspSignatureHelpParams,
) -> Option<LspSignatureHelp> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let pos = params.position.to_source_position();
let signatures = self.compute_signatures(&source, pos);
if signatures.is_empty() {
return None;
}
let active_param = self.compute_active_parameter(&source, pos);
let lsp_signatures: Vec<LspSignatureInformation> = signatures
.into_iter()
.enumerate()
.map(|(i, sig)| {
let params: Option<Vec<LspParameterInformation>> = if sig.parameters.is_empty() {
None
} else {
Some(
sig.parameters
.into_iter()
.enumerate()
.map(|(pi, param)| LspParameterInformation {
label: LspParameterLabel::String(param.label),
documentation: param.documentation,
})
.collect(),
)
};
LspSignatureInformation {
label: sig.label,
documentation: sig.documentation,
parameters: params,
active_parameter: if i == 0 {
active_param.map(|p| p as u32)
} else {
None
},
}
})
.collect();
Some(LspSignatureHelp {
signatures: lsp_signatures,
active_signature: Some(0),
active_parameter: active_param.map(|p| p as u32),
})
}
pub fn handle_hover(&self, params: LspHoverParams) -> Option<LspHover> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let pos = params.position.to_source_position();
let info = self.compute_hover(&source, pos);
info.map(|h| {
let contents = match h.contents {
HoverContents::PlainText(text) => LspHoverContents::MarkupContent {
kind: "plaintext".to_string(),
value: text,
},
HoverContents::Markdown(md) => LspHoverContents::MarkupContent {
kind: "markdown".to_string(),
value: md,
},
HoverContents::MarkedStrings(strings) => LspHoverContents::Array(
strings
.into_iter()
.map(|ms| LspHoverContents::MarkupContent {
kind: if ms.language.is_empty() {
"plaintext".to_string()
} else {
ms.language
},
value: ms.value,
})
.collect(),
),
};
LspHover {
contents,
range: h.range.map(|r| LspRange {
start: LspPosition {
line: (r.start.line - 1) as u32,
character: (r.start.column - 1) as u32,
},
end: LspPosition {
line: (r.end.line - 1) as u32,
character: (r.end.column - 1) as u32,
},
}),
}
})
}
pub fn handle_definition(&self, params: LspDefinitionParams) -> Option<Vec<LspLocationLink>> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let pos = params.position.to_source_position();
let locations = self.find_definition(&source, pos, uri);
if locations.is_empty() {
None
} else {
Some(
locations
.into_iter()
.map(|loc| LspLocationLink {
origin_selection_range: Some(LspRange {
start: LspPosition {
line: (pos.line - 1) as u32,
character: (pos.column - 1) as u32,
},
end: LspPosition {
line: (pos.line - 1) as u32,
character: (pos.column) as u32,
},
}),
target_uri: loc.uri,
target_range: LspRange {
start: LspPosition {
line: (loc.range.start.line - 1) as u32,
character: (loc.range.start.column - 1) as u32,
},
end: LspPosition {
line: (loc.range.end.line - 1) as u32,
character: (loc.range.end.column - 1) as u32,
},
},
target_selection_range: LspRange {
start: LspPosition {
line: (loc.range.start.line - 1) as u32,
character: (loc.range.start.column - 1) as u32,
},
end: LspPosition {
line: (loc.range.start.line - 1) as u32,
character: (loc.range.end.column - 1) as u32,
},
},
})
.collect(),
)
}
}
pub fn handle_declaration(&self, params: LspDefinitionParams) -> Option<Vec<LspLocationLink>> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let pos = params.position.to_source_position();
let locations = self.find_declaration(&source, pos, uri);
if locations.is_empty() {
None
} else {
Some(
locations
.into_iter()
.map(|loc| LspLocationLink {
origin_selection_range: None,
target_uri: loc.uri,
target_range: LspRange {
start: LspPosition {
line: (loc.range.start.line - 1) as u32,
character: (loc.range.start.column - 1) as u32,
},
end: LspPosition {
line: (loc.range.end.line - 1) as u32,
character: (loc.range.end.column - 1) as u32,
},
},
target_selection_range: LspRange {
start: LspPosition {
line: (loc.range.start.line - 1) as u32,
character: (loc.range.start.column - 1) as u32,
},
end: LspPosition {
line: (loc.range.start.line - 1) as u32,
character: (loc.range.end.column - 1) as u32,
},
},
})
.collect(),
)
}
}
pub fn handle_references(&self, params: LspReferenceParams) -> Option<Vec<LspLocation>> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let pos = params.position.to_source_position();
let refs = self.find_references(&source, pos, uri, params.context.include_declaration);
if refs.is_empty() {
None
} else {
Some(
refs.into_iter()
.map(|loc| LspLocation {
uri: loc.uri,
range: LspRange {
start: LspPosition {
line: (loc.range.start.line - 1) as u32,
character: (loc.range.start.column - 1) as u32,
},
end: LspPosition {
line: (loc.range.end.line - 1) as u32,
character: (loc.range.end.column - 1) as u32,
},
},
})
.collect(),
)
}
}
pub fn handle_document_symbol(
&self,
params: LspDocumentSymbolParams,
) -> Option<LspDocumentSymbolResult> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let symbols = self.compute_document_symbols(&source, uri);
if symbols.is_empty() {
None
} else {
let lsp_symbols: Vec<LspDocumentSymbol> = symbols
.into_iter()
.map(|s| self.to_lsp_document_symbol(&s))
.collect();
Some(LspDocumentSymbolResult::Nested(lsp_symbols))
}
}
pub fn handle_workspace_symbol(
&self,
params: LspWorkspaceSymbolParams,
) -> Option<Vec<LspSymbolInformation>> {
let query = params.query.to_lowercase();
let ws_symbols = self.workspace_symbols.read().unwrap();
let mut results: Vec<LspSymbolInformation> = Vec::new();
for symbols in ws_symbols.values() {
for sym in symbols {
if query.is_empty() || sym.name.to_lowercase().contains(&query) {
results.push(LspSymbolInformation {
name: sym.name.clone(),
kind: sym.kind.as_lsp_kind(),
deprecated: if sym.deprecated { Some(true) } else { None },
location: LspLocation {
uri: sym.location.uri.clone(),
range: LspRange {
start: LspPosition {
line: (sym.location.range.start.line - 1) as u32,
character: (sym.location.range.start.column - 1) as u32,
},
end: LspPosition {
line: (sym.location.range.end.line - 1) as u32,
character: (sym.location.range.end.column - 1) as u32,
},
},
},
container_name: sym.container_name.clone(),
tags: None,
});
}
}
}
if results.is_empty() {
None
} else {
Some(results)
}
}
pub fn handle_semantic_tokens(
&self,
params: LspSemanticTokensParams,
) -> Option<LspSemanticTokens> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let tokens = self.compute_semantic_tokens(&source);
if tokens.is_empty() {
None
} else {
let data = self.encode_semantic_tokens(&tokens);
Some(LspSemanticTokens {
result_id: Some(format!("st-{}", rand_id())),
data,
})
}
}
pub fn handle_code_action(&self, params: LspCodeActionParams) -> Option<Vec<CodeAction>> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let mut actions = Vec::new();
for provider in &self.code_action_providers {
actions.extend(provider.provide_code_actions(¶ms, &source));
}
if actions.is_empty() {
None
} else {
Some(actions)
}
}
pub fn handle_code_lens(&self, params: LspCodeLensParams) -> Option<Vec<LspCodeLens>> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let mut lenses = Vec::new();
for provider in &self.code_lens_providers {
lenses.extend(provider.provide_code_lens(uri, &source));
}
if lenses.is_empty() {
None
} else {
let lsp_lenses: Vec<LspCodeLens> = lenses
.into_iter()
.map(|cl| LspCodeLens {
range: LspRange {
start: LspPosition {
line: (cl.range.start.line - 1) as u32,
character: (cl.range.start.column - 1) as u32,
},
end: LspPosition {
line: (cl.range.end.line - 1) as u32,
character: (cl.range.end.column - 1) as u32,
},
},
command: cl.command.map(|cmd| LspCommand {
title: cmd.title,
command: cmd.command,
arguments: Some(cmd.arguments),
}),
data: cl.data,
})
.collect();
Some(lsp_lenses)
}
}
pub fn handle_rename(&self, params: LspRenameParams) -> Option<WorkspaceEdit> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let pos = params.position.to_source_position();
let refs = self.find_references(&source, pos, uri, true);
if refs.is_empty() {
return None;
}
let mut edit = WorkspaceEdit::new();
for loc in &refs {
edit.add_edit(&loc.uri, TextEdit::new(loc.range, ¶ms.new_name));
}
Some(edit)
}
pub fn handle_formatting(&self, params: LspFormattingParams) -> Option<Vec<LspTextEdit>> {
let uri = ¶ms.text_document.uri;
let source = self.get_cached_document(uri);
let fmt_options = FormattingOptions {
tab_size: params.options.tab_size as usize,
insert_spaces: params.options.insert_spaces,
column_limit: 80,
brace_style: BraceStyle::Attach,
trim_trailing_whitespace: params.options.trim_trailing_whitespace.unwrap_or(true),
insert_final_newline: params.options.insert_final_newline.unwrap_or(true),
max_empty_lines: 1,
};
let formatted = self.format_document(&source, &fmt_options);
let total_lines = source.lines().count();
if formatted == source {
None
} else {
Some(vec![LspTextEdit {
range: LspRange {
start: LspPosition {
line: 0,
character: 0,
},
end: LspPosition {
line: total_lines as u32,
character: 0,
},
},
new_text: formatted,
}])
}
}
pub fn collect_diagnostics(&self) -> Vec<LspPublishDiagnosticsParams> {
let mut pending = self.pending_diagnostics.lock().unwrap();
let result: Vec<LspPublishDiagnosticsParams> = pending
.drain(..)
.map(|(uri, diagnostics)| LspPublishDiagnosticsParams {
uri,
version: None,
diagnostics,
})
.collect();
result
}
pub fn publish_diagnostics(&self, uri: &str, diagnostics: Vec<LspDiagnostic>) {
let mut pending = self.pending_diagnostics.lock().unwrap();
pending.push((uri.to_string(), diagnostics));
}
pub fn open_document(&self, uri: &str, text: &str) {
let mut cache = self.file_cache.write().unwrap();
cache.insert(uri.to_string(), text.to_string());
}
pub fn update_document(&self, uri: &str, text: &str) {
self.open_document(uri, text);
}
pub fn close_document(&self, uri: &str) {
let mut cache = self.file_cache.write().unwrap();
cache.remove(uri);
}
pub fn get_cached_document(&self, uri: &str) -> String {
let cache = self.file_cache.read().unwrap();
cache.get(uri).cloned().unwrap_or_default()
}
pub fn register_symbols(&self, uri: &str, items: Vec<CompletionItem>) {
let mut db = self.symbol_db.write().unwrap();
db.insert(uri.to_string(), items);
}
pub fn get_symbols(&self, uri: &str) -> Vec<CompletionItem> {
let db = self.symbol_db.read().unwrap();
db.get(uri).cloned().unwrap_or_default()
}
pub fn register_workspace_symbols(&self, uri: &str, symbols: Vec<SymbolInformation>) {
let mut ws = self.workspace_symbols.write().unwrap();
ws.insert(uri.to_string(), symbols);
}
pub fn unregister_workspace_symbols(&self, uri: &str) {
let mut ws = self.workspace_symbols.write().unwrap();
ws.remove(uri);
}
fn compute_completions(
&self,
source: &str,
position: SourcePosition,
_uri: &str,
) -> Vec<CompletionItem> {
let prefix = self.extract_prefix(source, position);
let prefix_lower = prefix.to_lowercase();
let mut items: Vec<CompletionItem> = self
.get_c_keywords()
.into_iter()
.filter(|kw| prefix.is_empty() || kw.label.to_lowercase().starts_with(&prefix_lower))
.collect();
items.truncate(self.config.max_completions);
items
}
fn get_c_keywords(&self) -> Vec<CompletionItem> {
vec![
("auto", "auto ", "Automatic storage class"),
("break", "break", "Break from loop or switch"),
("case", "case ", "Case label in switch"),
("char", "char ", "Character type"),
("const", "const ", "Const qualifier"),
("continue", "continue", "Continue to next loop iteration"),
("default", "default:", "Default case in switch"),
("do", "do {\n \n} while ();", "Do-while loop"),
("double", "double ", "Double precision float"),
("else", "else ", "Else branch"),
("enum", "enum ", "Enumeration type"),
("extern", "extern ", "External linkage"),
("float", "float ", "Single precision float"),
("for", "for (;;) {\n \n}", "For loop"),
("goto", "goto ", "Unconditional jump"),
("if", "if () {\n \n}", "If statement"),
("int", "int ", "Integer type"),
("long", "long ", "Long integer type"),
("register", "register ", "Register storage class"),
("return", "return ", "Return statement"),
("short", "short ", "Short integer type"),
("signed", "signed ", "Signed qualifier"),
("sizeof", "sizeof()", "Size-of operator"),
("static", "static ", "Static storage class"),
("struct", "struct ", "Structure type"),
("switch", "switch () {\n \n}", "Switch statement"),
("typedef", "typedef ", "Type definition"),
("union", "union ", "Union type"),
("unsigned", "unsigned ", "Unsigned qualifier"),
("void", "void ", "Void type"),
("volatile", "volatile ", "Volatile qualifier"),
("while", "while () {\n \n}", "While loop"),
("_Bool", "_Bool ", "Boolean type (C99)"),
("_Complex", "_Complex ", "Complex type (C99)"),
("_Imaginary", "_Imaginary ", "Imaginary type (C99)"),
("inline", "inline ", "Inline function"),
("restrict", "restrict ", "Restrict qualifier (C99)"),
("_Alignas", "_Alignas()", "Alignment specifier (C11)"),
("_Alignof", "_Alignof()", "Alignment operator (C11)"),
("_Atomic", "_Atomic ", "Atomic type (C11)"),
("_Generic", "_Generic(, ...)", "Generic selection (C11)"),
("_Noreturn", "_Noreturn ", "No-return function (C11)"),
(
"_Static_assert",
"_Static_assert(, \"\")",
"Static assertion (C11)",
),
(
"_Thread_local",
"_Thread_local ",
"Thread-local storage (C11)",
),
]
.into_iter()
.enumerate()
.map(|(i, (label, insert, doc))| CompletionItem {
label: label.to_string(),
insert_text: insert.to_string(),
detail: Some(doc.to_string()),
documentation: Some(format!("C keyword: {}", label)),
kind: CompletionItemKind::Keyword,
sort_priority: i as u32,
deprecated: false,
filter_text: Some(label.to_string()),
is_snippet: insert.contains('\n'),
additional_edits: Vec::new(),
command: None,
data: None,
})
.collect()
}
fn extract_prefix(&self, source: &str, position: SourcePosition) -> String {
let mut prefix = String::new();
if let Some(line) = source.lines().nth(position.line.saturating_sub(1)) {
let col = position.column.saturating_sub(1).min(line.len());
let before = &line[..col];
for ch in before.chars().rev() {
if ch.is_alphanumeric() || ch == '_' {
prefix.insert(0, ch);
} else {
break;
}
}
}
prefix
}
fn compute_signatures(
&self,
_source: &str,
_position: SourcePosition,
) -> Vec<SignatureInformation> {
vec![
SignatureInformation {
label: "int printf(const char *format, ...)".to_string(),
documentation: Some("Write formatted output to stdout".to_string()),
parameters: vec![
ParameterInformation {
label: "format".to_string(),
documentation: Some("Format string with conversion specifiers".to_string()),
},
ParameterInformation {
label: "...".to_string(),
documentation: Some(
"Variable arguments matching format specifiers".to_string(),
),
},
],
},
SignatureInformation {
label: "int scanf(const char *format, ...)".to_string(),
documentation: Some("Read formatted input from stdin".to_string()),
parameters: vec![
ParameterInformation {
label: "format".to_string(),
documentation: Some("Format string with conversion specifiers".to_string()),
},
ParameterInformation {
label: "...".to_string(),
documentation: Some("Pointers to variables for storing input".to_string()),
},
],
},
]
}
fn compute_active_parameter(&self, _source: &str, _position: SourcePosition) -> Option<usize> {
Some(0)
}
fn compute_hover(&self, source: &str, position: SourcePosition) -> Option<HoverInfo> {
let word = self.extract_prefix(source, position);
if word.is_empty() {
return None;
}
for keyword in self.get_c_keywords() {
if keyword.label == word {
return Some(HoverInfo {
contents: HoverContents::Markdown(format!(
"**{}**\n\n{}\n\n```c\n{}\n```",
keyword.label,
keyword.detail.as_deref().unwrap_or("C keyword"),
keyword.insert_text,
)),
range: None,
});
}
}
None
}
fn find_definition(&self, _source: &str, position: SourcePosition, uri: &str) -> Vec<Location> {
let word = self.extract_prefix(_source, position);
if word.is_empty() {
return Vec::new();
}
vec![Location {
uri: uri.to_string(),
range: SourceRange::new(
SourcePosition::new(position.line, position.column - word.len()),
SourcePosition::new(position.line, position.column),
),
}]
}
fn find_declaration(
&self,
_source: &str,
position: SourcePosition,
uri: &str,
) -> Vec<Location> {
self.find_definition(_source, position, uri)
}
fn find_references(
&self,
source: &str,
position: SourcePosition,
uri: &str,
_include_declaration: bool,
) -> Vec<Location> {
let word = self.extract_prefix(source, position);
if word.is_empty() {
return Vec::new();
}
let mut refs = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let mut col = 0;
while let Some(pos) = line[col..].find(&word) {
let abs_col = col + pos;
refs.push(Location {
uri: uri.to_string(),
range: SourceRange::new(
SourcePosition::new(line_num + 1, abs_col + 1),
SourcePosition::new(line_num + 1, abs_col + 1 + word.len()),
),
});
col = abs_col + word.len();
}
}
refs
}
fn compute_document_symbols(&self, source: &str, _uri: &str) -> Vec<DocumentSymbol> {
let mut symbols = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
let line_num = line_num + 1;
if trimmed.starts_with("int main") || trimmed.starts_with("void main") {
symbols.push(DocumentSymbol {
name: "main".to_string(),
detail: Some("int (int argc, char **argv)".to_string()),
kind: SymbolKind::Function,
deprecated: false,
range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, line.len()),
),
selection_range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, 5),
),
children: Vec::new(),
tags: Vec::new(),
});
} else if trimmed.starts_with("int ") || trimmed.starts_with("void ") {
if let Some(name) = trimmed
.split_whitespace()
.nth(1)
.and_then(|s| s.split('(').next())
{
if !name.is_empty() && name != "main" {
symbols.push(DocumentSymbol {
name: name.to_string(),
detail: Some(trimmed.to_string()),
kind: SymbolKind::Function,
deprecated: false,
range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, line.len()),
),
selection_range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, name.len()),
),
children: Vec::new(),
tags: Vec::new(),
});
}
}
} else if trimmed.starts_with("struct ") {
if let Some(name) = trimmed.split_whitespace().nth(1) {
let clean_name = name.trim_end_matches('{').trim_end_matches(';').trim();
if !clean_name.is_empty() {
symbols.push(DocumentSymbol {
name: clean_name.to_string(),
detail: Some("struct".to_string()),
kind: SymbolKind::Struct,
deprecated: false,
range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, line.len()),
),
selection_range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, clean_name.len()),
),
children: Vec::new(),
tags: Vec::new(),
});
}
}
} else if trimmed.starts_with("enum ") {
if let Some(name) = trimmed.split_whitespace().nth(1) {
let clean_name = name.trim_end_matches('{').trim_end_matches(';').trim();
if !clean_name.is_empty() {
symbols.push(DocumentSymbol {
name: clean_name.to_string(),
detail: Some("enum".to_string()),
kind: SymbolKind::Enum,
deprecated: false,
range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, line.len()),
),
selection_range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, clean_name.len()),
),
children: Vec::new(),
tags: Vec::new(),
});
}
}
} else if trimmed.starts_with("#define ") {
if let Some(name) = trimmed[8..].split_whitespace().next() {
symbols.push(DocumentSymbol {
name: name.to_string(),
detail: Some("macro".to_string()),
kind: SymbolKind::Constant,
deprecated: false,
range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, line.len()),
),
selection_range: SourceRange::new(
SourcePosition::new(line_num, 1),
SourcePosition::new(line_num, name.len()),
),
children: Vec::new(),
tags: Vec::new(),
});
}
}
}
symbols
}
fn compute_semantic_tokens(&self, source: &str) -> Vec<SemanticToken> {
let mut tokens = Vec::new();
let keywords: HashSet<&str> = [
"auto", "break", "case", "char", "const", "continue", "default", "do", "double",
"else", "enum", "extern", "float", "for", "goto", "if", "int", "long", "register",
"return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef",
"union", "unsigned", "void", "volatile", "while",
]
.iter()
.cloned()
.collect();
let types: HashSet<&str> = [
"int", "char", "float", "double", "void", "long", "short", "unsigned", "signed",
"size_t", "ssize_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "int8_t",
"int16_t", "int32_t", "int64_t",
]
.iter()
.cloned()
.collect();
for (line_num, line) in source.lines().enumerate() {
let mut col = 0u32;
let chars: Vec<char> = line.chars().collect();
while col < chars.len() as u32 {
let ch = chars[col as usize];
if ch == '/' && (col + 1) < chars.len() as u32 {
let next = chars[(col + 1) as usize];
if next == '/' {
tokens.push(SemanticToken {
line: line_num as u32,
start_char: col,
length: (chars.len() as u32 - col),
token_type: SemanticTokenType::Comment as u32,
token_modifiers: 0,
});
break;
} else if next == '*' {
tokens.push(SemanticToken {
line: line_num as u32,
start_char: col,
length: 2,
token_type: SemanticTokenType::Comment as u32,
token_modifiers: 1 << SemanticTokenModifier::Documentation as u32,
});
col += 2;
continue;
}
}
if ch == '"' {
let start = col;
col += 1;
while col < chars.len() as u32
&& chars[col as usize] != '"'
&& chars[col as usize] != '\n'
{
if chars[col as usize] == '\\' {
col += 1;
}
col += 1;
}
if col < chars.len() as u32 {
col += 1; }
tokens.push(SemanticToken {
line: line_num as u32,
start_char: start,
length: col - start,
token_type: SemanticTokenType::String as u32,
token_modifiers: 0,
});
continue;
}
if ch == '#' {
let start = col;
let length = chars.len() as u32 - col;
tokens.push(SemanticToken {
line: line_num as u32,
start_char: start,
length,
token_type: SemanticTokenType::Macro as u32,
token_modifiers: 0,
});
break;
}
if ch.is_alphabetic() || ch == '_' {
let start = col;
while col < chars.len() as u32
&& (chars[col as usize].is_alphanumeric() || chars[col as usize] == '_')
{
col += 1;
}
let word: String = chars[start as usize..col as usize].iter().collect();
let token_type = if keywords.contains(word.as_str()) {
SemanticTokenType::Keyword as u32
} else if types.contains(word.as_str()) {
SemanticTokenType::Type as u32
} else if word.chars().next().map_or(false, |c| c.is_uppercase()) {
SemanticTokenType::Type as u32
} else if line.trim_start().starts_with(&word) {
SemanticTokenType::Function as u32
} else {
SemanticTokenType::Variable as u32
};
tokens.push(SemanticToken {
line: line_num as u32,
start_char: start,
length: col - start,
token_type,
token_modifiers: 0,
});
continue;
}
if ch.is_ascii_digit() {
let start = col;
while col < chars.len() as u32
&& (chars[col as usize].is_ascii_digit()
|| chars[col as usize] == '.'
|| chars[col as usize] == 'x'
|| chars[col as usize] == 'X'
|| chars[col as usize].is_alphabetic())
{
col += 1;
}
tokens.push(SemanticToken {
line: line_num as u32,
start_char: start,
length: col - start,
token_type: SemanticTokenType::Number as u32,
token_modifiers: 0,
});
continue;
}
col += 1;
}
}
tokens
}
fn encode_semantic_tokens(&self, tokens: &[SemanticToken]) -> Vec<u32> {
if tokens.is_empty() {
return Vec::new();
}
let mut data = Vec::with_capacity(tokens.len() * 5);
let mut prev_line = 0u32;
let mut prev_start = 0u32;
for token in tokens {
let delta_line = token.line - prev_line;
let delta_start = if delta_line == 0 {
token.start_char - prev_start
} else {
token.start_char
};
data.push(delta_line);
data.push(delta_start);
data.push(token.length);
data.push(token.token_type);
data.push(token.token_modifiers);
prev_line = token.line;
prev_start = token.start_char;
}
data
}
fn format_document(&self, source: &str, options: &FormattingOptions) -> String {
let lines: Vec<String> = source
.lines()
.map(|line| {
if options.trim_trailing_whitespace {
line.trim_end().to_string()
} else {
line.to_string()
}
})
.collect();
let mut result = String::new();
let mut blank_count = 0;
for line in &lines {
if line.trim().is_empty() {
blank_count += 1;
if blank_count <= options.max_empty_lines {
result.push('\n');
}
} else {
blank_count = 0;
result.push_str(line);
result.push('\n');
}
}
if options.insert_final_newline && !result.ends_with('\n') {
result.push('\n');
}
result
}
fn to_lsp_completion_item(&self, item: &CompletionItem) -> LspCompletionItem {
LspCompletionItem {
label: item.label.clone(),
kind: Some(item.kind.as_lsp_kind()),
detail: item.detail.clone(),
documentation: item.documentation.clone(),
deprecated: if item.deprecated { Some(true) } else { None },
preselect: if item.sort_priority == 0 {
Some(true)
} else {
None
},
sort_text: Some(
item.filter_text
.clone()
.unwrap_or_else(|| item.label.clone()),
),
filter_text: item.filter_text.clone(),
insert_text: if item.is_snippet {
Some(item.insert_text.clone())
} else {
Some(item.insert_text.clone())
},
insert_text_format: if item.is_snippet { Some(2) } else { Some(1) },
text_edit: None,
additional_text_edits: None,
commit_characters: None,
command: item.command.as_ref().map(|cmd| LspCommand {
title: item.label.clone(),
command: cmd.clone(),
arguments: None,
}),
data: item.data.clone(),
}
}
fn to_lsp_document_symbol(&self, sym: &DocumentSymbol) -> LspDocumentSymbol {
LspDocumentSymbol {
name: sym.name.clone(),
detail: sym.detail.clone(),
kind: sym.kind.as_lsp_kind(),
tags: if sym.tags.is_empty() {
None
} else {
Some(sym.tags.iter().map(|t| *t as u32).collect())
},
deprecated: if sym.deprecated { Some(true) } else { None },
range: LspRange {
start: LspPosition {
line: (sym.range.start.line - 1) as u32,
character: (sym.range.start.column - 1) as u32,
},
end: LspPosition {
line: (sym.range.end.line - 1) as u32,
character: (sym.range.end.column - 1) as u32,
},
},
selection_range: LspRange {
start: LspPosition {
line: (sym.selection_range.start.line - 1) as u32,
character: (sym.selection_range.start.column - 1) as u32,
},
end: LspPosition {
line: (sym.selection_range.end.line - 1) as u32,
character: (sym.selection_range.end.column - 1) as u32,
},
},
children: if sym.children.is_empty() {
None
} else {
Some(
sym.children
.iter()
.map(|c| self.to_lsp_document_symbol(c))
.collect(),
)
},
}
}
}
impl Default for X86CodeCompletionService {
fn default() -> Self {
Self::new()
}
}
pub struct X86IndexService {
symbols: Arc<RwLock<BTreeMap<String, Vec<SymbolEntry>>>>,
callers: Arc<RwLock<HashMap<String, HashSet<String>>>>,
callees: Arc<RwLock<HashMap<String, HashSet<String>>>>,
type_derived_to_base: Arc<RwLock<HashMap<String, String>>>,
type_base_to_derived: Arc<RwLock<HashMap<String, Vec<String>>>>,
include_graph: Arc<RwLock<HashMap<String, Vec<String>>>>,
reverse_include_graph: Arc<RwLock<HashMap<String, Vec<String>>>>,
db_path: Option<String>,
storage: Arc<Mutex<IndexStorage>>,
total_files: AtomicU64,
total_symbols: AtomicU64,
}
#[derive(Debug, Clone)]
pub struct SymbolEntry {
pub name: String,
pub kind: SymbolKind,
pub file: String,
pub location: SourcePosition,
pub usr: String,
pub is_definition: bool,
pub type_string: Option<String>,
pub documentation: Option<String>,
pub language: String,
pub parent: Option<String>,
}
struct IndexStorage {
symbols: Vec<SymbolEntry>,
call_edges: Vec<(String, String)>,
type_edges: Vec<(String, String)>,
include_edges: Vec<(String, String)>,
dirty: bool,
}
impl IndexStorage {
fn new() -> Self {
Self {
symbols: Vec::new(),
call_edges: Vec::new(),
type_edges: Vec::new(),
include_edges: Vec::new(),
dirty: false,
}
}
}
impl X86IndexService {
pub fn new(db_path: Option<&str>) -> Self {
let storage = IndexStorage::new();
Self {
symbols: Arc::new(RwLock::new(BTreeMap::new())),
callers: Arc::new(RwLock::new(HashMap::new())),
callees: Arc::new(RwLock::new(HashMap::new())),
type_derived_to_base: Arc::new(RwLock::new(HashMap::new())),
type_base_to_derived: Arc::new(RwLock::new(HashMap::new())),
include_graph: Arc::new(RwLock::new(HashMap::new())),
reverse_include_graph: Arc::new(RwLock::new(HashMap::new())),
db_path: db_path.map(String::from),
storage: Arc::new(Mutex::new(storage)),
total_files: AtomicU64::new(0),
total_symbols: AtomicU64::new(0),
}
}
pub fn total_symbols(&self) -> usize {
self.total_symbols.load(Ordering::SeqCst) as usize
}
pub fn total_files(&self) -> usize {
self.total_files.load(Ordering::SeqCst) as usize
}
pub fn database_size_bytes(&self) -> Option<u64> {
let storage = self.storage.lock().unwrap();
let mut size: u64 = 0;
for sym in &storage.symbols {
size += sym.name.len() as u64;
size += sym.file.len() as u64;
size += sym.usr.len() as u64;
size += sym
.type_string
.as_ref()
.map(|s| s.len() as u64)
.unwrap_or(0);
size += sym
.documentation
.as_ref()
.map(|s| s.len() as u64)
.unwrap_or(0);
size += sym.language.len() as u64;
size += sym.parent.as_ref().map(|s| s.len() as u64).unwrap_or(0);
size += 64; }
Some(size)
}
pub fn index_file(&self, file: &str, source: &str, language: &str) -> usize {
let symbols = self.extract_symbols(source, file, language);
let count = symbols.len();
{
let mut sym_map = self.symbols.write().unwrap();
sym_map.insert(file.to_string(), symbols.clone());
}
{
let mut storage = self.storage.lock().unwrap();
storage.symbols.retain(|s| s.file != file);
storage.symbols.extend(symbols);
storage.dirty = true;
}
self.total_symbols.fetch_add(count as u64, Ordering::SeqCst);
self.total_files.fetch_add(1, Ordering::SeqCst);
let includes = self.extract_includes(source);
{
let mut inc_graph = self.include_graph.write().unwrap();
inc_graph.insert(file.to_string(), includes.clone());
}
{
let mut rev_inc = self.reverse_include_graph.write().unwrap();
for inc in &includes {
rev_inc
.entry(inc.clone())
.or_default()
.push(file.to_string());
}
}
count
}
pub fn remove_file(&self, file: &str) {
{
let mut sym_map = self.symbols.write().unwrap();
if let Some(removed) = sym_map.remove(file) {
self.total_symbols
.fetch_sub(removed.len() as u64, Ordering::SeqCst);
}
}
{
let mut inc_graph = self.include_graph.write().unwrap();
inc_graph.remove(file);
}
{
let mut rev_inc = self.reverse_include_graph.write().unwrap();
rev_inc.remove(file);
}
{
let mut storage = self.storage.lock().unwrap();
storage.symbols.retain(|s| s.file != file);
storage.dirty = true;
}
if self.total_files.load(Ordering::SeqCst) > 0 {
self.total_files.fetch_sub(1, Ordering::SeqCst);
}
}
pub fn find_symbol(&self, name: &str) -> Vec<SymbolEntry> {
let sym_map = self.symbols.read().unwrap();
let name_lower = name.to_lowercase();
let mut results = Vec::new();
for entries in sym_map.values() {
for entry in entries {
if entry.name.to_lowercase() == name_lower {
results.push(entry.clone());
}
}
}
results
}
pub fn query_symbols(&self, query: &str) -> Vec<SymbolEntry> {
let sym_map = self.symbols.read().unwrap();
let query_lower = query.to_lowercase();
let mut results = Vec::new();
for entries in sym_map.values() {
for entry in entries {
if entry.name.to_lowercase().contains(&query_lower) {
results.push(entry.clone());
}
}
}
results
}
pub fn find_references(&self, name: &str) -> Vec<SymbolEntry> {
self.find_symbol(name)
}
pub fn find_definition(&self, name: &str) -> Option<SymbolEntry> {
let results = self.find_symbol(name);
results.into_iter().find(|s| s.is_definition).or_else(|| {
let sym_map = self.symbols.read().unwrap();
for entries in sym_map.values() {
for entry in entries {
if entry.name == name && entry.is_definition {
return Some(entry.clone());
}
}
}
None
})
}
pub fn referencing_files(&self, name: &str) -> Vec<String> {
let results = self.find_symbol(name);
let mut files: HashSet<String> = HashSet::new();
for entry in results {
files.insert(entry.file);
}
files.into_iter().collect()
}
pub fn add_call_edge(&self, caller: &str, callee: &str) {
{
let mut callers_map = self.callers.write().unwrap();
callers_map
.entry(caller.to_string())
.or_default()
.insert(callee.to_string());
}
{
let mut callees_map = self.callees.write().unwrap();
callees_map
.entry(callee.to_string())
.or_default()
.insert(caller.to_string());
}
{
let mut storage = self.storage.lock().unwrap();
storage
.call_edges
.push((caller.to_string(), callee.to_string()));
storage.dirty = true;
}
}
pub fn get_callees(&self, caller: &str) -> Vec<String> {
let callers_map = self.callers.read().unwrap();
callers_map
.get(caller)
.map(|set| set.iter().cloned().collect())
.unwrap_or_default()
}
pub fn get_callers(&self, callee: &str) -> Vec<String> {
let callees_map = self.callees.read().unwrap();
callees_map
.get(callee)
.map(|set| set.iter().cloned().collect())
.unwrap_or_default()
}
pub fn has_call_edge(&self, caller: &str, callee: &str) -> bool {
let callers_map = self.callers.read().unwrap();
callers_map
.get(caller)
.map(|set| set.contains(callee))
.unwrap_or(false)
}
pub fn call_graph_stats(&self) -> CallGraphStats {
let callers_map = self.callers.read().unwrap();
let callees_map = self.callees.read().unwrap();
CallGraphStats {
total_edges: callers_map.values().map(|s| s.len()).sum(),
total_callers: callers_map.len(),
total_callees: callees_map.len(),
}
}
pub fn add_type_edge(&self, derived: &str, base: &str) {
{
let mut dtb = self.type_derived_to_base.write().unwrap();
dtb.insert(derived.to_string(), base.to_string());
}
{
let mut btd = self.type_base_to_derived.write().unwrap();
btd.entry(base.to_string())
.or_default()
.push(derived.to_string());
}
{
let mut storage = self.storage.lock().unwrap();
storage
.type_edges
.push((derived.to_string(), base.to_string()));
storage.dirty = true;
}
}
pub fn get_base_type(&self, derived: &str) -> Option<String> {
let dtb = self.type_derived_to_base.read().unwrap();
dtb.get(derived).cloned()
}
pub fn get_derived_types(&self, base: &str) -> Vec<String> {
let btd = self.type_base_to_derived.read().unwrap();
btd.get(base).cloned().unwrap_or_default()
}
pub fn all_types(&self) -> Vec<String> {
let dtb = self.type_derived_to_base.read().unwrap();
let mut types: HashSet<String> = HashSet::new();
for (derived, base) in dtb.iter() {
types.insert(derived.clone());
types.insert(base.clone());
}
types.into_iter().collect()
}
pub fn is_derived_from(&self, derived: &str, base: &str) -> bool {
let dtb = self.type_derived_to_base.read().unwrap();
let mut current = Some(derived.to_string());
while let Some(curr) = current {
if curr == base {
return true;
}
current = dtb.get(&curr).cloned();
}
false
}
pub fn get_includes(&self, file: &str) -> Vec<String> {
let inc_graph = self.include_graph.read().unwrap();
inc_graph.get(file).cloned().unwrap_or_default()
}
pub fn get_included_by(&self, file: &str) -> Vec<String> {
let rev_inc = self.reverse_include_graph.read().unwrap();
rev_inc.get(file).cloned().unwrap_or_default()
}
pub fn get_transitive_includes(&self, file: &str) -> Vec<String> {
let inc_graph = self.include_graph.read().unwrap();
let mut visited = HashSet::new();
let mut result = Vec::new();
let mut queue = VecDeque::new();
queue.push_back(file.to_string());
visited.insert(file.to_string());
while let Some(current) = queue.pop_front() {
if let Some(includes) = inc_graph.get(¤t) {
for inc in includes {
if visited.insert(inc.clone()) {
result.push(inc.clone());
queue.push_back(inc.clone());
}
}
}
}
result
}
pub fn has_circular_include(&self, file: &str) -> Option<Vec<String>> {
let inc_graph = self.include_graph.read().unwrap();
let mut stack = Vec::new();
let mut visited = HashSet::new();
let mut in_stack = HashSet::new();
fn dfs(
current: &str,
inc_graph: &HashMap<String, Vec<String>>,
stack: &mut Vec<String>,
visited: &mut HashSet<String>,
in_stack: &mut HashSet<String>,
) -> Option<Vec<String>> {
visited.insert(current.to_string());
in_stack.insert(current.to_string());
stack.push(current.to_string());
if let Some(includes) = inc_graph.get(current) {
for inc in includes {
if in_stack.contains(inc) {
let pos = stack.iter().position(|x| x == inc).unwrap_or(0);
let mut cycle: Vec<String> = stack[pos..].to_vec();
cycle.push(inc.clone());
return Some(cycle);
}
if !visited.contains(inc) {
if let Some(cycle) = dfs(inc, inc_graph, stack, visited, in_stack) {
return Some(cycle);
}
}
}
}
stack.pop();
in_stack.remove(current);
None
}
dfs(file, &inc_graph, &mut stack, &mut visited, &mut in_stack)
}
pub fn save(&self) -> Result<(), IndexError> {
let mut storage = self.storage.lock().unwrap();
if !storage.dirty {
return Ok(());
}
storage.dirty = false;
if let Some(ref path) = self.db_path {
let _ = path; }
Ok(())
}
pub fn load(&self) -> Result<(), IndexError> {
if let Some(ref _path) = self.db_path {
}
Ok(())
}
pub fn clear(&self) {
self.symbols.write().unwrap().clear();
self.callers.write().unwrap().clear();
self.callees.write().unwrap().clear();
self.type_derived_to_base.write().unwrap().clear();
self.type_base_to_derived.write().unwrap().clear();
self.include_graph.write().unwrap().clear();
self.reverse_include_graph.write().unwrap().clear();
let mut storage = self.storage.lock().unwrap();
storage.symbols.clear();
storage.call_edges.clear();
storage.type_edges.clear();
storage.include_edges.clear();
storage.dirty = true;
self.total_files.store(0, Ordering::SeqCst);
self.total_symbols.store(0, Ordering::SeqCst);
}
fn extract_symbols(&self, source: &str, file: &str, language: &str) -> Vec<SymbolEntry> {
let mut symbols = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with("/*") {
continue;
}
let line_num = line_num + 1;
if let Some(name) = self.try_extract_function_name(trimmed) {
let is_def =
trimmed.contains('{') || (!trimmed.ends_with(';') && trimmed.contains('('));
symbols.push(SymbolEntry {
name: name.clone(),
kind: SymbolKind::Function,
file: file.to_string(),
location: SourcePosition::new(line_num, 1),
usr: format!("c:@F@{}", name),
is_definition: is_def,
type_string: Some(trimmed.to_string()),
documentation: None,
language: language.to_string(),
parent: None,
});
}
if let Some(name) = self.try_extract_struct_name(trimmed) {
symbols.push(SymbolEntry {
name: name.clone(),
kind: SymbolKind::Struct,
file: file.to_string(),
location: SourcePosition::new(line_num, 1),
usr: format!("c:@S@{}", name),
is_definition: trimmed.contains('{'),
type_string: Some("struct".to_string()),
documentation: None,
language: language.to_string(),
parent: None,
});
}
if let Some(name) = self.try_extract_enum_name(trimmed) {
symbols.push(SymbolEntry {
name: name.clone(),
kind: SymbolKind::Enum,
file: file.to_string(),
location: SourcePosition::new(line_num, 1),
usr: format!("c:@E@{}", name),
is_definition: trimmed.contains('{'),
type_string: Some("enum".to_string()),
documentation: None,
language: language.to_string(),
parent: None,
});
}
if let Some(name) = self.try_extract_macro_name(trimmed) {
symbols.push(SymbolEntry {
name: name.clone(),
kind: SymbolKind::Constant,
file: file.to_string(),
location: SourcePosition::new(line_num, 1),
usr: format!("c:@M@{}", name),
is_definition: true,
type_string: Some("macro".to_string()),
documentation: None,
language: language.to_string(),
parent: None,
});
}
if let Some(name) = self.try_extract_variable_name(trimmed) {
symbols.push(SymbolEntry {
name: name.clone(),
kind: SymbolKind::Variable,
file: file.to_string(),
location: SourcePosition::new(line_num, 1),
usr: format!("c:@V@{}", name),
is_definition: !trimmed.starts_with("extern"),
type_string: Some(trimmed.to_string()),
documentation: None,
language: language.to_string(),
parent: None,
});
}
}
symbols
}
fn try_extract_function_name(&self, line: &str) -> Option<String> {
let line = line.trim();
if !line.contains('(') || !line.contains(')') {
return None;
}
if line.starts_with("if ") || line.starts_with("while ") || line.starts_with("for ") {
return None;
}
let before_paren = line.split('(').next()?;
let parts: Vec<&str> = before_paren.split_whitespace().collect();
parts.last().map(|s| s.trim_end_matches('*').to_string())
}
fn try_extract_struct_name(&self, line: &str) -> Option<String> {
let line = line.trim();
if line.starts_with("struct ") {
let after = &line[7..];
let name = after
.split(|c: char| c == '{' || c == ';' || c == ' ' || c == ':')
.next()?;
if !name.is_empty() && name != "struct" {
return Some(name.to_string());
}
}
None
}
fn try_extract_enum_name(&self, line: &str) -> Option<String> {
let line = line.trim();
if line.starts_with("enum ") {
let after = &line[5..];
let name = after
.split(|c: char| c == '{' || c == ';' || c == ' ' || c == ':')
.next()?;
if !name.is_empty() && name != "enum" {
return Some(name.to_string());
}
}
None
}
fn try_extract_macro_name(&self, line: &str) -> Option<String> {
let line = line.trim();
if line.starts_with("#define ") {
let after = &line[8..];
let name = after
.split(|c: char| c == '(' || c == ' ' || c == '\t')
.next()?;
if !name.is_empty() {
return Some(name.to_string());
}
}
None
}
fn try_extract_variable_name(&self, line: &str) -> Option<String> {
let line = line.trim();
let types = [
"int ",
"char ",
"float ",
"double ",
"void ",
"long ",
"short ",
"unsigned ",
"signed ",
"size_t ",
"uint8_t ",
"uint16_t ",
"uint32_t ",
"uint64_t ",
"int8_t ",
"int16_t ",
"int32_t ",
"int64_t ",
"const ",
];
for t in &types {
if line.starts_with(t) {
let after = &line[t.len()..];
let name = after
.split(|c: char| c == '=' || c == ';' || c == '[' || c == ' ' || c == ',')
.next()?;
let name = name.trim_matches('*');
if !name.is_empty() && !name.chars().next()?.is_ascii_digit() {
return Some(name.to_string());
}
}
}
None
}
fn extract_includes(&self, source: &str) -> Vec<String> {
let mut includes = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if trimmed.starts_with("#include ") {
if let Some(start) = trimmed.find('"').or_else(|| trimmed.find('<')) {
let end_char = if trimmed.as_bytes()[start] == b'"' {
'"'
} else {
'>'
};
if let Some(end) = trimmed[start + 1..].find(end_char) {
let include_path = &trimmed[start + 1..start + 1 + end];
includes.push(include_path.to_string());
}
}
}
}
includes
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IndexError {
IoError(String),
DatabaseError(String),
SerializationError(String),
}
impl fmt::Display for IndexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IoError(msg) => write!(f, "IO error: {}", msg),
Self::DatabaseError(msg) => write!(f, "Database error: {}", msg),
Self::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
}
}
}
#[derive(Debug, Clone)]
pub struct CallGraphStats {
pub total_edges: usize,
pub total_callers: usize,
pub total_callees: usize,
}
pub struct X86DiagnosticService {
enabled: bool,
file_diagnostics: Arc<RwLock<HashMap<String, FileDiagnostics>>>,
groups: Arc<RwLock<HashMap<String, Vec<CompilerDiagnostic>>>>,
quick_fix_providers: Vec<Box<dyn DiagnosticQuickFixProvider>>,
listeners: Arc<Mutex<Vec<Box<dyn DiagnosticListener>>>>,
total_diagnostics: AtomicU64,
supported_codes: HashSet<String>,
}
#[derive(Debug, Clone)]
struct FileDiagnostics {
source_hash: u64,
diagnostics: Vec<CompilerDiagnostic>,
groups: HashMap<String, Vec<usize>>,
timestamp: Instant,
}
impl FileDiagnostics {
fn new(source_hash: u64) -> Self {
Self {
source_hash,
diagnostics: Vec::new(),
groups: HashMap::new(),
timestamp: Instant::now(),
}
}
}
pub trait DiagnosticQuickFixProvider: Send + Sync {
fn provides_for(&self, diagnostic: &CompilerDiagnostic) -> bool;
fn get_fixes(&self, diagnostic: &CompilerDiagnostic, source: &str) -> Vec<FixItSuggestion>;
}
pub trait DiagnosticListener: Send + Sync {
fn on_diagnostics_changed(&self, uri: &str, diagnostics: &[CompilerDiagnostic]);
}
pub struct DefaultQuickFixProvider;
impl DiagnosticQuickFixProvider for DefaultQuickFixProvider {
fn provides_for(&self, diagnostic: &CompilerDiagnostic) -> bool {
diagnostic.code.is_some()
}
fn get_fixes(&self, diagnostic: &CompilerDiagnostic, _source: &str) -> Vec<FixItSuggestion> {
match diagnostic.code.as_deref() {
Some("Wunused-variable") => {
vec![FixItSuggestion::new(
"Remove unused variable",
"",
diagnostic.line,
diagnostic.column,
diagnostic.end_line.unwrap_or(diagnostic.line),
diagnostic.end_column.unwrap_or(diagnostic.column),
)]
}
Some("Wimplicit-function-declaration") => {
vec![FixItSuggestion::new(
"Add declaration before use",
"extern void function_placeholder(void);\n",
diagnostic.line,
1,
diagnostic.line,
1,
)]
}
Some("Wmissing-braces") => {
vec![FixItSuggestion::new(
"Add braces",
"{}",
diagnostic.line,
diagnostic.column,
diagnostic.line,
diagnostic.column,
)]
}
_ => Vec::new(),
}
}
}
impl X86DiagnosticService {
pub fn new(enabled: bool) -> Self {
let mut supported_codes = HashSet::new();
supported_codes.insert("Wunused-variable".to_string());
supported_codes.insert("Wimplicit-function-declaration".to_string());
supported_codes.insert("Wmissing-braces".to_string());
supported_codes.insert("Wunused-parameter".to_string());
supported_codes.insert("Wshadow".to_string());
supported_codes.insert("Wtype-limits".to_string());
supported_codes.insert("Eundeclared".to_string());
supported_codes.insert("Etype-mismatch".to_string());
supported_codes.insert("Esyntax-error".to_string());
supported_codes.insert("Emissing-semicolon".to_string());
Self {
enabled,
file_diagnostics: Arc::new(RwLock::new(HashMap::new())),
groups: Arc::new(RwLock::new(HashMap::new())),
quick_fix_providers: vec![Box::new(DefaultQuickFixProvider)],
listeners: Arc::new(Mutex::new(Vec::new())),
total_diagnostics: AtomicU64::new(0),
supported_codes,
}
}
pub fn is_code_supported(&self, code: &str) -> bool {
self.supported_codes.contains(code)
}
pub fn monitored_file_count(&self) -> usize {
self.file_diagnostics.read().unwrap().len()
}
pub fn total_diagnostics_count(&self) -> u64 {
self.total_diagnostics.load(Ordering::SeqCst)
}
pub fn add_fix_provider(&mut self, provider: Box<dyn DiagnosticQuickFixProvider>) {
self.quick_fix_providers.push(provider);
}
pub fn add_listener(&mut self, listener: Box<dyn DiagnosticListener>) {
self.listeners.lock().unwrap().push(listener);
}
pub fn analyze(&self, uri: &str, source: &str, force: bool) -> Vec<CompilerDiagnostic> {
if !self.enabled {
return Vec::new();
}
let source_hash = hash_source(source);
if !force {
let cache = self.file_diagnostics.read().unwrap();
if let Some(entry) = cache.get(uri) {
if entry.source_hash == source_hash {
return entry.diagnostics.clone();
}
}
}
let diagnostics = self.compute_diagnostics(source, uri);
let count = diagnostics.len();
{
let mut cache = self.file_diagnostics.write().unwrap();
let mut entry = FileDiagnostics::new(source_hash);
entry.diagnostics = diagnostics.clone();
entry.timestamp = Instant::now();
let mut groups: HashMap<String, Vec<usize>> = HashMap::new();
for (i, diag) in diagnostics.iter().enumerate() {
groups.entry(diag.category.clone()).or_default().push(i);
groups
.entry(diag.code.clone().unwrap_or_else(|| "nocode".to_string()))
.or_default()
.push(i);
}
entry.groups = groups;
cache.insert(uri.to_string(), entry);
}
self.total_diagnostics
.fetch_add(count as u64, Ordering::SeqCst);
let listeners = self.listeners.lock().unwrap();
for listener in listeners.iter() {
listener.on_diagnostics_changed(uri, &diagnostics);
}
diagnostics
}
pub fn get_cached(&self, uri: &str) -> Option<Vec<CompilerDiagnostic>> {
let cache = self.file_diagnostics.read().unwrap();
cache.get(uri).map(|e| e.diagnostics.clone())
}
pub fn is_fresh(&self, uri: &str, source_hash: u64) -> bool {
let cache = self.file_diagnostics.read().unwrap();
cache
.get(uri)
.map(|e| e.source_hash == source_hash)
.unwrap_or(false)
}
pub fn invalidate(&self, uri: &str) {
let mut cache = self.file_diagnostics.write().unwrap();
cache.remove(uri);
}
pub fn filter_by_severity(
&self,
uri: &str,
min_severity: DiagnosticSeverity,
) -> Vec<CompilerDiagnostic> {
self.get_cached(uri)
.unwrap_or_default()
.into_iter()
.filter(|d| d.severity >= min_severity)
.collect()
}
pub fn group_by_code(&self, uri: &str) -> HashMap<String, Vec<CompilerDiagnostic>> {
let mut groups: HashMap<String, Vec<CompilerDiagnostic>> = HashMap::new();
for diag in self.get_cached(uri).unwrap_or_default() {
let key = diag.code.clone().unwrap_or_else(|| "nocode".to_string());
groups.entry(key).or_default().push(diag);
}
groups
}
pub fn group_by_category(&self, uri: &str) -> HashMap<String, Vec<CompilerDiagnostic>> {
let mut groups: HashMap<String, Vec<CompilerDiagnostic>> = HashMap::new();
for diag in self.get_cached(uri).unwrap_or_default() {
groups.entry(diag.category.clone()).or_default().push(diag);
}
groups
}
pub fn get_quick_fixes(
&self,
diagnostic: &CompilerDiagnostic,
source: &str,
) -> Vec<FixItSuggestion> {
let mut fixes = Vec::new();
for provider in &self.quick_fix_providers {
if provider.provides_for(diagnostic) {
fixes.extend(provider.get_fixes(diagnostic, source));
}
}
fixes.extend(diagnostic.fixits.clone());
fixes
}
pub fn to_lsp_diagnostics(&self, uri: &str) -> Option<LspPublishDiagnosticsParams> {
let diagnostics = self.get_cached(uri)?;
let lsp_diags: Vec<LspDiagnostic> = diagnostics
.into_iter()
.map(|d| {
let severity = match d.severity {
DiagnosticSeverity::Error => 1,
DiagnosticSeverity::Warning => 2,
DiagnosticSeverity::Note => 3,
DiagnosticSeverity::Ignored => 0,
DiagnosticSeverity::Fatal => 1,
};
let related_info = if d.notes.is_empty() {
None
} else {
Some(
d.notes
.into_iter()
.map(|note| LspDiagnosticRelatedInformation {
location: LspLocation {
uri: note.file,
range: LspRange {
start: LspPosition {
line: (note.line - 1) as u32,
character: (note.column - 1) as u32,
},
end: LspPosition {
line: (note.line - 1) as u32,
character: (note.column) as u32,
},
},
},
message: note.message,
})
.collect(),
)
};
LspDiagnostic {
range: LspRange {
start: LspPosition {
line: (d.line - 1) as u32,
character: (d.column - 1) as u32,
},
end: LspPosition {
line: (d.end_line.unwrap_or(d.line) - 1) as u32,
character: (d.end_column.unwrap_or(d.column)) as u32,
},
},
severity: Some(severity),
code: d.code.clone(),
source: Some("clang-x86".to_string()),
message: d.message,
tags: None,
related_information: related_info,
data: None,
}
})
.collect();
Some(LspPublishDiagnosticsParams {
uri: uri.to_string(),
version: None,
diagnostics: lsp_diags,
})
}
pub fn clear_all(&self) {
self.file_diagnostics.write().unwrap().clear();
self.groups.write().unwrap().clear();
self.total_diagnostics.store(0, Ordering::SeqCst);
}
fn compute_diagnostics(&self, source: &str, uri: &str) -> Vec<CompilerDiagnostic> {
let mut diagnostics = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let line_num = line_num + 1;
let trimmed = line.trim();
if !trimmed.is_empty()
&& !trimmed.starts_with("//")
&& !trimmed.starts_with("/*")
&& !trimmed.starts_with("#")
&& !trimmed.starts_with("if")
&& !trimmed.starts_with("else")
&& !trimmed.starts_with("for")
&& !trimmed.starts_with("while")
&& !trimmed.starts_with("switch")
&& !trimmed.starts_with("{")
&& !trimmed.starts_with("}")
&& !trimmed.starts_with("return")
&& !trimmed.ends_with(';')
&& !trimmed.ends_with('{')
&& !trimmed.ends_with('}')
&& !trimmed.ends_with(':')
&& !trimmed.ends_with('\\')
&& !trimmed.contains("//")
{
if trimmed.contains('=')
|| trimmed.contains("int ")
|| trimmed.contains("char ")
|| trimmed.contains("float ")
|| trimmed.contains("double ")
{
diagnostics.push(CompilerDiagnostic {
severity: DiagnosticSeverity::Warning,
message: "Statement may be missing a semicolon".to_string(),
file: uri.to_string(),
line: line_num,
column: trimmed.len(),
end_line: Some(line_num),
end_column: Some(trimmed.len()),
category: "syntax".to_string(),
code: Some("Emissing-semicolon".to_string()),
fixits: vec![FixItSuggestion::new(
"Add semicolon",
";",
line_num,
trimmed.len(),
line_num,
trimmed.len(),
)],
notes: Vec::new(),
source_text: Some(trimmed.to_string()),
});
}
}
if trimmed.contains("unused") {
diagnostics.push(CompilerDiagnostic {
severity: DiagnosticSeverity::Warning,
message: "Unused variable detected".to_string(),
file: uri.to_string(),
line: line_num,
column: 1,
end_line: Some(line_num),
end_column: Some(line.len()),
category: "semantic".to_string(),
code: Some("Wunused-variable".to_string()),
fixits: Vec::new(),
notes: Vec::new(),
source_text: Some(trimmed.to_string()),
});
}
if line.contains("shadow") {
diagnostics.push(CompilerDiagnostic {
severity: DiagnosticSeverity::Warning,
message: "Declaration shadows a previous declaration".to_string(),
file: uri.to_string(),
line: line_num,
column: 1,
end_line: Some(line_num),
end_column: Some(line.len()),
category: "semantic".to_string(),
code: Some("Wshadow".to_string()),
fixits: Vec::new(),
notes: vec![DiagnosticNote::new(
"Previous declaration was here",
uri,
line_num.saturating_sub(1).max(1),
1,
)],
source_text: Some(trimmed.to_string()),
});
}
}
diagnostics
}
}
fn hash_source(source: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
source.hash(&mut hasher);
hasher.finish()
}
pub struct X86BuildService {
enabled: bool,
default_jobs: usize,
distributed_enabled: bool,
distcc_scheduler: Option<String>,
local_cache: Arc<Mutex<BuildCache>>,
dependency_tracker: Arc<Mutex<DependencyTracker>>,
task_queue: Arc<Mutex<VecDeque<BuildTask>>>,
tasks_completed: AtomicU64,
progress_listeners: Arc<Mutex<Vec<Box<dyn BuildProgressListener>>>>,
build_status: Arc<RwLock<BuildStatus>>,
}
#[derive(Debug, Clone)]
pub struct BuildCache {
entries: HashMap<String, BuildCacheEntry>,
max_entries: usize,
hits: u64,
misses: u64,
}
#[derive(Debug, Clone)]
pub struct BuildCacheEntry {
source_hash: u64,
options_hash: u64,
output: Vec<u8>,
dependencies: Vec<String>,
timestamp: Instant,
size_bytes: usize,
}
#[derive(Debug, Clone)]
pub struct DependencyTracker {
dependencies: HashMap<String, Vec<String>>,
dependents: HashMap<String, Vec<String>>,
timestamps: HashMap<String, SystemTime>,
}
#[derive(Debug, Clone)]
pub struct BuildTask {
pub id: String,
pub file: String,
pub options: CompileOptions,
pub incremental: bool,
pub priority: u32,
pub dispatched: bool,
pub completed: bool,
pub result: Option<CompilationResult>,
pub created_at: Instant,
pub estimated_duration_ms: u64,
}
#[derive(Debug, Clone)]
pub struct BuildStatus {
pub building: bool,
pub phase: BuildPhase,
pub queued: usize,
pub running: usize,
pub completed: usize,
pub failed: usize,
pub progress: u32,
pub started_at: Option<Instant>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuildPhase {
Idle,
Resolving,
Compiling,
Linking,
Packaging,
Done,
}
pub trait BuildProgressListener: Send + Sync {
fn on_progress(&self, status: &BuildStatus);
fn on_task_complete(&self, task: &BuildTask);
fn on_task_fail(&self, task: &BuildTask, error: &str);
}
impl X86BuildService {
pub fn new(config: &CompilerServiceConfig) -> Self {
let max_cache = config.daemon_cache_size.max(64);
Self {
enabled: config.build_service_enabled,
default_jobs: config.default_build_jobs,
distributed_enabled: config.distributed_compilation,
distcc_scheduler: config.distcc_scheduler.clone(),
local_cache: Arc::new(Mutex::new(BuildCache {
entries: HashMap::new(),
max_entries: max_cache,
hits: 0,
misses: 0,
})),
dependency_tracker: Arc::new(Mutex::new(DependencyTracker {
dependencies: HashMap::new(),
dependents: HashMap::new(),
timestamps: HashMap::new(),
})),
task_queue: Arc::new(Mutex::new(VecDeque::new())),
tasks_completed: AtomicU64::new(0),
progress_listeners: Arc::new(Mutex::new(Vec::new())),
build_status: Arc::new(RwLock::new(BuildStatus {
building: false,
phase: BuildPhase::Idle,
queued: 0,
running: 0,
completed: 0,
failed: 0,
progress: 0,
started_at: None,
})),
}
}
pub fn tasks_completed(&self) -> u64 {
self.tasks_completed.load(Ordering::SeqCst)
}
pub fn add_progress_listener(&mut self, listener: Box<dyn BuildProgressListener>) {
self.progress_listeners.lock().unwrap().push(listener);
}
pub fn get_status(&self) -> BuildStatus {
self.build_status.read().unwrap().clone()
}
pub fn needs_rebuild(&self, file: &str, options: &CompileOptions) -> bool {
let deps = self.dependency_tracker.lock().unwrap();
let source_hash = 0;
let cache = self.local_cache.lock().unwrap();
let cache_key = format!("{}:{}", file, hash_options(options));
if let Some(entry) = cache.entries.get(&cache_key) {
for dep in &entry.dependencies {
if let Some(&ts) = deps.timestamps.get(dep) {
let _ = ts;
}
}
return false; }
true }
pub fn queue_incremental(&self, file: &str, options: &CompileOptions) -> String {
let task_id = format!("build-{}", rand_id());
let task = BuildTask {
id: task_id.clone(),
file: file.to_string(),
options: options.clone(),
incremental: true,
priority: 0,
dispatched: false,
completed: false,
result: None,
created_at: Instant::now(),
estimated_duration_ms: 0,
};
let mut queue = self.task_queue.lock().unwrap();
queue.push_back(task);
{
let mut status = self.build_status.write().unwrap();
status.queued = queue.len();
}
task_id
}
pub fn register_dependencies(&self, file: &str, deps: Vec<String>) {
let mut tracker = self.dependency_tracker.lock().unwrap();
for dep in &deps {
tracker
.dependents
.entry(dep.clone())
.or_default()
.push(file.to_string());
}
tracker.dependencies.insert(file.to_string(), deps);
tracker
.timestamps
.insert(file.to_string(), SystemTime::now());
}
pub fn get_dependents(&self, file: &str) -> Vec<String> {
let tracker = self.dependency_tracker.lock().unwrap();
tracker.dependents.get(file).cloned().unwrap_or_default()
}
pub fn get_rebuild_impact(&self, file: &str) -> Vec<String> {
let tracker = self.dependency_tracker.lock().unwrap();
let mut impact = Vec::new();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(file.to_string());
while let Some(current) = queue.pop_front() {
if !visited.insert(current.clone()) {
continue;
}
if ¤t != file {
impact.push(current.clone());
}
if let Some(dependents) = tracker.dependents.get(¤t) {
for dep in dependents {
queue.push_back(dep.clone());
}
}
}
impact
}
pub fn cache_lookup(&self, file: &str, options: &CompileOptions) -> Option<BuildCacheEntry> {
let mut cache = self.local_cache.lock().unwrap();
let key = format!("{}:{}", file, hash_options(options));
let entry = cache.entries.get(&key).cloned();
if entry.is_some() {
cache.hits += 1;
} else {
cache.misses += 1;
}
entry
}
pub fn cache_store(
&self,
file: &str,
options: &CompileOptions,
output: Vec<u8>,
dependencies: Vec<String>,
) {
let mut cache = self.local_cache.lock().unwrap();
let key = format!("{}:{}", file, hash_options(options));
if cache.entries.len() >= cache.max_entries {
if let Some(oldest_key) = cache
.entries
.iter()
.min_by_key(|(_, v)| v.timestamp)
.map(|(k, _)| k.clone())
{
cache.entries.remove(&oldest_key);
}
}
let size = output.len();
cache.entries.insert(
key,
BuildCacheEntry {
source_hash: hash_source(file),
options_hash: hash_options(options),
output,
dependencies,
timestamp: Instant::now(),
size_bytes: size,
},
);
}
pub fn cache_stats(&self) -> CacheStats {
let cache = self.local_cache.lock().unwrap();
let total_size: usize = cache.entries.values().map(|e| e.size_bytes).sum();
CacheStats {
entries: cache.entries.len(),
max_entries: cache.max_entries,
hits: cache.hits,
misses: cache.misses,
total_size_bytes: total_size,
hit_rate: if cache.hits + cache.misses > 0 {
cache.hits as f64 / (cache.hits + cache.misses) as f64
} else {
0.0
},
}
}
pub fn clear_cache(&self) {
let mut cache = self.local_cache.lock().unwrap();
cache.entries.clear();
cache.hits = 0;
cache.misses = 0;
}
pub fn is_distributed_available(&self) -> bool {
self.distributed_enabled && self.distcc_scheduler.is_some()
}
pub fn submit_distributed(
&self,
file: &str,
options: &CompileOptions,
) -> Result<String, CompilerServiceError> {
if !self.is_distributed_available() {
return Err(CompilerServiceError::BuildError(
"Distributed compilation not enabled".to_string(),
));
}
let task_id = format!("dist-{}", rand_id());
Ok(task_id)
}
pub fn distributed_status(&self) -> DistributedBuildStatus {
DistributedBuildStatus {
enabled: self.distributed_enabled,
scheduler: self.distcc_scheduler.clone(),
connected: false, active_nodes: 0,
total_nodes: 0,
queued_jobs: 0,
}
}
pub fn start_build(&self, total_tasks: usize) {
let mut status = self.build_status.write().unwrap();
status.building = true;
status.phase = BuildPhase::Compiling;
status.queued = total_tasks;
status.running = 0;
status.completed = 0;
status.failed = 0;
status.progress = 0;
status.started_at = Some(Instant::now());
}
pub fn update_progress(&self, completed: usize, failed: usize, total: usize) {
let mut status = self.build_status.write().unwrap();
status.completed = completed;
status.failed = failed;
status.queued = total;
if total > 0 {
status.progress = ((completed + failed) * 100 / total) as u32;
}
if completed + failed >= total {
status.phase = if status.phase == BuildPhase::Compiling {
BuildPhase::Linking
} else {
BuildPhase::Done
};
status.building = status.phase != BuildPhase::Done;
}
let current = status.clone();
let listeners = self.progress_listeners.lock().unwrap();
for listener in listeners.iter() {
listener.on_progress(¤t);
}
}
pub fn complete_task(&self, task_id: &str, result: CompilationResult) {
self.tasks_completed.fetch_add(1, Ordering::SeqCst);
let mut queue = self.task_queue.lock().unwrap();
if let Some(task) = queue.iter_mut().find(|t| t.id == task_id) {
task.completed = true;
task.result = Some(result.clone());
let listeners = self.progress_listeners.lock().unwrap();
if result.success {
for listener in listeners.iter() {
listener.on_task_complete(task);
}
} else {
let error = result
.diagnostics
.first()
.map(|d| d.message.clone())
.unwrap_or_else(|| "Unknown error".to_string());
for listener in listeners.iter() {
listener.on_task_fail(task, &error);
}
}
}
}
pub fn finish_build(&self) {
let mut status = self.build_status.write().unwrap();
status.building = false;
status.phase = BuildPhase::Done;
status.progress = 100;
}
}
#[derive(Debug, Clone)]
pub struct CacheStats {
pub entries: usize,
pub max_entries: usize,
pub hits: u64,
pub misses: u64,
pub total_size_bytes: usize,
pub hit_rate: f64,
}
#[derive(Debug, Clone)]
pub struct DistributedBuildStatus {
pub enabled: bool,
pub scheduler: Option<String>,
pub connected: bool,
pub active_nodes: usize,
pub total_nodes: usize,
pub queued_jobs: usize,
}
fn hash_options(options: &CompileOptions) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
options.optimization.hash(&mut hasher);
options.target.hash(&mut hasher);
options.c_standard.hash(&mut hasher);
options.debug_info.hash(&mut hasher);
options.lto.hash(&mut hasher);
options.pic.hash(&mut hasher);
for inc in &options.includes {
inc.hash(&mut hasher);
}
hasher.finish()
}
pub struct X86DaemonMode {
pch_cache: Arc<Mutex<LruCache<String, PrecompiledHeaderEntry>>>,
module_cache: Arc<Mutex<LruCache<String, ModuleCacheEntry>>>,
compilation_cache: Arc<Mutex<LruCache<String, CompilationCacheEntry>>>,
running: AtomicBool,
healthy: AtomicBool,
started_at: Option<Instant>,
config: DaemonConfig,
memory_usage_bytes: AtomicU64,
last_activity: AtomicU64,
stats: Arc<RwLock<DaemonStats>>,
}
struct LruCache<K, V> {
map: HashMap<K, (V, u64)>,
access_order: VecDeque<K>,
max_size: usize,
access_counter: u64,
}
impl<K: Clone + Eq + std::hash::Hash, V> LruCache<K, V> {
fn new(max_size: usize) -> Self {
Self {
map: HashMap::new(),
access_order: VecDeque::new(),
max_size,
access_counter: 0,
}
}
fn get(&mut self, key: &K) -> Option<&V> {
if let Some((val, counter)) = self.map.get_mut(key) {
self.access_counter += 1;
*counter = self.access_counter;
Some(&*val)
} else {
None
}
}
fn insert(&mut self, key: K, value: V) {
if self.map.len() >= self.max_size {
self.evict_lru();
}
self.access_counter += 1;
self.map.insert(key.clone(), (value, self.access_counter));
self.access_order.push_back(key);
}
fn remove(&mut self, key: &K) -> Option<V> {
self.map.remove(key).map(|(v, _)| v)
}
fn len(&self) -> usize {
self.map.len()
}
fn is_empty(&self) -> bool {
self.map.is_empty()
}
fn evict_lru(&mut self) {
let mut lru_key = None;
let mut min_counter = u64::MAX;
for (k, (_, counter)) in &self.map {
if *counter < min_counter {
min_counter = *counter;
lru_key = Some(k.clone());
}
}
if let Some(key) = lru_key {
self.map.remove(&key);
}
while self.access_order.len() > self.max_size {
self.access_order.pop_front();
}
}
fn clear(&mut self) {
self.map.clear();
self.access_order.clear();
}
}
#[derive(Debug, Clone)]
struct PrecompiledHeaderEntry {
header: String,
data: Vec<u8>,
modified_at: u64,
size_bytes: usize,
}
#[derive(Debug, Clone)]
struct ModuleCacheEntry {
name: String,
data: Vec<u8>,
dependencies: Vec<String>,
size_bytes: usize,
}
#[derive(Debug, Clone)]
struct CompilationCacheEntry {
source_hash: u64,
options_hash: u64,
result: CompilationResult,
size_bytes: usize,
}
#[derive(Debug, Clone)]
pub struct DaemonConfig {
pub pch_cache_size: usize,
pub module_cache_size: usize,
pub compilation_cache_size: usize,
pub idle_timeout_secs: u64,
pub health_check_interval_secs: u64,
pub max_memory_mb: f64,
}
impl Default for DaemonConfig {
fn default() -> Self {
Self {
pch_cache_size: 32,
module_cache_size: 64,
compilation_cache_size: DEFAULT_DAEMON_CACHE_SIZE,
idle_timeout_secs: DAEMON_IDLE_TIMEOUT_SECS,
health_check_interval_secs: DAEMON_HEALTH_CHECK_INTERVAL_SECS,
max_memory_mb: 4096.0,
}
}
}
#[derive(Debug, Clone)]
pub struct DaemonStats {
pub pch_cache_entries: usize,
pub module_cache_entries: usize,
pub compilation_cache_entries: usize,
pub total_hits: u64,
pub total_misses: u64,
pub total_compilations: u64,
pub uptime_secs: u64,
pub memory_usage_bytes: u64,
pub health_checks: u64,
}
impl X86DaemonMode {
pub fn new(config: &CompilerServiceConfig) -> Self {
let daemon_config = DaemonConfig {
compilation_cache_size: config.daemon_cache_size,
..Default::default()
};
Self {
pch_cache: Arc::new(Mutex::new(LruCache::new(daemon_config.pch_cache_size))),
module_cache: Arc::new(Mutex::new(LruCache::new(daemon_config.module_cache_size))),
compilation_cache: Arc::new(Mutex::new(LruCache::new(
daemon_config.compilation_cache_size,
))),
running: AtomicBool::new(false),
healthy: AtomicBool::new(false),
started_at: None,
config: daemon_config,
memory_usage_bytes: AtomicU64::new(0),
last_activity: AtomicU64::new(0),
stats: Arc::new(RwLock::new(DaemonStats {
pch_cache_entries: 0,
module_cache_entries: 0,
compilation_cache_entries: 0,
total_hits: 0,
total_misses: 0,
total_compilations: 0,
uptime_secs: 0,
memory_usage_bytes: 0,
health_checks: 0,
})),
}
}
pub fn start(&mut self) -> Result<(), CompilerServiceError> {
if self.running.load(Ordering::SeqCst) {
return Err(CompilerServiceError::DaemonError(
"Daemon is already running".to_string(),
));
}
self.running.store(true, Ordering::SeqCst);
self.healthy.store(true, Ordering::SeqCst);
self.started_at = Some(Instant::now());
Ok(())
}
pub fn shutdown(&mut self, flush_caches: bool) -> Result<(), CompilerServiceError> {
if !self.running.load(Ordering::SeqCst) {
return Err(CompilerServiceError::DaemonError(
"Daemon is not running".to_string(),
));
}
if flush_caches {
self.clear_all_caches();
}
self.running.store(false, Ordering::SeqCst);
self.healthy.store(false, Ordering::SeqCst);
Ok(())
}
pub fn is_healthy(&self) -> bool {
self.healthy.load(Ordering::SeqCst)
}
pub fn cache_size(&self) -> usize {
let pch = self.pch_cache.lock().unwrap();
let module = self.module_cache.lock().unwrap();
let comp = self.compilation_cache.lock().unwrap();
pch.len() + module.len() + comp.len()
}
pub fn memory_usage_mb(&self) -> f64 {
let mem = self.memory_usage_bytes.load(Ordering::SeqCst);
mem as f64 / (1024.0 * 1024.0)
}
pub fn get_stats(&self) -> DaemonStats {
let stats = self.stats.read().unwrap();
let mut result = stats.clone();
result.pch_cache_entries = self.pch_cache.lock().unwrap().len();
result.module_cache_entries = self.module_cache.lock().unwrap().len();
result.compilation_cache_entries = self.compilation_cache.lock().unwrap().len();
result.memory_usage_bytes = self.memory_usage_bytes.load(Ordering::SeqCst);
if let Some(start) = self.started_at {
result.uptime_secs = start.elapsed().as_secs();
}
result
}
pub fn lookup_pch(&self, header: &str) -> Option<Vec<u8>> {
let mut cache = self.pch_cache.lock().unwrap();
cache.get(&header.to_string()).map(|e| e.data.clone())
}
pub fn cache_pch(&self, header: &str, data: Vec<u8>, modified_at: u64) {
let size = data.len();
let mut cache = self.pch_cache.lock().unwrap();
cache.insert(
header.to_string(),
PrecompiledHeaderEntry {
header: header.to_string(),
data,
modified_at,
size_bytes: size,
},
);
{
let mut stats = self.stats.write().unwrap();
stats.pch_cache_entries = cache.len();
stats.total_hits += 1;
}
self.update_memory_usage();
}
pub fn invalidate_pch(&self, header: &str) {
let mut cache = self.pch_cache.lock().unwrap();
cache.remove(&header.to_string());
self.update_memory_usage();
}
pub fn lookup_module(&self, name: &str) -> Option<Vec<u8>> {
let mut cache = self.module_cache.lock().unwrap();
cache.get(&name.to_string()).map(|e| e.data.clone())
}
pub fn cache_module(&self, name: &str, data: Vec<u8>, dependencies: Vec<String>) {
let size = data.len();
let mut cache = self.module_cache.lock().unwrap();
cache.insert(
name.to_string(),
ModuleCacheEntry {
name: name.to_string(),
data,
dependencies,
size_bytes: size,
},
);
{
let mut stats = self.stats.write().unwrap();
stats.module_cache_entries = cache.len();
stats.total_hits += 1;
}
self.update_memory_usage();
}
pub fn invalidate_module(&self, name: &str) {
let mut cache = self.module_cache.lock().unwrap();
cache.remove(&name.to_string());
self.update_memory_usage();
}
pub fn lookup_compilation(
&self,
file: &str,
options: &CompileOptions,
) -> Option<CompilationResult> {
let key = format!("{}:{}", file, hash_options(options));
let mut cache = self.compilation_cache.lock().unwrap();
let entry = cache.get(&key).cloned();
drop(cache);
entry.map(|e| {
let result = e.result.clone();
self.record_activity();
result
})
}
pub fn cache_compilation(
&self,
file: &str,
options: &CompileOptions,
result: CompilationResult,
) {
let source_hash = hash_source(file);
let options_hash = hash_options(options);
let size = result.assembly.as_ref().map(|a| a.len()).unwrap_or(0);
let key = format!("{}:{}", file, options_hash);
let mut cache = self.compilation_cache.lock().unwrap();
cache.insert(
key,
CompilationCacheEntry {
source_hash,
options_hash,
result,
size_bytes: size,
},
);
{
let mut stats = self.stats.write().unwrap();
stats.compilation_cache_entries = cache.len();
stats.total_compilations += 1;
}
self.record_activity();
self.update_memory_usage();
}
pub fn maybe_evict(&self) {
let usage_mb = self.memory_usage_mb();
if usage_mb > self.config.max_memory_mb {
{
let mut cache = self.compilation_cache.lock().unwrap();
while cache.len() > 0 && self.memory_usage_mb() > self.config.max_memory_mb * 0.9 {
cache.evict_lru();
}
}
if self.memory_usage_mb() > self.config.max_memory_mb * 0.9 {
let mut cache = self.module_cache.lock().unwrap();
while cache.len() > 0 && self.memory_usage_mb() > self.config.max_memory_mb * 0.9 {
cache.evict_lru();
}
}
if self.memory_usage_mb() > self.config.max_memory_mb * 0.9 {
let mut cache = self.pch_cache.lock().unwrap();
while cache.len() > 0 && self.memory_usage_mb() > self.config.max_memory_mb * 0.9 {
cache.evict_lru();
}
}
self.update_memory_usage();
}
}
fn update_memory_usage(&self) {
let pch = self.pch_cache.lock().unwrap();
let modules = self.module_cache.lock().unwrap();
let comp = self.compilation_cache.lock().unwrap();
let mut total: u64 = 0;
for (_, (entry, _)) in &pch.map {
total += entry.size_bytes as u64;
}
for (_, (entry, _)) in &modules.map {
total += entry.size_bytes as u64;
}
for (_, (entry, _)) in &comp.map {
total += entry.size_bytes as u64;
}
self.memory_usage_bytes.store(total, Ordering::SeqCst);
}
pub fn health_check(&self) -> DaemonHealth {
let healthy = self.healthy.load(Ordering::SeqCst);
let running = self.running.load(Ordering::SeqCst);
let memory_mb = self.memory_usage_mb();
let cache_size = self.cache_size();
{
let mut stats = self.stats.write().unwrap();
stats.health_checks += 1;
}
DaemonHealth {
running,
healthy: healthy && running,
memory_usage_mb: memory_mb,
memory_limit_mb: self.config.max_memory_mb,
cache_entries: cache_size,
pch_cache_entries: self.pch_cache.lock().unwrap().len(),
module_cache_entries: self.module_cache.lock().unwrap().len(),
compilation_cache_entries: self.compilation_cache.lock().unwrap().len(),
idle_secs: self.idle_seconds(),
}
}
fn record_activity(&self) {
self.last_activity.store(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
Ordering::SeqCst,
);
}
fn idle_seconds(&self) -> u64 {
let last = self.last_activity.load(Ordering::SeqCst);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
if last == 0 {
0
} else {
now.saturating_sub(last)
}
}
pub fn should_auto_shutdown(&self) -> bool {
self.idle_seconds() > self.config.idle_timeout_secs
}
fn clear_all_caches(&self) {
self.pch_cache.lock().unwrap().clear();
self.module_cache.lock().unwrap().clear();
self.compilation_cache.lock().unwrap().clear();
self.memory_usage_bytes.store(0, Ordering::SeqCst);
}
}
#[derive(Debug, Clone)]
pub struct DaemonHealth {
pub running: bool,
pub healthy: bool,
pub memory_usage_mb: f64,
pub memory_limit_mb: f64,
pub cache_entries: usize,
pub pch_cache_entries: usize,
pub module_cache_entries: usize,
pub compilation_cache_entries: usize,
pub idle_secs: u64,
}
pub struct X86CompilerAPI {
default_options: CompileOptions,
default_format_options: FormattingOptions,
server_address: String,
initialized: bool,
}
impl X86CompilerAPI {
pub fn new(config: &CompilerServiceConfig) -> Self {
Self {
default_options: CompileOptions {
target: config.default_target.clone(),
..Default::default()
},
default_format_options: FormattingOptions::default(),
server_address: config.grpc_bind_address.clone(),
initialized: true,
}
}
pub fn is_initialized(&self) -> bool {
self.initialized
}
pub fn compile(&self, source: &str, options: Option<&CompileOptions>) -> CompilationResult {
let opts = options.unwrap_or(&self.default_options);
let start = Instant::now();
let mut result = CompilationResult::new("api-compile.c");
result.assembly = Some(format!(
"; API Compilation\n; Source: {} bytes\n; Target: {}\n; Optimization: {}",
source.len(),
opts.target,
opts.optimization
));
result.compile_time_ms = start.elapsed().as_millis() as u64;
result.success = true;
result
}
pub fn compile_file(
&self,
file_path: &str,
options: Option<&CompileOptions>,
) -> CompilationResult {
let source = std::fs::read_to_string(file_path).unwrap_or_default();
let mut result = self.compile(&source, options);
result.input_file = file_path.to_string();
result.output_file = Some(
file_path
.replace(".c", ".o")
.replace(".cpp", ".o")
.replace(".cxx", ".o"),
);
result
}
pub fn compile_to_assembly(&self, source: &str, options: Option<&CompileOptions>) -> String {
let result = self.compile(source, options);
result.assembly.unwrap_or_default()
}
pub fn preprocess(&self, source: &str, options: Option<&CompileOptions>) -> PreprocessedSource {
let opts = options.unwrap_or(&self.default_options);
let macros: Vec<(String, Option<String>)> = opts
.defines
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
PreprocessedSource {
text: source.to_string(),
input_file: "api-preprocess.c".to_string(),
included_files: opts.includes.clone(),
line_markers: Vec::new(),
macros,
undef_macros: Vec::new(),
diagnostics: Vec::new(),
}
}
pub fn preprocess_file(
&self,
file_path: &str,
options: Option<&CompileOptions>,
) -> PreprocessedSource {
let source = std::fs::read_to_string(file_path).unwrap_or_default();
let mut result = self.preprocess(&source, options);
result.input_file = file_path.to_string();
result
}
pub fn parse(&self, source: &str, _options: Option<&CompileOptions>) -> AstNode {
let children = self.extract_top_level_decls(source);
let end_line = source.lines().count().max(1);
let end_col = source.lines().last().map(|l| l.len()).unwrap_or(1);
AstNode {
kind: AstNodeKind::TranslationUnit,
location: SourcePosition::start(),
end_location: SourcePosition::new(end_line, end_col),
children,
text: Some(source.to_string()),
type_annotation: None,
name: Some("translation_unit".to_string()),
symbol_ref: None,
}
}
fn extract_top_level_decls(&self, source: &str) -> Vec<AstNode> {
let mut nodes = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
let line_num = line_num + 1;
if trimmed.starts_with("int ") || trimmed.starts_with("void ") {
if let Some(name) = trimmed
.split_whitespace()
.nth(1)
.and_then(|s| s.split('(').next())
{
nodes.push(AstNode {
kind: if trimmed.contains('{') {
AstNodeKind::FunctionDef
} else {
AstNodeKind::FunctionDecl
},
location: SourcePosition::new(line_num, 1),
end_location: SourcePosition::new(line_num, line.len()),
children: Vec::new(),
text: Some(trimmed.to_string()),
type_annotation: Some("int".to_string()),
name: Some(name.to_string()),
symbol_ref: None,
});
}
} else if trimmed.starts_with("struct ") {
if let Some(name) = trimmed.split_whitespace().nth(1) {
let clean = name.trim_end_matches('{').trim_end_matches(';').trim();
nodes.push(AstNode {
kind: AstNodeKind::StructDecl,
location: SourcePosition::new(line_num, 1),
end_location: SourcePosition::new(line_num, line.len()),
children: Vec::new(),
text: Some(trimmed.to_string()),
type_annotation: Some("struct".to_string()),
name: Some(clean.to_string()),
symbol_ref: None,
});
}
} else if trimmed.starts_with("enum ") {
if let Some(name) = trimmed.split_whitespace().nth(1) {
let clean = name.trim_end_matches('{').trim_end_matches(';').trim();
nodes.push(AstNode {
kind: AstNodeKind::EnumDecl,
location: SourcePosition::new(line_num, 1),
end_location: SourcePosition::new(line_num, line.len()),
children: Vec::new(),
text: Some(trimmed.to_string()),
type_annotation: Some("enum".to_string()),
name: Some(clean.to_string()),
symbol_ref: None,
});
}
} else if trimmed.starts_with("#define ") {
if let Some(name) = trimmed[8..].split(|c: char| c == '(' || c == ' ').next() {
nodes.push(AstNode {
kind: AstNodeKind::Identifier,
location: SourcePosition::new(line_num, 1),
end_location: SourcePosition::new(line_num, name.len()),
children: Vec::new(),
text: Some(trimmed.to_string()),
type_annotation: Some("macro".to_string()),
name: Some(name.to_string()),
symbol_ref: None,
});
}
}
}
nodes
}
pub fn parse_file(&self, file_path: &str, options: Option<&CompileOptions>) -> AstNode {
let source = std::fs::read_to_string(file_path).unwrap_or_default();
self.parse(&source, options)
}
pub fn complete(
&self,
source: &str,
position: SourcePosition,
_options: Option<&CompileOptions>,
) -> Vec<CompletionItem> {
let prefix = self.extract_word_at(source, position);
let prefix_lower = prefix.to_lowercase();
let keywords: Vec<(&str, &str, &str)> = vec![
("int", "int ", "Integer type"),
("char", "char ", "Character type"),
("float", "float ", "Single-precision float"),
("double", "double ", "Double-precision float"),
("void", "void ", "Void type"),
("return", "return ", "Return statement"),
("if", "if", "If statement"),
("for", "for", "For loop"),
("while", "while", "While loop"),
("struct", "struct ", "Structure type"),
("enum", "enum ", "Enumeration type"),
("const", "const ", "Const qualifier"),
("static", "static ", "Static storage class"),
("extern", "extern ", "External linkage"),
("sizeof", "sizeof", "Size-of operator"),
("typedef", "typedef ", "Type definition"),
("unsigned", "unsigned ", "Unsigned qualifier"),
("long", "long ", "Long type"),
("short", "short ", "Short type"),
("break", "break", "Break statement"),
("continue", "continue", "Continue statement"),
("switch", "switch", "Switch statement"),
("case", "case ", "Case label"),
("default", "default:", "Default label"),
("goto", "goto ", "Go-to statement"),
("union", "union ", "Union type"),
("volatile", "volatile ", "Volatile qualifier"),
("register", "register ", "Register storage"),
("auto", "auto ", "Automatic storage"),
("signed", "signed ", "Signed qualifier"),
];
keywords
.into_iter()
.enumerate()
.filter(|(_, (label, _, _))| {
prefix.is_empty() || label.to_lowercase().starts_with(&prefix_lower)
})
.map(|(i, (label, insert, detail))| CompletionItem {
label: label.to_string(),
insert_text: insert.to_string(),
detail: Some(detail.to_string()),
documentation: None,
kind: CompletionItemKind::Keyword,
sort_priority: i as u32,
deprecated: false,
filter_text: Some(label.to_string()),
is_snippet: false,
additional_edits: Vec::new(),
command: None,
data: None,
})
.collect()
}
pub fn format(&self, source: &str, options: Option<&FormattingOptions>) -> FormattedSource {
let opts = options.unwrap_or(&self.default_format_options);
let lines: Vec<String> = source
.lines()
.map(|l| {
if opts.trim_trailing_whitespace {
l.trim_end().to_string()
} else {
l.to_string()
}
})
.collect();
let mut result = String::new();
let mut blanks = 0usize;
for line in &lines {
if line.trim().is_empty() {
blanks += 1;
if blanks <= opts.max_empty_lines {
result.push('\n');
}
} else {
blanks = 0;
result.push_str(line);
result.push('\n');
}
}
if opts.insert_final_newline && !result.ends_with('\n') {
result.push('\n');
}
let was_changed = result != source;
FormattedSource::new(
"api-format.c",
&result,
was_changed,
if was_changed { 1 } else { 0 },
)
}
pub fn format_file(
&self,
file_path: &str,
options: Option<&FormattingOptions>,
) -> FormattedSource {
let source = std::fs::read_to_string(file_path).unwrap_or_default();
let mut result = self.format(&source, options);
result.file = file_path.to_string();
result
}
pub fn index(&self, project_path: &str) -> IndexDatabase {
IndexDatabase::new(project_path)
}
fn extract_word_at(&self, source: &str, position: SourcePosition) -> String {
let mut prefix = String::new();
if let Some(line) = source.lines().nth(position.line.saturating_sub(1)) {
let col = position.column.saturating_sub(1).min(line.len());
let before = &line[..col];
for ch in before.chars().rev() {
if ch.is_alphanumeric() || ch == '_' {
prefix.insert(0, ch);
} else {
break;
}
}
}
prefix
}
}
#[derive(Debug, Clone)]
pub struct IndexDatabase {
pub project_path: String,
pub files: Vec<String>,
pub symbols: Vec<SymbolEntry>,
pub includes: HashMap<String, Vec<String>>,
pub call_graph: Vec<(String, String)>,
}
impl IndexDatabase {
pub fn new(project_path: &str) -> Self {
Self {
project_path: project_path.to_string(),
files: Vec::new(),
symbols: Vec::new(),
includes: HashMap::new(),
call_graph: Vec::new(),
}
}
pub fn file_count(&self) -> usize {
self.files.len()
}
pub fn symbol_count(&self) -> usize {
self.symbols.len()
}
pub fn query(&self, name: &str) -> Vec<&SymbolEntry> {
let lower = name.to_lowercase();
self.symbols
.iter()
.filter(|s| s.name.to_lowercase().contains(&lower))
.collect()
}
pub fn include_stats(&self) -> IncludeStats {
let mut max_depth = 0u32;
let total_edges: usize = self.includes.values().map(|v| v.len()).sum();
IncludeStats {
total_edges,
total_files: self.files.len(),
max_chain_depth: max_depth,
}
}
}
#[derive(Debug, Clone)]
pub struct IncludeStats {
pub total_edges: usize,
pub total_files: usize,
pub max_chain_depth: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compilation_result_new() {
let r = CompilationResult::new("test.c");
assert!(!r.success);
assert_eq!(r.input_file, "test.c");
assert_eq!(r.warning_count, 0);
assert_eq!(r.error_count, 0);
}
#[test]
fn test_compilation_result_has_errors() {
let mut r = CompilationResult::new("test.c");
assert!(!r.has_errors());
r.error_count = 1;
assert!(r.has_errors());
}
#[test]
fn test_compilation_result_has_warnings() {
let mut r = CompilationResult::new("test.c");
assert!(!r.has_warnings());
r.warning_count = 3;
assert!(r.has_warnings());
}
#[test]
fn test_compilation_result_summary_success() {
let mut r = CompilationResult::new("test.c");
r.success = true;
r.compile_time_ms = 42;
let s = r.summary();
assert!(s.contains("test.c"));
assert!(s.contains("42ms"));
}
#[test]
fn test_compilation_result_summary_failure() {
let mut r = CompilationResult::new("test.c");
r.success = false;
r.error_count = 2;
r.warning_count = 5;
let s = r.summary();
assert!(s.contains("2 errors"));
assert!(s.contains("5 warnings"));
}
#[test]
fn test_diagnostic_severity_ordering() {
assert!(DiagnosticSeverity::Error > DiagnosticSeverity::Warning);
assert!(DiagnosticSeverity::Warning > DiagnosticSeverity::Note);
assert!(DiagnosticSeverity::Note > DiagnosticSeverity::Ignored);
assert!(DiagnosticSeverity::Fatal > DiagnosticSeverity::Error);
}
#[test]
fn test_diagnostic_severity_is_warning_or_worse() {
assert!(!DiagnosticSeverity::Ignored.is_warning_or_worse());
assert!(!DiagnosticSeverity::Note.is_warning_or_worse());
assert!(DiagnosticSeverity::Warning.is_warning_or_worse());
assert!(DiagnosticSeverity::Error.is_warning_or_worse());
assert!(DiagnosticSeverity::Fatal.is_warning_or_worse());
}
#[test]
fn test_diagnostic_severity_is_error_or_worse() {
assert!(!DiagnosticSeverity::Warning.is_error_or_worse());
assert!(DiagnosticSeverity::Error.is_error_or_worse());
assert!(DiagnosticSeverity::Fatal.is_error_or_worse());
}
#[test]
fn test_diagnostic_severity_display() {
assert_eq!(format!("{}", DiagnosticSeverity::Error), "error");
assert_eq!(format!("{}", DiagnosticSeverity::Warning), "warning");
assert_eq!(format!("{}", DiagnosticSeverity::Note), "note");
assert_eq!(format!("{}", DiagnosticSeverity::Fatal), "fatal error");
}
#[test]
fn test_fixit_new() {
let fix = FixItSuggestion::new("Add semicolon", ";", 10, 20, 10, 20);
assert_eq!(fix.description, "Add semicolon");
assert_eq!(fix.replacement, ";");
assert_eq!(fix.start_line, 10);
assert_eq!(fix.start_column, 20);
}
#[test]
fn test_diagnostic_note_new() {
let note = DiagnosticNote::new("Previous declaration", "file.c", 5, 10);
assert_eq!(note.message, "Previous declaration");
assert_eq!(note.file, "file.c");
assert_eq!(note.line, 5);
assert_eq!(note.column, 10);
}
#[test]
fn test_source_position_new() {
let pos = SourcePosition::new(5, 10);
assert_eq!(pos.line, 5);
assert_eq!(pos.column, 10);
}
#[test]
fn test_source_position_start() {
let pos = SourcePosition::start();
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 1);
}
#[test]
fn test_source_position_advance_same_line() {
let pos = SourcePosition::new(1, 1);
let advanced = pos.advance("hello world", 5);
assert_eq!(advanced.line, 1);
assert_eq!(advanced.column, 6);
}
#[test]
fn test_source_position_advance_newline() {
let pos = SourcePosition::new(1, 1);
let advanced = pos.advance("hello\nworld", 7);
assert_eq!(advanced.line, 2);
}
#[test]
fn test_completion_item_kind_as_lsp_kind() {
assert_eq!(CompletionItemKind::Text.as_lsp_kind(), 1);
assert_eq!(CompletionItemKind::Function.as_lsp_kind(), 3);
assert_eq!(CompletionItemKind::Keyword.as_lsp_kind(), 14);
}
#[test]
fn test_completion_item_kind_icon() {
assert_eq!(CompletionItemKind::Function.icon(), "f");
assert_eq!(CompletionItemKind::Keyword.icon(), "k");
assert_eq!(CompletionItemKind::Variable.icon(), "v");
}
#[test]
fn test_text_edit_new() {
let range = SourceRange::new(SourcePosition::new(1, 1), SourcePosition::new(1, 5));
let edit = TextEdit::new(range, "hello");
assert_eq!(edit.new_text, "hello");
}
#[test]
fn test_text_edit_insert() {
let edit = TextEdit::insert(SourcePosition::new(1, 1), "inserted");
assert_eq!(edit.new_text, "inserted");
assert_eq!(edit.range.start, edit.range.end); }
#[test]
fn test_text_edit_delete() {
let range = SourceRange::new(SourcePosition::new(1, 1), SourcePosition::new(1, 5));
let edit = TextEdit::delete(range);
assert!(edit.new_text.is_empty());
}
#[test]
fn test_workspace_edit_new() {
let edit = WorkspaceEdit::new();
assert!(edit.changes.is_empty());
assert!(edit.document_changes.is_empty());
}
#[test]
fn test_workspace_edit_add_edit() {
let mut edit = WorkspaceEdit::new();
edit.add_edit(
"file:///test.c",
TextEdit::insert(SourcePosition::new(1, 1), "added"),
);
assert_eq!(edit.changes.len(), 1);
assert_eq!(edit.changes.get("file:///test.c").unwrap().len(), 1);
}
#[test]
fn test_formatting_options_default() {
let opts = FormattingOptions::default();
assert_eq!(opts.tab_size, 4);
assert!(opts.insert_spaces);
assert_eq!(opts.column_limit, 80);
}
#[test]
fn test_ast_node_kind_display() {
assert_eq!(format!("{}", AstNodeKind::FunctionDef), "FunctionDef");
assert_eq!(
format!("{}", AstNodeKind::TranslationUnit),
"TranslationUnit"
);
assert_eq!(format!("{}", AstNodeKind::IfStmt), "IfStmt");
}
#[test]
fn test_symbol_kind_as_lsp_kind() {
assert_eq!(SymbolKind::Function.as_lsp_kind(), 12);
assert_eq!(SymbolKind::Struct.as_lsp_kind(), 23);
assert_eq!(SymbolKind::Variable.as_lsp_kind(), 13);
}
#[test]
fn test_code_action_kind_as_str() {
assert_eq!(CodeActionKind::QuickFix.as_str(), "quickfix");
assert_eq!(CodeActionKind::Refactor.as_str(), "refactor");
assert_eq!(
CodeActionKind::SourceOrganizeImports.as_str(),
"source.organizeImports"
);
}
#[test]
fn test_compilation_db_new() {
let db = CompilationDatabase::new("/src");
assert!(db.valid);
assert_eq!(db.directory, "/src");
assert!(db.is_empty());
}
#[test]
fn test_compilation_db_add_entry() {
let mut db = CompilationDatabase::new("/src");
let entry = CompilationDatabaseEntry::new("/src", "main.c");
db.add_entry(entry);
assert_eq!(db.len(), 1);
assert!(!db.is_empty());
}
#[test]
fn test_compilation_db_get_compile_args() {
let mut db = CompilationDatabase::new("/src");
let mut entry = CompilationDatabaseEntry::new("/src", "main.c");
entry.arguments = Some(vec![
"clang".to_string(),
"-c".to_string(),
"main.c".to_string(),
]);
db.add_entry(entry);
let args = db.get_compile_args("main.c");
assert!(args.is_some());
assert_eq!(args.unwrap().len(), 3);
}
#[test]
fn test_compilation_db_files() {
let mut db = CompilationDatabase::new("/src");
db.add_entry(CompilationDatabaseEntry::new("/src", "a.c"));
db.add_entry(CompilationDatabaseEntry::new("/src", "b.c"));
assert_eq!(db.files().len(), 2);
}
#[test]
fn test_shell_split_simple() {
let args = shell_split("clang -c main.c -O2");
assert_eq!(args, vec!["clang", "-c", "main.c", "-O2"]);
}
#[test]
fn test_shell_split_quoted() {
let args = shell_split("clang -DNAME=\"hello world\" main.c");
assert_eq!(args, vec!["clang", "-DNAME=hello world", "main.c"]);
}
#[test]
fn test_shell_split_single_quote() {
let args = shell_split("echo 'hello world'");
assert_eq!(args, vec!["echo", "hello world"]);
}
#[test]
fn test_batch_result_new() {
let r = BatchCompilationResult::new();
assert_eq!(r.succeeded, 0);
assert_eq!(r.failed, 0);
assert!(r.is_success());
}
#[test]
fn test_batch_result_summary() {
let mut r = BatchCompilationResult::new();
r.succeeded = 3;
r.failed = 1;
r.total_time_ms = 1000;
let s = r.summary();
assert!(s.contains("3 succeeded"));
assert!(s.contains("1 failed"));
assert!(s.contains("1000ms"));
}
#[test]
fn test_compile_options_default() {
let opts = CompileOptions::default();
assert_eq!(opts.optimization, "2");
assert_eq!(opts.target, "x86_64-unknown-linux-gnu");
assert_eq!(opts.c_standard, "c17");
}
#[test]
fn test_compiler_service_config_default() {
let config = CompilerServiceConfig::default();
assert!(config.grpc_enabled);
assert!(config.rest_enabled);
assert!(!config.daemon_enabled);
assert_eq!(config.max_concurrent_compilations, 8);
}
#[test]
fn test_compiler_service_new() {
let service = X86CompilerService::new();
assert!(!service.is_running());
}
#[test]
fn test_compiler_service_with_config() {
let mut config = CompilerServiceConfig::default();
config.daemon_enabled = true;
let service = X86CompilerService::with_config(config);
assert!(service.daemon.is_some());
}
#[test]
fn test_compiler_service_default() {
let service = X86CompilerService::default();
assert!(!service.is_running());
}
#[test]
fn test_compiler_service_health_check() {
let service = X86CompilerService::new();
let health = service.health_check();
assert!(!health.running);
assert!(!health.server_active);
assert!(!health.daemon_healthy);
}
#[test]
fn test_compiler_service_statistics() {
let service = X86CompilerService::new();
let stats = service.statistics();
assert_eq!(stats.total_compilations, 0);
assert_eq!(stats.active_compilations, 0);
}
#[test]
fn test_compiler_service_start_stop() {
let mut service = X86CompilerService::new();
assert!(service.start().is_ok());
assert!(service.is_running());
assert!(service.stop().is_ok());
assert!(!service.is_running());
}
#[test]
fn test_compiler_service_start_twice_fails() {
let mut service = X86CompilerService::new();
service.start().unwrap();
let result = service.start();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), CompilerServiceError::AlreadyRunning);
service.stop().unwrap();
}
#[test]
fn test_compiler_service_stop_not_running_fails() {
let mut service = X86CompilerService::new();
let result = service.stop();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), CompilerServiceError::NotRunning);
}
#[test]
fn test_compiler_service_error_display() {
let err = CompilerServiceError::CompilationFailed("bad source".to_string());
assert!(format!("{}", err).contains("bad source"));
let err = CompilerServiceError::FileNotFound("missing.c".to_string());
assert!(format!("{}", err).contains("missing.c"));
let err = CompilerServiceError::InvalidOptions("invalid".to_string());
assert!(format!("{}", err).contains("invalid"));
}
#[test]
fn test_compiler_service_error_equality() {
assert_eq!(
CompilerServiceError::AlreadyRunning,
CompilerServiceError::AlreadyRunning
);
assert_ne!(
CompilerServiceError::AlreadyRunning,
CompilerServiceError::NotRunning
);
}
#[test]
fn test_service_health_new() {
let h = ServiceHealth::new();
assert!(!h.running);
assert!(!h.is_healthy());
}
#[test]
fn test_service_health_report() {
let h = ServiceHealth::new();
let report = h.report();
assert!(report.contains("STOPPED"));
}
#[test]
fn test_service_health_is_healthy_running() {
let mut h = ServiceHealth::new();
h.running = true;
h.server_active = true;
assert!(h.is_healthy());
}
#[test]
fn test_server_new() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
assert!(!server.is_active());
assert_eq!(server.total_compilation_count(), 0);
}
#[test]
fn test_server_start_grpc() {
let config = CompilerServiceConfig::default();
let mut server = X86CompilationServer::new(&config);
assert!(server.start_grpc().is_ok());
assert!(server.is_active());
}
#[test]
fn test_server_start_rest() {
let config = CompilerServiceConfig::default();
let mut server = X86CompilationServer::new(&config);
assert!(server.start_rest().is_ok());
assert!(server.is_active());
}
#[test]
fn test_server_shutdown() {
let config = CompilerServiceConfig::default();
let mut server = X86CompilationServer::new(&config);
server.start_grpc().unwrap();
assert!(server.shutdown().is_ok());
assert!(!server.is_active());
}
#[test]
fn test_grpc_compile_inline() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = GrpcCompileRequest {
source: GrpcSource::Inline {
text: "int main() { return 0; }".to_string(),
filename: "test.c".to_string(),
},
options: CompileOptions::default(),
request_id: "r1".to_string(),
stream_diagnostics: false,
};
let response = server.grpc_compile(request).unwrap();
assert_eq!(response.request_id, "r1");
assert!(response.result.success);
}
#[test]
fn test_grpc_preprocess() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = GrpcPreprocessRequest {
source: GrpcSource::Inline {
text: "#define X 1\nint x = X;".to_string(),
filename: "test.c".to_string(),
},
defines: vec![("X".to_string(), Some("1".to_string()))],
includes: Vec::new(),
request_id: "p1".to_string(),
};
let response = server.grpc_preprocess(request).unwrap();
assert_eq!(response.request_id, "p1");
}
#[test]
fn test_grpc_parse() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = GrpcParseRequest {
source: GrpcSource::Inline {
text: "int main() { return 0; }".to_string(),
filename: "test.c".to_string(),
},
options: CompileOptions::default(),
request_id: "a1".to_string(),
};
let response = server.grpc_parse(request).unwrap();
assert_eq!(response.ast.kind, AstNodeKind::TranslationUnit);
}
#[test]
fn test_grpc_complete() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = GrpcCompleteRequest {
source: GrpcSource::Inline {
text: "#include <stdio.h>\nint main() { pri".to_string(),
filename: "test.c".to_string(),
},
position: SourcePosition::new(2, 18),
max_results: 10,
request_id: "c1".to_string(),
};
let response = server.grpc_complete(request).unwrap();
assert!(!response.items.is_empty());
}
#[test]
fn test_grpc_format() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = GrpcFormatRequest {
source: GrpcSource::Inline {
text: "int main() { return 0 ; } ".to_string(),
filename: "test.c".to_string(),
},
options: FormattingOptions::default(),
request_id: "f1".to_string(),
};
let response = server.grpc_format(request).unwrap();
assert_eq!(response.request_id, "f1");
}
#[test]
fn test_rest_compile() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = RestCompileRequest {
source: Some("int main() { return 0; }".to_string()),
file: None,
options: None,
};
let response = server.rest_compile(request).unwrap();
assert!(response.success);
}
#[test]
fn test_rest_compile_no_source() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = RestCompileRequest {
source: None,
file: None,
options: None,
};
let result = server.rest_compile(request);
assert!(result.is_err());
}
#[test]
fn test_rest_preprocess() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = RestPreprocessRequest {
source: Some("#define PI 3.14".to_string()),
file: None,
defines: Some(vec!["PI=3.14".to_string()]),
includes: None,
};
let response = server.rest_preprocess(request).unwrap();
assert!(!response.macros.is_empty());
}
#[test]
fn test_rest_parse() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = RestParseRequest {
source: Some("int main() { return 0; }".to_string()),
file: None,
};
let response = server.rest_parse(request).unwrap();
assert!(!response.ast.is_empty());
}
#[test]
fn test_rest_complete() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = RestCompleteRequest {
source: Some("#include <stdio.h>\nint main() { ret".to_string()),
file: None,
line: 2,
column: 15,
max_results: Some(5),
};
let response = server.rest_complete(request).unwrap();
assert!(!response.items.is_empty());
}
#[test]
fn test_rest_format() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = RestFormatRequest {
source: Some("int main()\n{\nreturn 0;\n}".to_string()),
file: None,
options: None,
};
let response = server.rest_format(request).unwrap();
assert!(!response.text.is_empty());
}
#[test]
fn test_streaming_session_new() {
let session = StreamingCompileSession::new();
assert!(session.source().is_empty());
assert!(session.diagnostics().is_empty());
}
#[test]
fn test_streaming_session_append() {
let mut session = StreamingCompileSession::new();
session.append_source("int main() {\n");
session.append_source(" return 0;\n");
session.append_source("}\n");
assert!(session.source().contains("int main()"));
assert!(!session.id().is_empty());
}
#[test]
fn test_server_stream_compile() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let mut session = StreamingCompileSession::new();
let diags = server
.stream_compile_step(&mut session, "int main() { return 0; }")
.unwrap();
assert!(!session.id().is_empty());
}
#[test]
fn test_batch_compile_empty() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let request = BatchCompilationRequest {
files: Vec::new(),
common_options: CompileOptions::default(),
stop_on_error: false,
parallel_jobs: 4,
link: false,
output_name: None,
};
let result = server.batch_compile(request).unwrap();
assert!(result.is_success());
assert_eq!(result.succeeded, 0);
}
#[test]
fn test_load_compilation_db() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
assert!(server.load_compilation_db("/build").is_ok());
}
#[test]
fn test_get_compilation_db() {
let config = CompilerServiceConfig::default();
let server = X86CompilationServer::new(&config);
let db = server.get_compilation_db();
assert!(db.valid);
}
#[test]
fn test_completion_service_new() {
let cs = X86CodeCompletionService::new();
assert!(cs.get_cached_document("test.c").is_empty());
}
#[test]
fn test_completion_service_document_cache() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int main() { }");
assert_eq!(cs.get_cached_document("file:///test.c"), "int main() { }");
cs.close_document("file:///test.c");
assert!(cs.get_cached_document("file:///test.c").is_empty());
}
#[test]
fn test_completion_service_update_document() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "old");
cs.update_document("file:///test.c", "new");
assert_eq!(cs.get_cached_document("file:///test.c"), "new");
}
#[test]
fn test_handle_completion() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int main() { ret");
let params = LspCompletionParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
position: LspPosition {
line: 0,
character: 15,
},
context: None,
};
let result = cs.handle_completion(params);
assert!(!result.items.is_empty());
}
#[test]
fn test_handle_signature_help() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "printf(");
let params = LspSignatureHelpParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
position: LspPosition {
line: 0,
character: 7,
},
context: None,
};
let result = cs.handle_signature_help(params);
assert!(result.is_some());
let sig = result.unwrap();
assert!(!sig.signatures.is_empty());
}
#[test]
fn test_handle_hover() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int x = 0;");
let params = LspHoverParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
position: LspPosition {
line: 0,
character: 0,
},
};
let result = cs.handle_hover(params);
assert!(result.is_some());
}
#[test]
fn test_handle_definition() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int main() { return 0; }");
let params = LspDefinitionParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
position: LspPosition {
line: 0,
character: 4,
},
};
let result = cs.handle_definition(params);
assert!(result.is_some());
}
#[test]
fn test_handle_declaration() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int main() { return 0; }");
let params = LspDefinitionParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
position: LspPosition {
line: 0,
character: 4,
},
};
let result = cs.handle_declaration(params);
assert!(result.is_some());
}
#[test]
fn test_handle_references() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int x = 1;\nint y = x + 2;\n");
let params = LspReferenceParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
position: LspPosition {
line: 0,
character: 4,
},
context: LspReferenceContext {
include_declaration: true,
},
};
let result = cs.handle_references(params);
assert!(result.is_some());
}
#[test]
fn test_handle_document_symbol() {
let cs = X86CodeCompletionService::new();
cs.open_document(
"file:///test.c",
"int main() { return 0; }\nvoid helper() { }\nstruct Point { int x, y; };",
);
let params = LspDocumentSymbolParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
};
let result = cs.handle_document_symbol(params);
assert!(result.is_some());
match result.unwrap() {
LspDocumentSymbolResult::Nested(symbols) => {
assert!(!symbols.is_empty());
}
_ => panic!("Expected nested symbols"),
}
}
#[test]
fn test_handle_workspace_symbol() {
let cs = X86CodeCompletionService::new();
cs.register_workspace_symbols(
"file:///a.c",
vec![SymbolInformation {
name: "my_function".to_string(),
kind: SymbolKind::Function,
location: Location::new(
"file:///a.c",
SourceRange::new(SourcePosition::new(1, 1), SourcePosition::new(1, 10)),
),
container_name: None,
deprecated: false,
}],
);
let params = LspWorkspaceSymbolParams {
query: "my_func".to_string(),
};
let result = cs.handle_workspace_symbol(params);
assert!(result.is_some());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_handle_semantic_tokens() {
let cs = X86CodeCompletionService::new();
cs.open_document(
"file:///test.c",
"int main() {\n int x = 42;\n return x;\n}\n",
);
let params = LspSemanticTokensParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
};
let result = cs.handle_semantic_tokens(params);
assert!(result.is_some());
}
#[test]
fn test_handle_code_action() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int main() { }");
let params = LspCodeActionParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
range: LspRange {
start: LspPosition {
line: 0,
character: 0,
},
end: LspPosition {
line: 0,
character: 14,
},
},
context: LspCodeActionContext {
diagnostics: vec![LspDiagnostic {
range: LspRange {
start: LspPosition {
line: 0,
character: 0,
},
end: LspPosition {
line: 0,
character: 14,
},
},
severity: Some(1),
code: Some("Wunused-parameter".to_string()),
source: Some("clang".to_string()),
message: "Unused parameter".to_string(),
tags: None,
related_information: None,
data: None,
}],
only: None,
trigger_kind: None,
},
};
let result = cs.handle_code_action(params);
assert!(result.is_some());
}
#[test]
fn test_handle_code_lens() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int main() { return 0; }");
let params = LspCodeLensParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
};
let result = cs.handle_code_lens(params);
assert!(result.is_some());
}
#[test]
fn test_handle_rename() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int old_name = 1;\nint x = old_name;\n");
let params = LspRenameParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
position: LspPosition {
line: 0,
character: 4,
},
new_name: "new_name".to_string(),
};
let result = cs.handle_rename(params);
assert!(result.is_some());
}
#[test]
fn test_handle_formatting() {
let cs = X86CodeCompletionService::new();
cs.open_document("file:///test.c", "int main()\n{\n return 0;\n}\n");
let params = LspFormattingParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
options: LspFormattingOptions {
tab_size: 4,
insert_spaces: true,
trim_trailing_whitespace: Some(true),
insert_final_newline: Some(true),
trim_final_newlines: Some(false),
},
};
let result = cs.handle_formatting(params);
assert!(result.is_some());
}
#[test]
fn test_publish_diagnostics() {
let cs = X86CodeCompletionService::new();
cs.publish_diagnostics(
"file:///test.c",
vec![LspDiagnostic {
range: LspRange {
start: LspPosition {
line: 0,
character: 0,
},
end: LspPosition {
line: 0,
character: 10,
},
},
severity: Some(1),
code: Some("W001".to_string()),
source: Some("clang".to_string()),
message: "Test warning".to_string(),
tags: None,
related_information: None,
data: None,
}],
);
let collected = cs.collect_diagnostics();
assert_eq!(collected.len(), 1);
assert_eq!(collected[0].uri, "file:///test.c");
}
#[test]
fn test_register_symbols() {
let cs = X86CodeCompletionService::new();
let items = vec![CompletionItem {
label: "test_fn".to_string(),
insert_text: "test_fn".to_string(),
detail: Some("void test_fn()".to_string()),
documentation: Some("A test function".to_string()),
kind: CompletionItemKind::Function,
sort_priority: 0,
deprecated: false,
filter_text: None,
is_snippet: false,
additional_edits: Vec::new(),
command: None,
data: None,
}];
cs.register_symbols("file:///test.c", items.clone());
let retrieved = cs.get_symbols("file:///test.c");
assert_eq!(retrieved.len(), 1);
assert_eq!(retrieved[0].label, "test_fn");
}
#[test]
fn test_extract_prefix() {
let cs = X86CodeCompletionService::new();
let source = "int main() { ret";
let pos = SourcePosition::new(1, 15);
cs.open_document("file:///test.c", source);
let params = LspCompletionParams {
text_document: LspTextDocumentIdentifier {
uri: "file:///test.c".to_string(),
},
position: LspPosition {
line: 0,
character: 14,
},
context: None,
};
let result = cs.handle_completion(params);
assert!(!result.items.is_empty());
}
#[test]
fn test_index_service_new() {
let idx = X86IndexService::new(None);
assert_eq!(idx.total_symbols(), 0);
assert_eq!(idx.total_files(), 0);
assert!(idx.database_size_bytes().is_some());
}
#[test]
fn test_index_service_index_file() {
let idx = X86IndexService::new(None);
let count = idx.index_file(
"test.c",
"int main() { return 0; }\nstruct Point { int x; };\n#define MAX 100\n",
"c",
);
assert!(count > 0);
assert_eq!(idx.total_files(), 1);
assert!(idx.total_symbols() > 0);
}
#[test]
fn test_index_service_find_symbol() {
let idx = X86IndexService::new(None);
idx.index_file(
"test.c",
"int main() { return 0; }\nvoid helper() { }\n",
"c",
);
let results = idx.find_symbol("main");
assert!(!results.is_empty());
assert_eq!(results[0].name, "main");
}
#[test]
fn test_index_service_query_symbols() {
let idx = X86IndexService::new(None);
idx.index_file(
"test.c",
"int main() { return 0; }\nint max_value = 100;\n",
"c",
);
let results = idx.query_symbols("ma");
assert!(results.len() >= 2);
}
#[test]
fn test_index_service_find_definition() {
let idx = X86IndexService::new(None);
idx.index_file("test.c", "int main() { return 0; }", "c");
let def = idx.find_definition("main");
assert!(def.is_some());
}
#[test]
fn test_index_service_referencing_files() {
let idx = X86IndexService::new(None);
idx.index_file("a.c", "int x = 1;", "c");
idx.index_file("b.c", "extern int x;", "c");
let files = idx.referencing_files("x");
assert_eq!(files.len(), 2);
}
#[test]
fn test_call_graph() {
let idx = X86IndexService::new(None);
idx.add_call_edge("main", "helper");
idx.add_call_edge("main", "printf");
idx.add_call_edge("helper", "malloc");
let callees = idx.get_callees("main");
assert!(callees.contains(&"helper".to_string()));
assert!(callees.contains(&"printf".to_string()));
let callers = idx.get_callers("helper");
assert!(callers.contains(&"main".to_string()));
assert!(idx.has_call_edge("main", "helper"));
assert!(!idx.has_call_edge("helper", "main"));
}
#[test]
fn test_call_graph_stats() {
let idx = X86IndexService::new(None);
idx.add_call_edge("a", "b");
idx.add_call_edge("a", "c");
idx.add_call_edge("b", "c");
let stats = idx.call_graph_stats();
assert_eq!(stats.total_edges, 3);
}
#[test]
fn test_type_hierarchy() {
let idx = X86IndexService::new(None);
idx.add_type_edge("Dog", "Animal");
idx.add_type_edge("Cat", "Animal");
idx.add_type_edge("Poodle", "Dog");
assert_eq!(idx.get_base_type("Dog"), Some("Animal".to_string()));
let derived = idx.get_derived_types("Animal");
assert!(derived.contains(&"Dog".to_string()));
assert!(derived.contains(&"Cat".to_string()));
assert!(idx.is_derived_from("Poodle", "Animal"));
assert!(!idx.is_derived_from("Animal", "Dog"));
}
#[test]
fn test_include_graph() {
let idx = X86IndexService::new(None);
idx.index_file(
"main.c",
"#include <stdio.h>\n#include \"helper.h\"\nint main() { return 0; }",
"c",
);
let includes = idx.get_includes("main.c");
assert!(includes.contains(&"stdio.h".to_string()));
assert!(includes.contains(&"helper.h".to_string()));
}
#[test]
fn test_include_included_by() {
let idx = X86IndexService::new(None);
idx.index_file("a.c", "#include \"common.h\"", "c");
idx.index_file("b.c", "#include \"common.h\"", "c");
let included_by = idx.get_included_by("common.h");
assert!(included_by.contains(&"a.c".to_string()));
assert!(included_by.contains(&"b.c".to_string()));
}
#[test]
fn test_transitive_includes() {
let idx = X86IndexService::new(None);
idx.index_file("a.c", "#include \"b.h\"", "c");
idx.index_file("b.h", "#include \"c.h\"", "c");
idx.index_file("c.h", "// empty", "c");
let transitive = idx.get_transitive_includes("a.c");
assert!(transitive.contains(&"b.h".to_string()));
assert!(transitive.contains(&"c.h".to_string()));
}
#[test]
fn test_circular_include_detection() {
let idx = X86IndexService::new(None);
idx.index_file("a.h", "#include \"b.h\"", "c");
idx.index_file("b.h", "#include \"a.h\"", "c");
let cycle = idx.has_circular_include("a.h");
assert!(cycle.is_some());
}
#[test]
fn test_no_circular_include() {
let idx = X86IndexService::new(None);
idx.index_file("a.h", "#include \"b.h\"", "c");
idx.index_file("b.h", "#include \"c.h\"", "c");
idx.index_file("c.h", "// end", "c");
let cycle = idx.has_circular_include("a.h");
assert!(cycle.is_none());
}
#[test]
fn test_index_service_remove_file() {
let idx = X86IndexService::new(None);
idx.index_file("test.c", "int x;", "c");
assert_eq!(idx.total_files(), 1);
idx.remove_file("test.c");
assert_eq!(idx.total_files(), 0);
}
#[test]
fn test_index_service_clear() {
let idx = X86IndexService::new(None);
idx.index_file("test.c", "int x;", "c");
idx.add_call_edge("a", "b");
idx.clear();
assert_eq!(idx.total_symbols(), 0);
assert_eq!(idx.total_files(), 0);
assert_eq!(idx.call_graph_stats().total_edges, 0);
}
#[test]
fn test_index_service_save_load() {
let idx = X86IndexService::new(Some("/tmp/test-index.db"));
idx.index_file("test.c", "int x;", "c");
assert!(idx.save().is_ok());
assert!(idx.load().is_ok());
}
#[test]
fn test_diagnostic_service_new() {
let ds = X86DiagnosticService::new(true);
assert_eq!(ds.monitored_file_count(), 0);
}
#[test]
fn test_diagnostic_service_analyze() {
let ds = X86DiagnosticService::new(true);
let diags = ds.analyze("file:///test.c", "int x\nint y;", false);
assert!(!diags.is_empty());
assert!(ds.monitored_file_count() > 0);
}
#[test]
fn test_diagnostic_service_caching() {
let ds = X86DiagnosticService::new(true);
let source = "int x = 1;";
let hash = hash_source(source);
assert!(!ds.is_fresh("file:///test.c", hash));
ds.analyze("file:///test.c", source, false);
assert!(ds.is_fresh("file:///test.c", hash));
}
#[test]
fn test_diagnostic_service_force_reanalyze() {
let ds = X86DiagnosticService::new(true);
let source = "int x = 1;";
ds.analyze("file:///test.c", source, false);
let diags1 = ds.analyze("file:///test.c", source, true);
assert!(diags1.len() >= 0);
}
#[test]
fn test_diagnostic_service_cache_invalidation() {
let ds = X86DiagnosticService::new(true);
ds.analyze("file:///test.c", "int x;", false);
ds.invalidate("file:///test.c");
let cached = ds.get_cached("file:///test.c");
assert!(cached.is_none());
}
#[test]
fn test_diagnostic_service_filter_by_severity() {
let ds = X86DiagnosticService::new(true);
ds.analyze("file:///test.c", "int x\nint y;", false);
let errors = ds.filter_by_severity("file:///test.c", DiagnosticSeverity::Error);
let warnings = ds.filter_by_severity("file:///test.c", DiagnosticSeverity::Warning);
assert!(warnings.len() >= errors.len());
}
#[test]
fn test_diagnostic_service_group_by_code() {
let ds = X86DiagnosticService::new(true);
ds.analyze("file:///test.c", "int x\nint y;", false);
let groups = ds.group_by_code("file:///test.c");
assert!(!groups.is_empty());
}
#[test]
fn test_diagnostic_service_group_by_category() {
let ds = X86DiagnosticService::new(true);
ds.analyze("file:///test.c", "int x\nint y;", false);
let groups = ds.group_by_category("file:///test.c");
assert!(!groups.is_empty());
}
#[test]
fn test_diagnostic_service_quick_fixes() {
let ds = X86DiagnosticService::new(true);
let diag = CompilerDiagnostic {
severity: DiagnosticSeverity::Warning,
message: "Unused variable".to_string(),
file: "test.c".to_string(),
line: 1,
column: 5,
end_line: Some(1),
end_column: Some(6),
category: "semantic".to_string(),
code: Some("Wunused-variable".to_string()),
fixits: Vec::new(),
notes: Vec::new(),
source_text: Some("int x;".to_string()),
};
let fixes = ds.get_quick_fixes(&diag, "int x;");
assert!(!fixes.is_empty());
assert_eq!(fixes[0].description, "Remove unused variable");
}
#[test]
fn test_diagnostic_service_to_lsp() {
let ds = X86DiagnosticService::new(true);
ds.analyze("file:///test.c", "int x\nint y;", false);
let lsp = ds.to_lsp_diagnostics("file:///test.c");
assert!(lsp.is_some());
assert!(!lsp.unwrap().diagnostics.is_empty());
}
#[test]
fn test_diagnostic_service_disabled() {
let ds = X86DiagnosticService::new(false);
let diags = ds.analyze("file:///test.c", "int x;", false);
assert!(diags.is_empty());
}
#[test]
fn test_diagnostic_service_clear_all() {
let ds = X86DiagnosticService::new(true);
ds.analyze("file:///a.c", "int x;", false);
ds.analyze("file:///b.c", "int y;", false);
ds.clear_all();
assert_eq!(ds.monitored_file_count(), 0);
assert_eq!(ds.total_diagnostics_count(), 0);
}
#[test]
fn test_diagnostic_service_is_code_supported() {
let ds = X86DiagnosticService::new(true);
assert!(ds.is_code_supported("Wunused-variable"));
assert!(ds.is_code_supported("Eundeclared"));
assert!(!ds.is_code_supported("Wnonexistent"));
}
#[test]
fn test_build_service_new() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
assert_eq!(bs.tasks_completed(), 0);
assert!(!bs.is_distributed_available());
}
#[test]
fn test_build_service_cache_stats() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
let stats = bs.cache_stats();
assert_eq!(stats.entries, 0);
assert!(stats.hit_rate >= 0.0);
}
#[test]
fn test_build_service_cache_store_and_lookup() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
let opts = CompileOptions::default();
let result = bs.cache_lookup("test.c", &opts);
assert!(result.is_none());
bs.cache_store("test.c", &opts, vec![1, 2, 3], vec!["stdio.h".to_string()]);
let result = bs.cache_lookup("test.c", &opts);
assert!(result.is_some());
assert_eq!(result.unwrap().output, vec![1, 2, 3]);
}
#[test]
fn test_build_service_clear_cache() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
let opts = CompileOptions::default();
bs.cache_store("test.c", &opts, vec![1], Vec::new());
bs.clear_cache();
assert!(bs.cache_lookup("test.c", &opts).is_none());
}
#[test]
fn test_dependency_tracker() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
bs.register_dependencies(
"main.c",
vec!["stdio.h".to_string(), "helper.h".to_string()],
);
let dependents = bs.get_dependents("stdio.h");
assert!(dependents.contains(&"main.c".to_string()));
}
#[test]
fn test_rebuild_impact() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
bs.register_dependencies("a.c", vec!["common.h".to_string()]);
bs.register_dependencies("b.c", vec!["common.h".to_string(), "a.o".to_string()]);
let impact = bs.get_rebuild_impact("common.h");
assert!(impact.contains(&"a.c".to_string()));
assert!(impact.contains(&"b.c".to_string()));
}
#[test]
fn test_build_service_submit_distributed_not_enabled() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
let result = bs.submit_distributed("test.c", &CompileOptions::default());
assert!(result.is_err());
}
#[test]
fn test_build_service_distributed_status() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
let status = bs.distributed_status();
assert!(!status.enabled);
}
#[test]
fn test_build_service_progress() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
bs.start_build(10);
let status = bs.get_status();
assert!(status.building);
assert_eq!(status.queued, 10);
bs.update_progress(5, 1, 10);
let status = bs.get_status();
assert_eq!(status.completed, 5);
assert_eq!(status.failed, 1);
}
#[test]
fn test_build_service_finish() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
bs.start_build(1);
bs.finish_build();
let status = bs.get_status();
assert!(!status.building);
assert_eq!(status.phase, BuildPhase::Done);
}
#[test]
fn test_build_service_needs_rebuild() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
assert!(bs.needs_rebuild("test.c", &CompileOptions::default()));
}
#[test]
fn test_daemon_new() {
let config = CompilerServiceConfig::default();
let daemon = X86DaemonMode::new(&config);
assert!(!daemon.is_healthy());
assert_eq!(daemon.cache_size(), 0);
}
#[test]
fn test_daemon_start_stop() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
assert!(daemon.start().is_ok());
assert!(daemon.is_healthy());
assert!(daemon.shutdown(true).is_ok());
assert!(!daemon.is_healthy());
}
#[test]
fn test_daemon_start_twice_fails() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
daemon.start().unwrap();
assert!(daemon.start().is_err());
daemon.shutdown(true).unwrap();
}
#[test]
fn test_daemon_pch_cache() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
daemon.start().unwrap();
assert!(daemon.lookup_pch("stdio.h").is_none());
daemon.cache_pch("stdio.h", vec![0xde, 0xad, 0xbe, 0xef], 1000);
let cached = daemon.lookup_pch("stdio.h");
assert!(cached.is_some());
assert_eq!(cached.unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
daemon.invalidate_pch("stdio.h");
assert!(daemon.lookup_pch("stdio.h").is_none());
daemon.shutdown(true).unwrap();
}
#[test]
fn test_daemon_module_cache() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
daemon.start().unwrap();
daemon.cache_module("std.core", vec![1, 2, 3], vec!["std.io".to_string()]);
let cached = daemon.lookup_module("std.core");
assert!(cached.is_some());
daemon.shutdown(true).unwrap();
}
#[test]
fn test_daemon_compilation_cache() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
daemon.start().unwrap();
let opts = CompileOptions::default();
let result = CompilationResult::new("test.c");
daemon.cache_compilation("test.c", &opts, result.clone());
let cached = daemon.lookup_compilation("test.c", &opts);
assert!(cached.is_some());
daemon.shutdown(true).unwrap();
}
#[test]
fn test_daemon_memory_usage() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
daemon.start().unwrap();
assert!(daemon.memory_usage_mb() >= 0.0);
daemon.shutdown(true).unwrap();
}
#[test]
fn test_daemon_health_check() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
daemon.start().unwrap();
let health = daemon.health_check();
assert!(health.running);
assert!(health.healthy);
daemon.shutdown(true).unwrap();
}
#[test]
fn test_daemon_stats() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
daemon.start().unwrap();
let stats = daemon.get_stats();
assert_eq!(stats.compilation_cache_entries, 0);
daemon.shutdown(true).unwrap();
}
#[test]
fn test_daemon_maybe_evict() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
daemon.start().unwrap();
daemon.maybe_evict(); daemon.shutdown(true).unwrap();
}
#[test]
fn test_daemon_should_auto_shutdown_false() {
let config = CompilerServiceConfig::default();
let mut daemon = X86DaemonMode::new(&config);
daemon.start().unwrap();
assert!(!daemon.should_auto_shutdown());
daemon.shutdown(true).unwrap();
}
#[test]
fn test_api_new() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
assert!(api.is_initialized());
}
#[test]
fn test_api_compile() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let result = api.compile("int main() { return 0; }", None);
assert!(result.success);
}
#[test]
fn test_api_compile_with_options() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let mut opts = CompileOptions::default();
opts.optimization = "0".to_string();
let result = api.compile("int main() { return 0; }", Some(&opts));
assert!(result.success);
}
#[test]
fn test_api_preprocess() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let mut opts = CompileOptions::default();
opts.defines
.push(("DEBUG".to_string(), Some("1".to_string())));
let result = api.preprocess("#define DEBUG 1\nint x = DEBUG;", Some(&opts));
assert!(!result.macros.is_empty());
}
#[test]
fn test_api_parse() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let ast = api.parse(
"int main() { return 0; }\nstruct Point { int x, y; };",
None,
);
assert_eq!(ast.kind, AstNodeKind::TranslationUnit);
assert!(!ast.children.is_empty());
}
#[test]
fn test_api_complete() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let completions = api.complete("int main() { ret", SourcePosition::new(1, 15), None);
assert!(!completions.is_empty());
}
#[test]
fn test_api_complete_empty_prefix() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let completions = api.complete("int x;", SourcePosition::new(1, 1), None);
assert!(!completions.is_empty());
}
#[test]
fn test_api_format() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let result = api.format("int main()\n{\n return 0 ;\n}\n", None);
assert!(!result.text.is_empty());
}
#[test]
fn test_api_format_trim_trailing() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let mut opts = FormattingOptions::default();
opts.trim_trailing_whitespace = true;
let result = api.format("int x = 1; ", Some(&opts));
assert!(!result.text.contains(" "));
}
#[test]
fn test_api_format_no_change() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let source = "int main() {\n return 0;\n}\n";
let result = api.format(source, None);
assert!(!result.was_changed || result.text == source);
}
#[test]
fn test_api_index() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let db = api.index("/project");
assert_eq!(db.file_count(), 0);
assert_eq!(db.symbol_count(), 0);
}
#[test]
fn test_index_database_new() {
let db = IndexDatabase::new("/project");
assert_eq!(db.project_path, "/project");
assert!(db.files.is_empty());
}
#[test]
fn test_index_database_query() {
let mut db = IndexDatabase::new("/project");
db.symbols.push(SymbolEntry {
name: "main".to_string(),
kind: SymbolKind::Function,
file: "main.c".to_string(),
location: SourcePosition::new(1, 1),
usr: "c:@F@main".to_string(),
is_definition: true,
type_string: Some("int ()".to_string()),
documentation: None,
language: "c".to_string(),
parent: None,
});
let results = db.query("main");
assert_eq!(results.len(), 1);
}
#[test]
fn test_include_stats() {
let db = IndexDatabase::new("/project");
let stats = db.include_stats();
assert_eq!(stats.total_edges, 0);
assert_eq!(stats.total_files, 0);
}
#[test]
fn test_preprocessed_source_structure() {
let ps = PreprocessedSource {
text: "processed content".to_string(),
input_file: "input.c".to_string(),
included_files: vec!["stdio.h".to_string()],
line_markers: Vec::new(),
macros: vec![("MAX".to_string(), Some("100".to_string()))],
undef_macros: Vec::new(),
diagnostics: Vec::new(),
};
assert_eq!(ps.included_files.len(), 1);
}
#[test]
fn test_lsp_position_to_source() {
let lsp = LspPosition {
line: 0,
character: 4,
};
let sp = lsp.to_source_position();
assert_eq!(sp.line, 1);
assert_eq!(sp.column, 5);
}
#[test]
fn test_formatted_source_new() {
let fs = FormattedSource::new("test.c", "content", true, 3);
assert!(fs.was_changed);
assert_eq!(fs.edit_count, 3);
}
#[test]
fn test_compiler_diagnostic_values() {
let diag = CompilerDiagnostic {
severity: DiagnosticSeverity::Error,
message: "Test error".to_string(),
file: "test.c".to_string(),
line: 10,
column: 5,
end_line: Some(10),
end_column: Some(10),
category: "semantic".to_string(),
code: Some("E001".to_string()),
fixits: Vec::new(),
notes: vec![DiagnosticNote::new("see here", "test.c", 9, 1)],
source_text: Some("int x;".to_string()),
};
assert_eq!(diag.severity, DiagnosticSeverity::Error);
assert_eq!(diag.notes.len(), 1);
}
#[test]
fn test_service_health_fields() {
let mut h = ServiceHealth::new();
h.total_compilations = 42;
h.active_compilations = 3;
assert_eq!(h.total_compilations, 42);
assert_eq!(h.active_compilations, 3);
}
#[test]
fn test_hash_source_same_input() {
let h1 = hash_source("hello");
let h2 = hash_source("hello");
assert_eq!(h1, h2);
}
#[test]
fn test_hash_source_different_input() {
let h1 = hash_source("hello");
let h2 = hash_source("world");
assert_ne!(h1, h2);
}
#[test]
fn test_hash_options_same() {
let o1 = CompileOptions::default();
let o2 = CompileOptions::default();
assert_eq!(hash_options(&o1), hash_options(&o2));
}
#[test]
fn test_hash_options_different() {
let mut o1 = CompileOptions::default();
let mut o2 = CompileOptions::default();
o2.optimization = "0".to_string();
assert_ne!(hash_options(&o1), hash_options(&o2));
}
#[test]
fn test_encode_semantic_tokens_empty() {
let cs = X86CodeCompletionService::new();
let tokens: Vec<SemanticToken> = Vec::new();
let encoded = cs.encode_semantic_tokens(&tokens);
assert!(encoded.is_empty());
}
#[test]
fn test_encode_semantic_tokens_delta() {
let cs = X86CodeCompletionService::new();
let tokens = vec![
SemanticToken {
line: 0,
start_char: 0,
length: 3,
token_type: SemanticTokenType::Keyword as u32,
token_modifiers: 0,
},
SemanticToken {
line: 0,
start_char: 4,
length: 4,
token_type: SemanticTokenType::Function as u32,
token_modifiers: 0,
},
];
let encoded = cs.encode_semantic_tokens(&tokens);
assert_eq!(encoded.len(), 10); assert_eq!(encoded[0], 0); }
#[test]
fn test_lsp_signature_help() {
let help = LspSignatureHelp {
signatures: vec![LspSignatureInformation {
label: "void foo(int x)".to_string(),
documentation: Some("Does foo".to_string()),
parameters: Some(vec![LspParameterInformation {
label: LspParameterLabel::String("int x".to_string()),
documentation: Some("The x parameter".to_string()),
}]),
active_parameter: Some(0),
}],
active_signature: Some(0),
active_parameter: Some(0),
};
assert_eq!(help.signatures.len(), 1);
}
#[test]
fn test_lsp_code_lens() {
let lens = LspCodeLens {
range: LspRange {
start: LspPosition {
line: 0,
character: 0,
},
end: LspPosition {
line: 0,
character: 10,
},
},
command: Some(LspCommand {
title: "Run".to_string(),
command: "run".to_string(),
arguments: None,
}),
data: None,
};
assert!(lens.command.is_some());
}
#[test]
fn test_semantic_tokens_legend() {
let legend = SemanticTokensLegend::default();
assert!(legend.token_types.len() > 10);
assert!(legend.token_modifiers.len() > 5);
assert!(legend.token_types.contains(&"keyword".to_string()));
assert!(legend.token_types.contains(&"function".to_string()));
}
#[test]
fn test_lsp_server_capabilities_default() {
let caps = LspServerCapabilities::default();
assert!(caps.completion_provider.is_some());
assert!(caps.hover_provider);
assert!(caps.definition_provider);
assert!(caps.references_provider);
assert!(caps.rename_provider);
}
#[test]
fn test_compiler_service_with_daemon_config() {
let mut config = CompilerServiceConfig::default();
config.daemon_enabled = true;
config.daemon_cache_size = 128;
let mut service = X86CompilerService::with_config(config);
service.start().unwrap();
let health = service.health_check();
assert!(health.daemon_healthy);
service.stop().unwrap();
}
#[test]
fn test_build_service_complete_task() {
let config = CompilerServiceConfig::default();
let bs = X86BuildService::new(&config);
let task_id = bs.queue_incremental("test.c", &CompileOptions::default());
let result = CompilationResult::new("test.c");
bs.complete_task(&task_id, result);
assert_eq!(bs.tasks_completed(), 1);
}
#[test]
fn test_completion_service_config_default() {
let cfg = CompletionServiceConfig::default();
assert_eq!(cfg.max_completions, DEFAULT_MAX_COMPLETIONS);
assert!(cfg.snippet_support);
}
#[test]
fn test_daemon_config_default() {
let cfg = DaemonConfig::default();
assert_eq!(cfg.pch_cache_size, 32);
assert_eq!(cfg.compilation_cache_size, DEFAULT_DAEMON_CACHE_SIZE);
}
#[test]
fn test_compiler_service_stats_default() {
let stats = CompilerServiceStats {
total_compilations: 0,
active_compilations: 0,
cached_entries: 0,
index_symbols: 0,
index_files: 0,
diagnostic_files: 0,
build_tasks_completed: 0,
uptime_secs: None,
};
assert_eq!(stats.total_compilations, 0);
}
#[test]
fn test_batch_compilation_result_default() {
let r = BatchCompilationResult::default();
assert!(r.file_results.is_empty());
assert_eq!(r.succeeded, 0);
}
#[test]
fn test_compilation_database_default() {
let db = CompilationDatabase::new(".");
assert!(db.valid);
}
#[test]
fn test_streaming_session_default() {
let s = StreamingCompileSession::default();
assert!(s.source().is_empty());
}
#[test]
fn test_all_code_action_kinds() {
let kinds = [
CodeActionKind::QuickFix,
CodeActionKind::Refactor,
CodeActionKind::RefactorExtract,
CodeActionKind::RefactorInline,
CodeActionKind::RefactorRewrite,
CodeActionKind::Source,
CodeActionKind::SourceOrganizeImports,
CodeActionKind::SourceFixAll,
];
for kind in &kinds {
let s = kind.as_str();
assert!(!s.is_empty());
}
}
#[test]
fn test_all_semantic_token_types_exist() {
let types = [
SemanticTokenType::Namespace,
SemanticTokenType::Type,
SemanticTokenType::Class,
SemanticTokenType::Enum,
SemanticTokenType::Interface,
SemanticTokenType::Struct,
SemanticTokenType::TypeParameter,
SemanticTokenType::Parameter,
SemanticTokenType::Variable,
SemanticTokenType::Property,
SemanticTokenType::EnumMember,
SemanticTokenType::Event,
SemanticTokenType::Function,
SemanticTokenType::Method,
SemanticTokenType::Macro,
SemanticTokenType::Keyword,
SemanticTokenType::Modifier,
SemanticTokenType::Comment,
SemanticTokenType::String,
SemanticTokenType::Number,
SemanticTokenType::Regexp,
SemanticTokenType::Operator,
SemanticTokenType::Decorator,
];
for (i, t) in types.iter().enumerate() {
assert_eq!(*t as u32, i as u32);
}
}
#[test]
fn test_all_completion_item_kinds() {
let kinds = [
CompletionItemKind::Text,
CompletionItemKind::Method,
CompletionItemKind::Function,
CompletionItemKind::Constructor,
CompletionItemKind::Field,
CompletionItemKind::Variable,
CompletionItemKind::Class,
CompletionItemKind::Interface,
CompletionItemKind::Module,
CompletionItemKind::Property,
CompletionItemKind::Unit,
CompletionItemKind::Value,
CompletionItemKind::Enum,
CompletionItemKind::Keyword,
CompletionItemKind::Snippet,
CompletionItemKind::Color,
CompletionItemKind::File,
CompletionItemKind::Reference,
CompletionItemKind::Folder,
CompletionItemKind::EnumMember,
CompletionItemKind::Constant,
CompletionItemKind::Struct,
CompletionItemKind::Event,
CompletionItemKind::Operator,
CompletionItemKind::TypeParameter,
];
for kind in &kinds {
let lsp = kind.as_lsp_kind();
assert!(lsp > 0);
let icon = kind.icon();
assert!(!icon.is_empty());
}
}
#[test]
fn test_all_symbol_kinds() {
let kinds = [
SymbolKind::File,
SymbolKind::Module,
SymbolKind::Namespace,
SymbolKind::Package,
SymbolKind::Class,
SymbolKind::Method,
SymbolKind::Property,
SymbolKind::Field,
SymbolKind::Constructor,
SymbolKind::Enum,
SymbolKind::Interface,
SymbolKind::Function,
SymbolKind::Variable,
SymbolKind::Constant,
SymbolKind::String,
SymbolKind::Number,
SymbolKind::Boolean,
SymbolKind::Array,
SymbolKind::Object,
SymbolKind::Key,
SymbolKind::Null,
SymbolKind::EnumMember,
SymbolKind::Struct,
SymbolKind::Event,
SymbolKind::Operator,
SymbolKind::TypeParameter,
];
for kind in &kinds {
assert!(kind.as_lsp_kind() > 0);
}
}
#[test]
fn test_all_ast_node_kinds_display() {
let kinds = [
AstNodeKind::TranslationUnit,
AstNodeKind::FunctionDecl,
AstNodeKind::FunctionDef,
AstNodeKind::VarDecl,
AstNodeKind::TypeDecl,
AstNodeKind::EnumDecl,
AstNodeKind::StructDecl,
AstNodeKind::UnionDecl,
AstNodeKind::CompoundStmt,
AstNodeKind::IfStmt,
AstNodeKind::ForStmt,
AstNodeKind::WhileStmt,
AstNodeKind::DoWhileStmt,
AstNodeKind::SwitchStmt,
AstNodeKind::ReturnStmt,
AstNodeKind::BreakStmt,
AstNodeKind::ContinueStmt,
AstNodeKind::DeclStmt,
AstNodeKind::ExprStmt,
AstNodeKind::CallExpr,
AstNodeKind::BinaryExpr,
AstNodeKind::UnaryExpr,
AstNodeKind::MemberExpr,
AstNodeKind::ArraySubscript,
AstNodeKind::ConditionalExpr,
AstNodeKind::CastExpr,
AstNodeKind::IntegerLiteral,
AstNodeKind::FloatLiteral,
AstNodeKind::StringLiteral,
AstNodeKind::CharLiteral,
AstNodeKind::Identifier,
AstNodeKind::ParameterDecl,
AstNodeKind::FieldDecl,
AstNodeKind::EnumConstant,
AstNodeKind::LabelStmt,
AstNodeKind::GotoStmt,
AstNodeKind::TypedefDecl,
AstNodeKind::NamespaceDecl,
AstNodeKind::UsingDecl,
AstNodeKind::TemplateDecl,
AstNodeKind::Unknown,
];
for kind in &kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_build_phases() {
let phases = [
BuildPhase::Idle,
BuildPhase::Resolving,
BuildPhase::Compiling,
BuildPhase::Linking,
BuildPhase::Packaging,
BuildPhase::Done,
];
for phase in &phases {
let _ = format!("{:?}", phase);
}
}
#[test]
fn test_lru_cache_insert_and_get() {
let mut cache: LruCache<String, i32> = LruCache::new(3);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
assert_eq!(*cache.get(&"a".to_string()).unwrap(), 1);
assert_eq!(*cache.get(&"b".to_string()).unwrap(), 2);
assert!(cache.get(&"c".to_string()).is_none());
}
#[test]
fn test_lru_cache_eviction() {
let mut cache: LruCache<String, i32> = LruCache::new(2);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
cache.insert("c".to_string(), 3);
assert_eq!(cache.len(), 2);
}
#[test]
fn test_lru_cache_remove() {
let mut cache: LruCache<String, i32> = LruCache::new(5);
cache.insert("x".to_string(), 42);
assert_eq!(cache.remove(&"x".to_string()), Some(42));
assert!(cache.get(&"x".to_string()).is_none());
}
#[test]
fn test_lru_cache_clear() {
let mut cache: LruCache<String, i32> = LruCache::new(5);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
cache.clear();
assert!(cache.is_empty());
}
#[test]
fn test_api_parse_children() {
let config = CompilerServiceConfig::default();
let api = X86CompilerAPI::new(&config);
let ast = api.parse(
"int main() { return 0; }\nstruct Point { int x; };\nenum Color { RED };\n#define MAX 100\n",
None,
);
assert!(ast.children.len() >= 4);
let kinds: Vec<AstNodeKind> = ast.children.iter().map(|c| c.kind).collect();
assert!(kinds.contains(&AstNodeKind::FunctionDef));
assert!(kinds.contains(&AstNodeKind::StructDecl));
assert!(kinds.contains(&AstNodeKind::EnumDecl));
}
#[test]
fn test_lsp_parameter_label_variants() {
let string_label = LspParameterLabel::String("int x".to_string());
let offset_label = LspParameterLabel::Offsets(0, 5);
match (&string_label, &offset_label) {
(LspParameterLabel::String(s), _) => assert_eq!(s, "int x"),
_ => {}
}
}
#[test]
fn test_completion_item_all_fields() {
let item = CompletionItem {
label: "test".to_string(),
insert_text: "test()".to_string(),
detail: Some("void test()".to_string()),
documentation: Some("A test function".to_string()),
kind: CompletionItemKind::Function,
sort_priority: 10,
deprecated: true,
filter_text: Some("test".to_string()),
is_snippet: false,
additional_edits: vec![TextEdit::insert(
SourcePosition::new(1, 1),
"#include <test.h>\n",
)],
command: Some("editor.action.triggerParameterHints".to_string()),
data: Some("{\"id\":1}".to_string()),
};
assert!(item.deprecated);
assert_eq!(item.sort_priority, 10);
assert!(item.command.is_some());
assert!(!item.additional_edits.is_empty());
}
#[test]
fn test_compiler_service_error_all_variants() {
let errors = [
CompilerServiceError::AlreadyRunning,
CompilerServiceError::NotRunning,
CompilerServiceError::Timeout,
CompilerServiceError::CompilationFailed("msg".to_string()),
CompilerServiceError::PreprocessFailed("msg".to_string()),
CompilerServiceError::ParseFailed("msg".to_string()),
CompilerServiceError::FileNotFound("path".to_string()),
CompilerServiceError::InvalidOptions("msg".to_string()),
CompilerServiceError::ServerError("msg".to_string()),
CompilerServiceError::Internal("msg".to_string()),
CompilerServiceError::GrpcError("msg".to_string()),
CompilerServiceError::RestError("msg".to_string()),
CompilerServiceError::DaemonError("msg".to_string()),
CompilerServiceError::BuildError("msg".to_string()),
CompilerServiceError::IndexError("msg".to_string()),
];
for err in &errors {
let s = format!("{}", err);
assert!(!s.is_empty());
}
}
#[test]
fn test_index_error_display() {
let e1 = IndexError::IoError("disk full".to_string());
let e2 = IndexError::DatabaseError("corrupt".to_string());
assert!(format!("{}", e1).contains("disk full"));
assert!(format!("{}", e2).contains("corrupt"));
}
#[test]
fn test_rand_id_unique() {
let id1 = rand_id();
let id2 = rand_id();
assert!(!id1.is_empty());
assert!(!id2.is_empty());
assert_eq!(id1.len(), 16);
}
#[test]
fn test_full_service_pipeline() {
let mut service = X86CompilerService::new();
service.start().unwrap();
let result = service
.compile("int main() { return 0; }", &CompileOptions::default())
.unwrap();
assert!(result.success);
let preprocessed = service
.preprocess("#define X 1", &CompileOptions::default())
.unwrap();
assert!(!preprocessed.text.is_empty());
let completions = service
.complete("int main() { ret", SourcePosition::new(1, 15))
.unwrap();
assert!(!completions.is_empty());
let formatted = service
.format("int main(){return 0;}", &FormattingOptions::default())
.unwrap();
assert!(!formatted.text.is_empty());
service.stop().unwrap();
}
#[test]
fn test_index_and_diagnostics_integration() {
let service = X86CompilerService::new();
service
.index_service
.index_file("main.c", "int main() { return 0; }", "c");
let symbols = service.index_service.query_symbols("main");
assert!(!symbols.is_empty());
let diags = service
.diagnostic_service
.analyze("file:///main.c", "int x\nint y;", false);
assert!(!diags.is_empty());
}
#[test]
fn test_build_and_daemon_integration() {
let mut config = CompilerServiceConfig::default();
config.daemon_enabled = true;
let mut service = X86CompilerService::with_config(config);
service.start().unwrap();
let opts = CompileOptions::default();
if let Some(ref daemon) = service.daemon {
let result = CompilationResult::new("test.c");
daemon.cache_compilation("test.c", &opts, result);
}
service.stop().unwrap();
}
}