Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MiniPython Interpreter

A tree-walking interpreter for a small Python-like language, written from scratch in pure C.

Language Standard Build Dependencies License Type


Overview

MiniPython is an interpreter for a small imperative subset of Python, implemented from scratch in C with zero external dependencies. It reads source text and executes it immediately through the three canonical phases of a real language runtime: a hand-written lexer tokenizes the input, a recursive-descent parser builds an abstract syntax tree (AST), and a tree-walking evaluator executes that tree. An interactive REPL ties everything together, accumulating lines and running them when the user submits a blank line.

The project is deliberately small enough to read end to end, yet its architecture mirrors how production interpreters are actually structured. It is a compact, honest demonstration of the core ideas behind language implementation — lexing, parsing, AST construction, operator precedence, and evaluation.

This was a university team project for a Programming Languages course. See Team & Credits.


Features

Everything below is implemented and working in the current codebase:

  • Integer variables & assignmentx = 10
  • Arithmetic operators+ - * / %, with division- and modulo-by-zero guarded at evaluation time
  • The six comparison operators== != < > <= >=
  • print(...) for output
  • Control flowif / else, while, and C-style for(init; cond; update)
  • Unary minus — modeled elegantly as 0 - x, so no dedicated negation node is needed
  • Parentheses and brace-delimited blocks { ... }
  • Correct operator precedence and left-associativity, achieved structurally through the parser's call hierarchy rather than a precedence table
  • Interactive REPL — write several lines, press ENTER on a blank line to run, type exit to quit
  • Full memory hygiene — every malloc'd AST node is reclaimed by a recursive freeAST() after each execution

Architecture

MiniPython is a classic three-stage tree-walking interpreter, with one concern per translation unit and no shared responsibilities between phases. Data flows in one direction: source text → tokens → AST → execution.

flowchart LR
    SRC["Source text<br/>(x = 10 * 2)"] --> LEX
    subgraph LEX["Lexer — lexer.c"]
        L["getNextToken()<br/>scans characters"]
    end
    LEX -->|Tokens| PAR
    subgraph PAR["Parser — parser.c"]
        P["recursive descent<br/>comparison → expr → term → factor"]
    end
    PAR -->|AST| EVAL
    subgraph EVAL["Evaluator — eval.c"]
        E["evaluate()<br/>walks the tree"]
    end
    EVAL --> OUT["Result / output"]
    REPL["main.c — REPL<br/>parse() → evaluate() → freeAST()"] -.orchestrates.- LEX
Loading

1. Lexer (lexer.c, lexer.h, token.h)

A hand-written scanner over the source string with an integer cursor. getNextToken() skips whitespace, then recognizes integer literals, identifiers and keywords (print, if, else, while, for), two-character operators (==, !=, <=, >=) matched before their single-character prefixes (a common lexer pitfall handled correctly), and single-character operators and punctuation. token.h defines the TokenType enum and a fixed Token struct (type + lexeme).

2. AST (ast.c, ast.h)

A single generic AST node struct is reused for every node type, tagged by a NodeType and sharing fields (value, name, op, child pointers left / right / third / fourth, plus a next pointer that links statements into a list). Constructor functions build nodes via malloc and a common zeroing helper; a recursive freeAST() reclaims every node and also walks the statement linked list.

3. Parser (parser.c, parser.h)

Recursive descent with a single-token lookahead and an eat() helper that asserts the expected token type and advances. Operator precedence and associativity are encoded purely by the call chaincomparison → expr → term → factor — instead of a precedence table. factor handles literals, variables, unary minus, and parenthesized sub-expressions. Statement functions cover assignment, print, if/else, while, C-style for, and brace blocks; program() and block() build next-linked statement lists appended in O(1) via first/last cursors.

4. Evaluator (eval.c, eval.h)

A tree-walking evaluator with a fixed-size symbol table (an array of name/value pairs, linear scan) for variables. A single evaluate() dispatch handles arithmetic, comparisons, assignment, print, blocks, and control flow directly through native C recursion and loops.


