This guide provides detailed, actionable steps to build the Xplainit framework from scratch. Follow these steps in order for optimal results.
- Install Rust (latest stable) -
rustup install stable - Install C/C++ compiler (MSVC on Windows, GCC/Clang on Linux/Mac)
- Install Python 3.8+ with pip
- Install Node.js 16+ with npm
- Install Java JDK 11+
- Install Go 1.19+
- Install Git
- Install VS Code or preferred IDE
- Install cargo-edit:
cargo install cargo-edit - Install cargo-watch:
cargo install cargo-watch - Install cargo-flamegraph:
cargo install flamegraph - Install maturin:
pip install maturin - Install wasm-pack:
cargo install wasm-pack - Install cbindgen:
cargo install cbindgen
# Navigate to project directory
cd "c:\Users\siter\Desktop\Xplainit Framework"
# Initialize main Cargo workspace
cargo init --lib xplainit-core
cargo init --lib xplainit-python
cargo init --lib xplainit-node
cargo init --lib xplainit-c
cargo init --lib xplainit-java
cargo init --lib xplainit-go
cargo init --bin xplainit-cli
# Create documentation directories
mkdir docs\book
mkdir docs\api
mkdir docs\examples
# Create test directories
mkdir tests\fixtures
mkdir tests\cross-langCreate root Cargo.toml with workspace configuration:
[workspace]
members = [
"xplainit-core",
"xplainit-python",
"xplainit-node",
"xplainit-c",
"xplainit-java",
"xplainit-go",
"xplainit-cli",
]
resolver = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Xplainit Contributors"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/xplainit/xplainit"
homepage = "https://xplainit.io"
[workspace.dependencies]
# Core dependencies (shared across all crates)
tree-sitter = "0.20"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
anyhow = "1.0"
log = "0.4"
env_logger = "0.11"
# Tree-sitter language grammars
tree-sitter-c = "0.20"
tree-sitter-cpp = "0.20"
tree-sitter-python = "0.20"
tree-sitter-javascript = "0.20"
tree-sitter-rust = "0.20"
tree-sitter-java = "0.20"
tree-sitter-go = "0.20"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = trueCreate .github/workflows/ci.yml:
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
rust: [stable, beta]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Build
run: cargo build --all --verbose
- name: Run tests
run: cargo test --all --verbose
- name: Run clippy
run: cargo clippy --all -- -D warnings
- name: Check formatting
run: cargo fmt --all -- --check
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install tarpaulin
run: cargo install cargo-tarpaulin
- name: Generate coverage
run: cargo tarpaulin --all --out Xml
- name: Upload coverage
uses: codecov/codecov-action@v3Create .vscode/settings.json:
{
"rust-analyzer.checkOnSave.command": "clippy",
"rust-analyzer.cargo.allFeatures": true,
"editor.formatOnSave": true,
"editor.rulers": [100],
"files.exclude": {
"**/target": true,
"**/.git": true
}
}Create .editorconfig:
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.{yml,yaml,toml,json}]
indent_size = 2
[*.md]
trim_trailing_whitespace = falseEdit xplainit-core/Cargo.toml:
[package]
name = "xplainit-core"
version.workspace = true
edition.workspace = true
[dependencies]
tree-sitter = { workspace = true }
tree-sitter-c = { workspace = true }
tree-sitter-cpp = { workspace = true }
tree-sitter-python = { workspace = true }
tree-sitter-javascript = { workspace = true }
tree-sitter-rust = { workspace = true }
tree-sitter-java = { workspace = true }
tree-sitter-go = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }
log = { workspace = true }
[dev-dependencies]
env_logger = { workspace = true }Create the following directory structure in xplainit-core/src/:
src/
βββ lib.rs
βββ error.rs
βββ config.rs
βββ parser/
β βββ mod.rs
β βββ tree_sitter.rs
β βββ ast.rs
β βββ normalizer.rs
βββ analyzer/
β βββ mod.rs
β βββ flow.rs
β βββ scope.rs
β βββ symbols.rs
βββ explainer/
β βββ mod.rs
β βββ templates.rs
β βββ generator.rs
β βββ rules/
β βββ mod.rs
β βββ loops.rs
β βββ conditionals.rs
β βββ functions.rs
β βββ variables.rs
βββ executor/
β βββ mod.rs
β βββ stepper.rs
β βββ state.rs
βββ output/
βββ mod.rs
βββ console.rs
βββ json.rs
βββ html.rs
xplainit-core/src/error.rs:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum XplainitError {
#[error("Parse error: {0}")]
ParseError(String),
#[error("Analysis error: {0}")]
AnalysisError(String),
#[error("Unsupported language: {0}")]
UnsupportedLanguage(String),
#[error("Invalid configuration: {0}")]
ConfigError(String),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Tree-sitter error: {0}")]
TreeSitterError(String),
#[error("Unknown error: {0}")]
Unknown(String),
}
pub type Result<T> = std::result::Result<T, XplainitError>;xplainit-core/src/config.rs:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Language {
C,
Cpp,
Python,
JavaScript,
Rust,
Java,
Go,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Verbosity {
Brief, // Minimal explanations
Normal, // Standard explanations
Detailed, // Comprehensive explanations
Debug, // Include internal details
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OutputFormat {
Console,
Json,
Html,
Markdown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub language: Language,
pub verbosity: Verbosity,
pub output_format: OutputFormat,
pub show_line_numbers: bool,
pub show_source_code: bool,
pub color_output: bool,
pub max_depth: usize,
pub track_variables: bool,
pub track_function_calls: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
language: Language::Python,
verbosity: Verbosity::Normal,
output_format: OutputFormat::Console,
show_line_numbers: true,
show_source_code: true,
color_output: true,
max_depth: 100,
track_variables: true,
track_function_calls: true,
}
}
}
impl Config {
pub fn new(language: Language) -> Self {
Self {
language,
..Default::default()
}
}
pub fn with_verbosity(mut self, verbosity: Verbosity) -> Self {
self.verbosity = verbosity;
self
}
pub fn with_output_format(mut self, format: OutputFormat) -> Self {
self.output_format = format;
self
}
}xplainit-core/src/parser/ast.rs:
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceLocation {
pub line: usize,
pub column: usize,
pub offset: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceRange {
pub start: SourceLocation,
pub end: SourceLocation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AstNode {
Program {
body: Vec<AstNode>,
range: SourceRange,
},
FunctionDeclaration {
name: String,
parameters: Vec<Parameter>,
body: Box<AstNode>,
return_type: Option<String>,
range: SourceRange,
},
VariableDeclaration {
name: String,
value: Option<Box<AstNode>>,
var_type: Option<String>,
is_const: bool,
range: SourceRange,
},
Assignment {
target: String,
value: Box<AstNode>,
range: SourceRange,
},
FunctionCall {
name: String,
arguments: Vec<AstNode>,
range: SourceRange,
},
IfStatement {
condition: Box<AstNode>,
then_branch: Box<AstNode>,
else_branch: Option<Box<AstNode>>,
range: SourceRange,
},
WhileLoop {
condition: Box<AstNode>,
body: Box<AstNode>,
range: SourceRange,
},
ForLoop {
init: Option<Box<AstNode>>,
condition: Option<Box<AstNode>>,
update: Option<Box<AstNode>>,
body: Box<AstNode>,
range: SourceRange,
},
Return {
value: Option<Box<AstNode>>,
range: SourceRange,
},
BinaryExpression {
operator: String,
left: Box<AstNode>,
right: Box<AstNode>,
range: SourceRange,
},
UnaryExpression {
operator: String,
operand: Box<AstNode>,
range: SourceRange,
},
Literal {
value: LiteralValue,
range: SourceRange,
},
Identifier {
name: String,
range: SourceRange,
},
Block {
statements: Vec<AstNode>,
range: SourceRange,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Parameter {
pub name: String,
pub param_type: Option<String>,
pub default_value: Option<Box<AstNode>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LiteralValue {
Integer(i64),
Float(f64),
String(String),
Boolean(bool),
Null,
}
impl AstNode {
pub fn range(&self) -> &SourceRange {
match self {
AstNode::Program { range, .. } => range,
AstNode::FunctionDeclaration { range, .. } => range,
AstNode::VariableDeclaration { range, .. } => range,
AstNode::Assignment { range, .. } => range,
AstNode::FunctionCall { range, .. } => range,
AstNode::IfStatement { range, .. } => range,
AstNode::WhileLoop { range, .. } => range,
AstNode::ForLoop { range, .. } => range,
AstNode::Return { range, .. } => range,
AstNode::BinaryExpression { range, .. } => range,
AstNode::UnaryExpression { range, .. } => range,
AstNode::Literal { range, .. } => range,
AstNode::Identifier { range, .. } => range,
AstNode::Block { range, .. } => range,
}
}
}xplainit-core/src/parser/mod.rs:
mod ast;
mod tree_sitter;
mod normalizer;
pub use ast::*;
pub use tree_sitter::TreeSitterParser;
pub use normalizer::AstNormalizer;
use crate::{config::Language, error::Result};
pub trait Parser {
fn parse(&self, source_code: &str) -> Result<AstNode>;
fn language(&self) -> Language;
}
pub fn create_parser(language: Language) -> Box<dyn Parser> {
Box::new(TreeSitterParser::new(language))
}xplainit-core/src/lib.rs:
mod error;
mod config;
mod parser;
mod analyzer;
mod explainer;
mod executor;
mod output;
pub use error::{XplainitError, Result};
pub use config::{Config, Language, Verbosity, OutputFormat};
pub use parser::{AstNode, Parser};
use parser::create_parser;
/// Main Explainer struct
pub struct Explainer {
config: Config,
parser: Box<dyn Parser>,
}
impl Explainer {
/// Create a new Explainer with the given configuration
pub fn new(config: Config) -> Self {
let parser = create_parser(config.language);
Self { config, parser }
}
/// Create a new Explainer for a specific language with default config
pub fn with_language(language: Language) -> Self {
Self::new(Config::new(language))
}
/// Explain the given source code
pub fn explain(&self, source_code: &str) -> Result<Vec<String>> {
// Parse the code
let ast = self.parser.parse(source_code)?;
// Analyze the AST
// (To be implemented)
// Generate explanations
// (To be implemented)
// For now, return a placeholder
Ok(vec!["Explanation system in development".to_string()])
}
/// Get the current configuration
pub fn config(&self) -> &Config {
&self.config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_explainer_creation() {
let explainer = Explainer::with_language(Language::Python);
assert_eq!(explainer.config().language, Language::Python);
}
}xplainit-core/src/parser/tree_sitter.rs:
use tree_sitter::{Parser as TSParser, Language as TSLanguage, Tree, Node};
use crate::{config::Language, error::{Result, XplainitError}};
use super::{AstNode, Parser};
extern "C" {
fn tree_sitter_c() -> TSLanguage;
fn tree_sitter_cpp() -> TSLanguage;
fn tree_sitter_python() -> TSLanguage;
fn tree_sitter_javascript() -> TSLanguage;
fn tree_sitter_rust() -> TSLanguage;
fn tree_sitter_java() -> TSLanguage;
fn tree_sitter_go() -> TSLanguage;
}
pub struct TreeSitterParser {
language: Language,
parser: TSParser,
}
impl TreeSitterParser {
pub fn new(language: Language) -> Self {
let mut parser = TSParser::new();
let ts_language = match language {
Language::C => unsafe { tree_sitter_c() },
Language::Cpp => unsafe { tree_sitter_cpp() },
Language::Python => unsafe { tree_sitter_python() },
Language::JavaScript => unsafe { tree_sitter_javascript() },
Language::Rust => unsafe { tree_sitter_rust() },
Language::Java => unsafe { tree_sitter_java() },
Language::Go => unsafe { tree_sitter_go() },
};
parser.set_language(ts_language)
.expect("Failed to set language");
Self { language, parser }
}
fn convert_tree_to_ast(&self, tree: Tree, source: &str) -> Result<AstNode> {
let root = tree.root_node();
self.convert_node(root, source)
}
fn convert_node(&self, node: Node, source: &str) -> Result<AstNode> {
// This will be language-specific conversion
// For now, return a placeholder
todo!("Implement AST conversion for each language")
}
}
impl Parser for TreeSitterParser {
fn parse(&self, source_code: &str) -> Result<AstNode> {
let tree = self.parser
.parse(source_code, None)
.ok_or_else(|| XplainitError::ParseError("Failed to parse code".to_string()))?;
if tree.root_node().has_error() {
return Err(XplainitError::ParseError(
"Syntax error in source code".to_string()
));
}
self.convert_tree_to_ast(tree, source_code)
}
fn language(&self) -> Language {
self.language
}
}After completing Phase 1-3 setup:
- Phase 4: Implement AST normalizers for each language
- Phase 5: Build the analyzer engine (scope, flow, symbols)
- Phase 6: Create the explanation template system
- Phase 7: Implement execution simulator
- Phase 8: Build output formatters
- Phase 9: Create language bindings (Python, Node, etc.)
- Phase 10: Develop CLI tool
- Phase 11: Comprehensive testing
- Phase 12: Documentation and release
# 1. Pull latest changes
git pull origin develop
# 2. Create feature branch
git checkout -b feature/your-feature-name
# 3. Make changes and test frequently
cargo watch -x "test --lib"
# 4. Run full test suite before commit
cargo test --all
cargo clippy --all -- -D warnings
cargo fmt --all
# 5. Commit and push
git add .
git commit -m "feat: your feature description"
git push origin feature/your-feature-name
# 6. Create pull request on GitHub# Run all tests
cargo test --all --verbose
# Run tests for specific crate
cargo test -p xplainit-core
# Run with coverage
cargo tarpaulin --all
# Run benchmarks
cargo bench
# Check for memory leaks (on Unix)
valgrind --leak-check=full cargo test- Test-Driven Development: Write tests before implementation
- Incremental Development: Build one feature at a time
- Documentation First: Document APIs before implementing
- Performance Awareness: Profile regularly
- Error Handling: Every function returns Result
- Type Safety: Leverage Rust's type system
- Modularity: Keep modules loosely coupled
- Consistency: Follow Rust conventions
- Rust Book: https://doc.rust-lang.org/book/
- Tree-sitter Docs: https://tree-sitter.github.io/tree-sitter/
- Parser Design: "Crafting Interpreters" by Bob Nystrom
- Rust API Guidelines: https://rust-lang.github.io/api-guidelines/
Ready to Start Building! π
Follow this guide step-by-step, and you'll have a production-ready framework that meets all your requirements.