Skip to content

Latest commit

Β 

History

History
797 lines (646 loc) Β· 18.4 KB

File metadata and controls

797 lines (646 loc) Β· 18.4 KB

Xplainit Framework - Step-by-Step Implementation Guide

🎯 Overview

This guide provides detailed, actionable steps to build the Xplainit framework from scratch. Follow these steps in order for optimal results.


πŸ“‹ Prerequisites Checklist

Development Environment

  • 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

Tools

  • 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

πŸ—οΈ Phase 1: Foundation Setup

Step 1.1: Initialize Project Structure

# 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-lang

Step 1.2: Create Workspace Cargo.toml

Create 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 = true

Step 1.3: Setup CI/CD Pipeline

Create .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@v3

Step 1.4: Setup Development Configuration

Create .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 = false

πŸ”§ Phase 2: Core Engine Development

Step 2.1: Define Core Module Structure

Edit 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 }

Step 2.2: Create Core Module Files

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

Step 2.3: Implement Error Handling

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>;

Step 2.4: Implement Configuration System

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
    }
}

Step 2.5: Implement AST Types

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,
        }
    }
}

Step 2.6: Implement Parser Module

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))
}

Step 2.7: Implement Main Library Interface

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);
    }
}

🎯 Phase 3: Parser Implementation

Step 3.1: Implement Tree-sitter Integration

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
    }
}

πŸ“ Next Steps Summary

After completing Phase 1-3 setup:

  1. Phase 4: Implement AST normalizers for each language
  2. Phase 5: Build the analyzer engine (scope, flow, symbols)
  3. Phase 6: Create the explanation template system
  4. Phase 7: Implement execution simulator
  5. Phase 8: Build output formatters
  6. Phase 9: Create language bindings (Python, Node, etc.)
  7. Phase 10: Develop CLI tool
  8. Phase 11: Comprehensive testing
  9. Phase 12: Documentation and release

πŸ”„ Development Workflow

Daily Development Loop

# 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

Testing Commands

# 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

πŸ“š Key Implementation Principles

  1. Test-Driven Development: Write tests before implementation
  2. Incremental Development: Build one feature at a time
  3. Documentation First: Document APIs before implementing
  4. Performance Awareness: Profile regularly
  5. Error Handling: Every function returns Result
  6. Type Safety: Leverage Rust's type system
  7. Modularity: Keep modules loosely coupled
  8. Consistency: Follow Rust conventions

πŸŽ“ Learning Resources During Development


Ready to Start Building! πŸš€

Follow this guide step-by-step, and you'll have a production-ready framework that meets all your requirements.