This guide covers development setup, workflows, and contribution guidelines for FerrisScript.
# Clone the repository
git clone https://github.com/dev-parkins/FerrisScript.git
cd FerrisScript
# Build all crates
cargo build --workspace
# Run all tests (96 tests)
cargo test --workspace
# Build optimized release
cargo build --workspace --release
# Build specific crate
cargo build -p ferrisscript_compiler
cargo build -p ferrisscript_runtime
cargo build -p ferrisscript_godot_bind
# Run tests with output
cargo test --workspace -- --show-output
# Check code formatting
cargo fmt --all -- --check
# Run clippy linter (strict - treats warnings as errors)
cargo clippy --workspace --all-targets --all-features -- -D warnings
# Generate documentation
cargo doc --workspace --open
FerrisScript supports building for multiple platforms. The CI automatically builds native binaries for Linux, macOS, and Windows.
Native Build (Recommended):
# Build for your current platform (always works)
cargo build --workspace --release
Cross-Compilation Setup:
β οΈ Note: Cross-compilation from Windows requires platform-specific linkers and is complex to set up. For most development, use native builds or rely on CI for multi-platform builds.
If you need to verify target compatibility locally:
# Install target platform support
rustup target add x86_64-unknown-linux-gnu
rustup target add aarch64-unknown-linux-gnu
rustup target add x86_64-apple-darwin
rustup target add aarch64-apple-darwin
# Verify compilation (may fail at linking stage without proper linkers)
cargo build --workspace --release --target x86_64-unknown-linux-gnu
CI Builds: The GitHub Actions workflow automatically builds native binaries for:
x86_64-unknown-linux-gnu)x86_64-pc-windows-msvc)x86_64-apple-darwin)See .github/workflows/ci.yml for the full build matrix.
ferrisscript/
βββ .github/workflows/ # CI/CD automation
βββ crates/
β βββ compiler/ # Lexer, parser, type checker (69 tests)
β βββ runtime/ # AST interpreter (26 tests)
β βββ godot_bind/ # GDExtension integration (1 test)
βββ docs/
β βββ archive/ # Version-specific documentation
β βββ DEVELOPMENT.md # This file
βββ examples/ # 11 example .ferris scripts
βββ godot_test/ # Test Godot project
βββ ARCHITECTURE.md # Technical design documentation
βββ LICENSE # MIT License
βββ README.md # Main project documentation
βββ RELEASE_NOTES.md # v0.0.1 release information
| Crate | Purpose | Lines of Code | Tests |
|---|---|---|---|
ferrisscript_compiler |
Tokenization, parsing, type checking | ~1,500 | 69 |
ferrisscript_runtime |
AST interpretation, execution engine | ~1,200 | 26 |
ferrisscript_godot_bind |
Godot 4.x GDExtension integration | ~800 | 1 |
Check the roadmap in ARCHITECTURE.md or create an issue for new features.
Follow our branch naming convention to get the right PR template automatically:
# For bug fixes β Bug Fix PR template
git checkout -b bugfix/your-bug-description
# or
git checkout -b fix/parser-null-pointer
# For new features β Feature PR template
git checkout -b feature/your-feature-name
# or
git checkout -b feat/async-loading
# For documentation β Documentation PR template
git checkout -b docs/add-api-examples
# or
git checkout -b doc/update-readme
Why Branch Naming Matters:
See CONTRIBUTING.md for full details.
π See testing/README.md for comprehensive testing documentation
# Run all tests (843+ tests across all layers)
cargo test --workspace
# Run specific test types
cargo test -p ferrisscript_compiler # Unit tests (compiler)
cargo test -p ferrisscript_runtime # Unit tests (runtime)
ferris-test --all # Integration tests (.ferris scripts)
# Run specific test
cargo test test_compile_hello
# Check formatting and linting
cargo fmt --all
cargo clippy --workspace --all-targets --all-features -- -D warnings
Testing Layers:
Quick Links:
If you modified any .md files, always run documentation checks before committing:
# Option 1: VS Code Task (Recommended)
# Install dependencies (first time only)
npm install
# Check markdown formatting
npm run docs:lint
# Auto-fix formatting issues
npm run docs:fix
Why This Matters:
Common Issues Caught:
Link Checking: Broken links are automatically checked in CI. To check links manually:
# Check specific file
npx markdown-link-check your-file.md
# Check with config (quieter output)
npx markdown-link-check your-file.md --config .markdown-link-check.json -q
See ../scripts/README.md for full documentation linting guide.
# Feature additions
git commit -m "feat(compiler): add support for array types"
# Bug fixes
git commit -m "fix(runtime): handle division by zero correctly"
# Documentation
git commit -m "docs: update README with new examples"
# Tests
git commit -m "test(parser): add tests for field access"
# Maintenance
git commit -m "chore: update dependencies to latest versions"
# Push your branch (use the actual branch name you created)
git push origin bugfix/your-bug-description
# or
git push origin feature/your-feature-name
# or
git push origin docs/your-doc-update
# Then create a pull request on GitHub
What Happens Next:
<!-- ... --> comments)For GitHub Copilot Users: If youβre using GitHub Copilot to create PRs automatically, the branch naming convention ensures your automated PRs get the correct template applied!
lexer.rs, parser.rs)tests/ directories.ferris files compile correctly# All tests
cargo test --workspace
# Specific crate
cargo test -p ferrisscript_compiler
# Specific module
cargo test --lib lexer::tests
# With output (see println! statements)
cargo test -- --show-output --nocapture
# Single test
cargo test test_compile_hello -- --exact
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_your_feature() {
let result = your_function();
assert_eq!(result, expected_value);
}
}
FerrisScript maintains high test coverage to ensure code quality and catch regressions early. This section explains how to generate, view, and interpret coverage reports.
We use different coverage tools for different environments:
| Tool | Environment | Purpose | Platform |
|---|---|---|---|
| cargo-llvm-cov | Local Development | Interactive development, Windows-compatible | Windows, macOS, Linux |
| cargo-tarpaulin | CI/CD (GitHub Actions) | Automated coverage in CI pipelines | Linux only |
Why two tools?
See infrastructure/COVERAGE_SETUP_NOTES.md for the technical investigation that led to this approach.
# PowerShell (Windows)
.\scripts\coverage.ps1
# Bash (Linux/macOS)
./scripts/coverage.sh
These scripts will:
cargo-llvm-cov is installed (auto-install if missing)target/coverage/# First time setup
rustup component add llvm-tools-preview
cargo install cargo-llvm-cov
# Generate HTML report (human-readable)
cargo llvm-cov --workspace --html --output-dir target/coverage
# Generate LCOV report (for tools like VSCode extensions)
cargo llvm-cov --workspace --lcov --output-path target/coverage/lcov.info
# Generate both formats at once
cargo llvm-cov --workspace --html --output-dir target/coverage
cargo llvm-cov --workspace --lcov --output-path target/coverage/lcov.info
# Coverage for specific crate
cargo llvm-cov -p ferrisscript_compiler --html
# Coverage with test output visible
cargo llvm-cov --workspace --html -- --nocapture
HTML Report (most user-friendly):
# Windows
Invoke-Item target/coverage/html/index.html
# Linux
xdg-open target/coverage/html/index.html
# macOS
open target/coverage/html/index.html
The HTML report shows:
LCOV Report (for tool integration):
The target/coverage/lcov.info file can be used by:
Coverage reports show several metrics:
| Metric | Description | Target |
|---|---|---|
| Line Coverage | % of code lines executed by tests | 80%+ |
| Branch Coverage | % of conditional branches tested | 75%+ |
| Function Coverage | % of functions called by tests | 90%+ |
Color Coding in HTML Reports:
GitHub Actions Workflow:
Coverage is automatically generated on every push to main and in pull requests:
# .github/workflows/ci.yml
coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- name: Generate coverage
run: cargo tarpaulin --workspace --out Xml --output-dir coverage
- name: Upload to Codecov
uses: codecov/codecov-action@v4
Why Tarpaulin in CI?
ubuntu-latest (Linux)Viewing CI Coverage:
tarpaulin.toml:
[tool]
out = ["Html", "Lcov", "Stdout"]
output-dir = "target/coverage"
workspace = true
timeout = "5m"
follow-exec = true
count = true
fail-under = 0 # Currently no enforcement, will increase as coverage improves
[report]
branches = true
lines = true
Whatβs Excluded:
*/tests/*, *_test.rs)Current Status:
fail-under = 0)Target Goals:
For New Code:
Solution:
# Install llvm-tools-preview component
rustup component add llvm-tools-preview
# Install cargo-llvm-cov
cargo install cargo-llvm-cov
# Verify installation
cargo llvm-cov --version
Solution: Use cargo-llvm-cov instead of cargo-tarpaulin:
# Don't use tarpaulin on Windows
# cargo tarpaulin # β May fail with file locks
# Use llvm-cov instead
cargo llvm-cov --workspace --html # β
Works on Windows
Why this happens:
If you must use tarpaulin on Windows:
# Close VS Code and all IDEs
# Kill rust-analyzer process
Get-Process rust-analyzer -ErrorAction SilentlyContinue | Stop-Process
# Then run tarpaulin
cargo tarpaulin --workspace --skip-clean
Check:
cargo test --workspace should show test execution#[cfg(test)] modules or tests/ directoriesImprove coverage:
// Add tests for error paths
#[test]
#[should_panic(expected = "expected error message")]
fn test_error_handling() {
// Test code that should panic
}
// Test edge cases
#[test]
fn test_boundary_conditions() {
// Test empty input, maximum values, etc.
}
This is normal:
If differences are large (>5%):
cargo test --workspace--workspace flag#[cfg(windows)], etc.)Exclude Code from Coverage:
// Exclude specific function
#[cfg(not(tarpaulin_include))]
fn debug_only_function() {
// ...
}
// Exclude block
#[cfg(not(tarpaulin))]
{
// Debug-only code
}
Coverage for Specific Test:
# Run coverage for specific test
cargo llvm-cov --test integration_tests --html
Coverage with Clean Build:
# Clean previous coverage data
cargo llvm-cov clean
# Generate fresh coverage
cargo llvm-cov --workspace --html
β Do:
β Donβt:
.gitignore)rustfmt for formatting: cargo fmt --all/// Compiles FerrisScript source code into an AST.
///
/// # Arguments
/// * `source` - The FerrisScript source code as a string
///
/// # Returns
/// * `Ok(Program)` - Successfully compiled AST
/// * `Err(String)` - Compilation error message
///
/// # Examples
/// ```
/// let source = "fn _ready() { print(\"Hello\"); }";
/// let ast = compile(source)?;
/// ```
pub fn compile(source: &str) -> Result<Program, String> {
// ...
}
Rust uses an edition system (2015, 2018, 2021) to introduce backwards-incompatible changes without breaking existing code. Think of it like:
[package]
edition = "2021" # In all Cargo.toml files
Benefits of 2021 Edition:
[1, 2, 3] directlyTryFrom, TryInto available automaticallyBackwards Compatibility: Code compiled with edition 2021 works with libraries using 2015/2018. The edition only affects how your code is compiled, not the ABI.
Current: 2021 edition (latest)
Recommendation: β
Keep 2021 - itβs the latest stable edition
# Check for outdated dependencies
cargo outdated
# Update all dependencies to latest compatible versions
cargo update
# Update specific dependency
cargo update -p gdext
# Update to breaking versions (edit Cargo.toml first)
cargo build
# In crates/compiler/Cargo.toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
// Use dbg! macro for debugging
let result = dbg!(some_calculation());
// Use eprintln! for errors
eprintln!("Error: {}", error_message);
// In godot_bind/src/lib.rs
godot_print!("Debug info: {}", value);
godot_warn!("Warning: {}", warning);
godot_error!("Error: {}", error);
# Build with debug symbols
cargo build
# Run with debugger
rust-lldb target/debug/your_binary
# or
rust-gdb target/debug/your_binary
# Build with release optimizations
cargo build --release
# Profile with perf (Linux)
perf record target/release/your_binary
perf report
# Benchmark (if you add benches/)
cargo bench
We use Conventional Commits:
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: New featurefix: Bug fixdocs: Documentation onlystyle: Code style changes (formatting, no logic change)refactor: Code refactoringtest: Adding/updating testschore: Maintenance tasksci: CI/CD changesExamples:
feat(compiler): add array type support
fix(runtime): handle division by zero
docs: update ARCHITECTURE.md with new patterns
test(parser): add tests for function calls
chore: update gdext to 0.2.0
Before submitting a PR:
cargo test --workspace)cargo fmt --all)cargo clippy --workspace --all-targets --all-features -- -D warnings)npm run docs:lint)FerrisScript is licensed under the MIT License
MIT (to be added)