Tech Stack

Layer Choice
Language C (C99)
Build gcc (single command; MinGW/GCC/Clang compatible)
Dependencies None — standard library only
Interface Terminal REPL
Lines of code ~1,200 across 5 .c files + headers

Getting Started

Prerequisites

  • A C compiler (gcc, clang, or MinGW on Windows)

Build

From inside the source directory:

gcc main.c lexer.c parser.c ast.c eval.c -o minipython

Run

./minipython        # Linux / macOS
minipython.exe      # Windows

Using the REPL

Type code, then press ENTER on a blank line to execute the whole block. Type exit (or send EOF with Ctrl+Z on Windows / Ctrl+D on Unix) to quit.

MINIPYTHON
>>> x = 10
>>> y = 20
>>> print(x + y * 2)
>>>
50
>>> for (i = 0; i < 3; i = i + 1) {
>>>     print(i)
>>> }
>>>
0
1
2

Project Structure

minipython-interpreter/
├── main.c        # Entry point + REPL: parse() -> evaluate() -> freeAST()
├── lexer.c/.h    # Hand-written scanner: source text -> tokens
├── token.h       # TokenType enum + Token struct
├── ast.c/.h      # Generic AST node, constructors, recursive freeAST()
├── parser.c/.h   # Recursive-descent parser with single-token lookahead
├── eval.c/.h     # Tree-walking evaluator + symbol table
├── LICENSE
└── README.md

Tests

This is a course project and does not currently ship an automated test suite. Verification was done interactively through the REPL. Adding a small set of example .mpy programs with expected outputs and a lightweight test harness is a natural next step, along with a Makefile and CI.


Design Highlights

A few implementation details worth calling out:

  • Precedence without a table. Correct math (* before +) and left-associativity fall out of the order in which parser functions call each other — a clean, idiomatic recursive-descent technique.
  • Unary minus for free. -x is desugared to 0 - x, avoiding a dedicated AST node and evaluator case entirely.
  • Two-char operators handled correctly. ==, !=, <=, >= are matched before their single-character prefixes, avoiding a classic tokenizer bug.
  • No memory leaks. Every allocated node is freed by a recursive freeAST() that also traverses the intrusive statement linked list, called after each REPL run.
  • Strict separation of concerns. Lexer, AST, parser, and evaluator each live in their own translation unit behind minimal headers.

Honest Scope & Limitations

In the spirit of transparency, this is an educational interpreter with a deliberately small surface:

  • Values are a single type: integers only (no strings, floats, or booleans as distinct types).
  • No functions, arrays/lists, or logical operators (and / or / not).
  • Error handling is coarse: syntax and runtime errors print a generic message and exit rather than recovering.
  • Capacities are fixed (bounded symbol table, fixed-size buffers); undefined variables read as 0.
  • The interpreter relies on module-global state, so it is intentionally single-run rather than reentrant.

These are honest trade-offs for a course project — the goal was a clear, correct, readable demonstration of interpreter fundamentals, and that goal is met.


Team & Credits

This was a team project for a university Programming Languages course. The original repository was created and hosted by a teammate:

  • Original repository: erickson-arestigue/PROYECTO_LP
  • Erickson Arestigue — original repository owner and collaborator
  • Marcelo Jáuregui (@mazk36) — collaborator; studied the codebase line by line, documented the design, and prepared this English portfolio edition

Full credit to all team members who contributed to the original coursework. The source code was originally documented in Spanish (comments and the two .txt design notes); this README presents the work in English for an international audience.


Related Projects

Part of a portfolio of systems and language work:


License

Released under the MIT License.


Built by Marcelo Jáuregui · github.com/mazk36

About

MiniPython: a tree-walking interpreter for a small Python-like language, written from scratch in pure C. Hand-written lexer, recursive-descent parser with operator precedence, AST, and an evaluator with variables, arithmetic, comparisons, print, if/else, while and for, exposed through a REPL. University team project.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages