This document describes the development workflow, code style guidelines, and tooling for the Polang project.
The project uses Docker for a consistent development environment:
# Start the development container
docker/docker_run.sh
# Run commands inside the container
docker exec polang <command> [options]
# Build the Docker image locally
docker/docker_build.shThe container includes all required tools:
- GCC and Clang 20 compilers
- CMake, Bison, Flex
- LLVM 20 with MLIR
- clang-format, clang-tidy, clangd
- lcov, Python 3
Generate compile_commands.json for IDE support:
cmake -S. -Bbuild -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_PREFIX_PATH="/usr/lib/llvm-20"Run clangd with path mapping (inside container):
clangd --path-mappings=$(pwd)=/workspace/polang --enable-config- Make variables and functions
constwhenever possible - Mark functions
noexceptwhen they don't throw exceptions - Prefer
constreferences for function parameters that aren't modified - Add
[[nodiscard]]to functions whose return value should not be ignored - Use braces around all control flow statement bodies
Avoid raw pointers for ownership. Use smart pointers (std::unique_ptr, std::shared_ptr) to manage dynamically allocated memory. This prevents memory leaks and makes ownership semantics explicit.
The AST uses std::unique_ptr for automatic memory management:
- Use
std::unique_ptrfor exclusive ownership of AST nodes - Use
std::make_uniquefor creating new nodes (exception-safe) - Use
.get()to obtain raw pointers for non-owning access (e.g., in visitors) - Use
std::movewhen transferring ownership between containers - Iterate with
const auto&over vectors of unique_ptr:for (const auto& stmt : block->statements) { stmt->accept(visitor); }
- Access members with
->through unique_ptr:auto* varDecl = dynamic_cast<NVariableDeclaration*>(stmt.get()); std::string name = varDecl->id->name; // id is unique_ptr
When raw pointers are acceptable:
- Interfacing with C APIs or libraries that require raw pointers
For non-owning access, prefer references (const T& or T&) over raw pointers. References cannot be null and make non-owning semantics explicit.
Avoid:
new/deletefor memory management (usestd::make_uniqueinstead)- Returning raw pointers that transfer ownership (return
std::unique_ptr) - Storing raw pointers in containers (use
std::vector<std::unique_ptr<T>>)
| Element | Case | Example |
|---|---|---|
| Classes/Structs | CamelCase |
TypeChecker, NBlock |
| Enums | CamelCase |
ErrorSeverity |
| Enum constants | CamelCase |
ImportKind::Module |
| Functions/Methods | lowerCamelCase |
checkTypes(), getError() |
| Variables | lowerCamelCase |
errorList, funcName |
| Parameters | lowerCamelCase |
emitTypeVars, hasMore |
| Members | lowerCamelCase |
inferredType, context |
| Global/Static constants | UPPER_CASE |
TypeNames::INT |
| Namespaces | lowercase |
polang |
Configuration is defined in .clang-tidy.
Always format C/C++ files before committing (not .l or .y files):
# Format all source files
./scripts/run-clang-format.sh
# Check formatting without modifying (CI mode)
./scripts/run-clang-format.sh --check
# Format a specific file
clang-format -i <path/to/file.cpp>Configuration is defined in .clang-format.
Run static analysis to catch issues:
# Run on all source files
./scripts/run-clang-tidy.sh
# Run with auto-fix (use with caution)
./scripts/run-clang-tidy.sh --fix
# Run on specific files
./scripts/run-clang-tidy.sh parser/src/ast_printer.cppConfiguration is defined in .clang-tidy.
# Run all tests
ctest --test-dir build --output-on-failure
# Run specific test category
ctest --test-dir build -R "TypeCheck"
# Run lit tests only
python3 /usr/lib/llvm-20/build/utils/lit/lit.py -v build/tests/lit
# Run specific lit test category
python3 /usr/lib/llvm-20/build/utils/lit/lit.py -v build/tests/lit/MLIRAfter modifying the compiler, verify all examples still work:
for f in example/*.po; do echo "=== $(basename $f) ==="; ./build/bin/PolangRepl "$f"; doneExpected outputs:
| Example | Output |
|---|---|
closures.po |
21 : i64 |
conditionals.po |
10 : i64 |
factorial.po |
120 : i64 |
fibonacci.po |
5 : i64 |
functions.po |
25 : i64 |
hello.po |
7 : i64 |
let_expressions.po |
16 : i64 |
types.po |
84 : i64 |
variables.po |
30 : i64 |
Lit tests use FileCheck to verify compiler output:
; RUN: %polang_compiler --dump-ast %s | %FileCheck %s
; Test integer literal AST
; CHECK: NBlock
; CHECK-NEXT: `-NExpressionStatement
; CHECK-NEXT: `-NInteger 42
42
Available substitutions:
| Substitution | Description |
|---|---|
%polang_compiler |
Path to PolangCompiler |
%polang_repl |
Path to PolangRepl |
%FileCheck |
Path to FileCheck |
%not |
Inverts exit code (for error tests) |
%s |
Current test file path |
FileCheck patterns:
| Pattern | Description |
|---|---|
; CHECK: |
Match full line (can skip lines) |
; CHECK-NEXT: |
Match immediately following line |
%{{[0-9]+}} |
Match SSA values like %0, %1 |
{{.*}} |
Match any characters |
{{^}} |
Match start of line |
Note: FileCheck is configured with --match-full-lines. Use {{.*}} for partial matching.
Best practices:
- Prefer
CHECK-NEXToverCHECKfor consecutive lines - Use exact full-line patterns when possible
- Use
{{.*}}pattern{{.*}}for partial matching
See doc/Testing.md for detailed test documentation.
When modifying code, update the relevant documentation:
| Change Type | Documentation |
|---|---|
| Language syntax | doc/Syntax.md |
| MLIR pipeline | doc/Architecture.md |
| Type system | doc/TypeSystem.md |
| Tests | doc/Testing.md |
| Build system | doc/Building.md |
| Architecture | doc/Architecture.md |
When adding features or fixing bugs, add corresponding lit tests:
| Test Type | Directory | Flag |
|---|---|---|
| AST dump | tests/lit/AST/ |
--dump-ast |
| MLIR output | tests/lit/MLIR/ |
--emit-mlir |
| LLVM IR | tests/lit/LLVMIR/ |
(default) |
| Execution | tests/lit/Execution/ |
REPL |
| Errors | tests/lit/Errors/ |
%not |
GitHub Actions runs on every push and pull request:
- format-check: Verifies clang-format compliance
- lint: Runs clang-tidy static analysis
- build-and-test: GCC/Clang × Debug/Release matrix
- sanitizers: AddressSanitizer and UndefinedBehaviorSanitizer
- coverage: Code coverage uploaded to Codecov
See doc/Testing.md for detailed CI documentation.
- Make changes to source code
- Format with
./scripts/run-clang-format.sh - Lint with
./scripts/run-clang-tidy.sh - Build with
cmake --build build - Test with
ctest --test-dir build --output-on-failure - Verify examples work correctly
- Update documentation as needed
- Commit and push