Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MiniPy

A Python-like interpreter written in C for learning how interpreters work.

Source Code → Lexer → Tokens → Parser → AST → Compiler → Bytecode → VM → Result

Features

  • Lexer - Tokenization with Python-style indentation (INDENT/DEDENT)
  • Parser - Recursive descent building AST
  • Two execution modes:
    • Tree-walking interpreter (direct AST execution)
    • Bytecode compiler + stack-based VM (~5x faster)
  • Memory management - Reference counting + mark-and-sweep GC
  • Pool allocator - Arena/pool/block hierarchy for small objects
  • Classes - Simple classes with __init__ and methods
  • Generators - yield with state preservation
  • Async/Await - Coroutines with gather() for concurrency
  • Bytecode serialization - Save/load compiled .pyc files

Build

make

Usage

./minipy script.py           # Tree-walking interpreter
./minipy --vm script.py      # Bytecode VM (faster)
./minipy                     # Interactive REPL

CLI Flags

Flag Description
--vm Use bytecode VM instead of tree-walking
--disasm Show disassembled bytecode
--bc Bytecode REPL (shows bytecode as you type)
--profile Profile opcode execution
--compile Compile to .pyc file

Language Examples

Basics

x = 10
y = 20
print(x + y)      # 30

if x > 5:
    print("big")
else:
    print("small")

Functions

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

print(factorial(5))  # 120

Lists

nums = [1, 2, 3, 4, 5]
print(len(nums))     # 5
print(nums[2])       # 3

for n in nums:
    print(n)

Classes

class Counter:
    def __init__(self, start):
        self.value = start
    
    def inc(self):
        self.value = self.value + 1
        return self.value

c = Counter(10)
print(c.inc())  # 11
print(c.inc())  # 12

Generators

def countdown(n):
    while n > 0:
        yield n
        n = n - 1

gen = countdown(3)
print(next(gen))  # 3
print(next(gen))  # 2
print(next(gen))  # 1

Async/Await

async def task(name, count):
    i = 0
    while i < count:
        print(name)
        i = i + 1
        yield

results = gather(task("A", 2), task("B", 2))
# Output: A, B, A, B (interleaved)

Bytecode

Compile and inspect bytecode:

./minipy --compile script.py    # Creates script.pyc
./minipy --disasm script.pyc    # Show bytecode
./minipy script.pyc             # Run compiled file

Example bytecode output:

== script.pyc ==
0000    0 BC_CONST            0 '10'
0002    | BC_STORE_NAME       0 (x)
0004    | BC_CONST            1 '20'
0006    | BC_STORE_NAME       1 (y)
0008    | BC_LOAD_NAME        2 (print)
0010    | BC_LOAD_NAME        0 (x)
0012    | BC_LOAD_NAME        1 (y)
0014    | BC_ADD
0015    | BC_CALL             1
0017    | BC_POP

Opcodes

Opcode Description
BC_CONST Push constant onto stack
BC_LOAD_NAME Load variable by name
BC_STORE_NAME Store top of stack to variable
BC_ADD/SUB/MUL/DIV Arithmetic operations
BC_CALL Call function with N arguments
BC_RETURN Return from function
BC_JUMP Unconditional jump
BC_JUMP_IF_FALSE Conditional jump
BC_YIELD Yield from generator
BC_AWAIT Await coroutine

Profiler

./minipy --profile script.py

Output:

=== Profiler Report ===
Total opcodes: 150234
Execution time: 0.023s
Opcodes/second: 6.5M

Top opcodes:
  BC_LOAD_FAST    45123  (30.0%)
  BC_ADD          22456  (14.9%)
  BC_JUMP_IF_FALSE 18234  (12.1%)
  ...

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         Source Code                             │
│                    "x = 10 + 20\nprint(x)"                      │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                           LEXER                                 │
│                         (lexer.c)                               │
│   Converts source text into tokens                              │
│   Handles Python-style INDENT/DEDENT                            │
└─────────────────────────────────────────────────────────────────┘
                              │
          [TOK_NAME, TOK_EQ, TOK_INT, TOK_PLUS, TOK_INT, ...]
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                          PARSER                                 │
│                        (parser.c)                               │
│   Recursive descent parser                                      │
│   Builds Abstract Syntax Tree                                   │
└─────────────────────────────────────────────────────────────────┘
                              │
                     AST_BLOCK
                        ├── AST_ASSIGN(x, AST_BINOP(+, 10, 20))
                        └── AST_CALL(print, [AST_VAR(x)])
                              │
              ┌───────────────┴───────────────┐
              ▼                               ▼
┌──────────────────────────┐    ┌──────────────────────────┐
│    TREE-WALK INTERP      │    │       COMPILER           │
│       (interp.c)         │    │      (compiler.c)        │
│                          │    │                          │
│  Directly executes AST   │    │  AST → Bytecode          │
│  Slower but simpler      │    │                          │
└──────────────────────────┘    └──────────────────────────┘
              │                               │
              ▼                               ▼
           Result              ┌──────────────────────────┐
                               │      BYTECODE VM         │
                               │         (vm.c)           │
                               │                          │
                               │  Stack-based execution   │
                               │  ~5x faster              │
                               └──────────────────────────┘
                                              │
                                              ▼
                                           Result

Memory Management

Reference Counting

Every object tracks how many references point to it:

obj->refcount++;  // When referenced
obj->refcount--;  // When unreferenced
if (obj->refcount == 0) free(obj);

Mark-and-Sweep GC

Handles reference cycles:

1. MARK:  Start from roots, mark all reachable objects
2. SWEEP: Free all unmarked objects

Pool Allocator

Efficient allocation for small objects:

Arena (256KB)
  └── Pool (4KB, one size class)
        └── Block (8-512 bytes)

.pyc File Format

┌────────────────────────────────────┐
│  Magic "MPC1" (4 bytes)            │  ← File format identifier
├────────────────────────────────────┤
│  Code length (4 bytes)             │
│  Bytecode (N bytes)                │  ← Raw instructions
├────────────────────────────────────┤
│  Constant count (4 bytes)          │
│  Constants (type + data each)      │  ← Numbers, strings
├────────────────────────────────────┤
│  Name count (4 bytes)              │
│  Names (length + string each)      │  ← Variable names
└────────────────────────────────────┘

Project Structure

src/
├── main.c       # Entry point, REPL, CLI handling
├── lexer.c/h    # Tokenization
├── parser.c/h   # AST construction
├── ast.c/h      # AST node definitions
├── interp.c/h   # Tree-walking interpreter
├── compiler.c/h # AST to bytecode compiler
├── bytecode.c/h # Bytecode chunk, disassembler, serialization
├── vm.c/h       # Stack-based virtual machine
├── object.c/h   # Object model (int, string, list, func, etc.)
├── pool.c/h     # Pool allocator
examples/
├── test.py      # Basic test
├── class_test.py
├── gen_test.py
├── async_test.py
└── ...

Built-in Functions

Function Description
print(x) Print value
len(x) Length of list/string
range(n) Generate 0 to n-1
int(x) Convert to integer
str(x) Convert to string
type(x) Get type name
next(gen) Get next value from generator
run(coro) Run single coroutine
gather(...) Run coroutines concurrently
gc_collect() Force garbage collection
gc_count() Count tracked objects

Learning Resources

This project demonstrates concepts from:

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages