Skip to content

Repository files navigation

Execore

ISO C++23 CMake 3.28+ Tests Passing Sanitizers Clean Compliance

Execore is an interpreted programming language built in ISO C++23. It combines indentation-based block syntax with explicit static types (int, float, char, str, list), first-class lexical closures, and dynamic method dispatch.

The design goal was simple: get the readable feel of Python while keeping unambiguous static typing and fast, zero-allocation execution paths.


Quick Start

Prerequisites

  • Clang 19+ or GCC 15+ (ISO C++23 support required)
  • CMake 3.28 or newer
  • Ninja build system
  • Mold or LLD linker (automatically detected)

Build and Run

# Configure and compile with Clang and Ninja
cmake --preset dev-clang
cmake --build --preset dev

# Run an example program
./build/dev-clang/execore examples/algorithms/factorial.exe

Run Tests

# Run the complete test matrix (unit, BDD, property, fuzz regression)
ctest --preset test-all

# Run under AddressSanitizer and UndefinedBehaviorSanitizer
cmake --preset asan-ubsan
cmake --build --preset asan-ubsan
ctest --preset test-asan

Language at a Glance

1. Functions, Recursion, and Control Flow

Blocks use 4-space indentation without braces. Variables must be declared with their static type before assignment or use.

def factorial(n)
    if n <= 1
        return 1
    return n * factorial(n - 1)

int number = 6
int result = factorial(number)
print "Factorial of", number, "is:", result

2. Lists and Dynamic Sequences

Lists store dynamic values with subscripting, negative indices, and slicing.

list items
items.append(10)
items.append(20)
items.append(30)

# Slicing creates a new list with elements from index 1 up to 3
list sub = items[1:3]
print "Sublist:", sub

# Iteration over sequences
for val in items
    print "Value:", val

3. Strings and Character Conversion

Strings support sequence slicing, repetition, and builtins like ord() and chr().

str greeting = "Execore"
print greeting[0:3]       # Prints: Exe
print "-" * 15            # Prints: ---------------

char letter = 'A'
int ascii_code = ord(letter)
print "ASCII:", ascii_code, "Next:", chr(ascii_code + 1)

Type System and Memory Layout

Type C++ Runtime Representation Storage Location Key Behaviors
int int64_t Inline inside std::variant 64-bit signed arithmetic, bitwise operators, modulo
float double Inline inside std::variant IEEE 754 double precision floating point
char char Inline inside std::variant Single-byte character
str std::shared_ptr<StringObject> Refcounted heap object Slicing, repetition with *, concatenation with +, .len()
list std::shared_ptr<ListObject> Refcounted heap object Slicing, .append(), .insert(), .remove(), .len()
none std::monostate Inline inside std::variant Default return value and null representation

Primitive types live directly inside a cacheline-friendly tagged variant. They never hit the heap during calculation or comparison.


Pipeline Architecture

 Source File (.exe)
         │
         ▼
 ┌───────────────┐
 │ SourceManager │  Zero-copy file buffer ownership and line offset indexing
 └───────┬───────┘
         │
         ▼
 ┌───────────────┐
 │ Lexer (SoA)   │  Off-side rule indentation tracking and synthetic INDENT/DEDENT
 └───────┬───────┘
         │
         ▼
 ┌───────────────┐
 │ Parser        │  Precedence climbing with C++23 std::expected monadic results
 └───────┬───────┘
         │
         ▼
 ┌───────────────┐
 │ AST & Visitor │  Arena bump-pointer allocations and polymorphic AST hierarchy
 └───────┬───────┘
         │
         ▼
 ┌───────────────┐
 │ Semantics     │  3-tier symbol table with built-in shadowing and scope checks
 └───────┬───────┘
         │
         ▼
 ┌───────────────┐
 │ Interpreter   │  Tree-walk execution, cycle-broken lexical closures
 └───────────────┘

Key Engineering Details

  • Data-Oriented Token Stream (TokenBufferSoA): Tokens can be stored as parallel contiguous vectors (kinds, spans, lexemes) instead of an array of heavy structs. This gives 100% L1 cache density during token lookahead scans.
  • Monadic Error Handling: The parser provides parse_program_monadic() returning Result<std::unique_ptr<Program>, ParseError>. This wraps C++23 std::expected and chains operations with .transform() and .and_then().
  • PMR Arena Allocation: MonotonicArenaResource inherits from std::pmr::memory_resource so standard library containers and AST nodes allocate from continuous monotonic memory blocks without global malloc contention.
  • Deterministic Cycle Breaking: Lexical closures hold references to their parent environment. To prevent memory leaks when a function is stored inside its own environment, Interpreter tracks active environments through weak references and clears bindings explicitly on teardown.

Performance and Benchmarks

We test performance using Google Benchmark across three microbenchmarks in benchmarks/micro/.

Run Benchmarks

# Build and run the entire benchmark suite
./benchmarks/run_benchmarks.sh

Measurements on AMD Ryzen / Linux x86_64:

  • Lexer Throughput: >100 MiB/s (~4.8 Million tokens/second).
  • Parser Throughput: >32 MiB/s (~870,000 expressions/second).
  • Interpreter Loop Latency: ~3.4 Million loop iterations/second.

Profile-Guided Optimization (PGO)

Execore includes an automated PGO script that compiles an instrumented build, trains it on all sample workloads, merges the profile data using llvm-profdata, and links an optimized binary with ThinLTO:

./tools/scripts/pgo_build.sh

Testing the PGO release binary shows a 4x reduction in total test execution time compared to debug builds (0.71s vs 3.02s).


5-Tier Verification Matrix

Every change to Execore passes five layers of testing:

  1. Unit Tests (tests/unit/): Granular component checks for lexer, parser, value variants, and interpreter behavior.
  2. BDD Scenarios (tests/unit/test_bdd_scenarios.cpp): End-to-end user workflows using GIVEN/WHEN/THEN covering off-side indentation, operator precedence binding, and recursive closures.
  3. Property-Based Testing (tests/property/test_properties.cpp): 2,000 randomized property tests verifying arithmetic commutativity, associativity, string slicing round-trips, and boolean truthiness.
  4. Continuous Fuzzing (tests/fuzz/): LibFuzzer harnesses for lexer and parser inputs, plus a standalone runner that executes 10,000 pseudo-random fuzzing rounds with zero crashes or leaks.
  5. Mutation Testing (tests/mutation/mutation_check.py): Injects AST and operator mutations across core source files to verify that the test suite kills more than 80% of mutants.

Command Line Usage

execore [options] <source-file>

Options:
  -h, --help             Display this help message and exit
  -v, --version          Display version information and exit
  -t, --tab-size <N>     Configure indentation tab size (default: 4)
  --dump-ast             Dump textual AST representation to stdout
  --emit-dot <file>      Export AST as a Graphviz DOT diagram to <file>
  --check-only           Perform lexical, syntax, and semantic checks without executing
  --no-color             Disable colorized diagnostic messages

Exporting an AST visualization to PNG:

./build/dev-clang/execore --emit-dot ast.dot examples/algorithms/factorial.exe
dot -Tpng ast.dot -o ast.png

Documentation and Records


License

MIT License. See LICENSE for details.

About

Modern programming language engine built in ISO C++23: off-side indentation, static typing, PMR arena allocation, and DoD token streaming.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages