This project is a C implementation of a lambda calculus interpreter that I previously made in Python. It has most of the same features and works largely the same way, including prefix math. Sorry.
- 
Parses lambda expressions, including variables, abstractions (λx.M), and applications (M N).
 - 
Supports Church numerals: input numbers are automatically converted to their Church numeral representation (e.g.,
2becomesλf.λx.f (f x)). - 
Performs beta reduction:
(λx.M) N → M[x:=N] - 
Performs delta reduction: replaces predefined constants (like
⊤,⊥,+,*,is_zero) with their lambda calculus definitions. - 
Normalizes expressions by repeatedly applying reduction rules until no more reductions are possible.
 - 
Outputs each step of the reduction process.
 - 
Can abstract Church numerals back to their integer representation in the final output (e.g.,
λf.λx.f (f x)becomes2). 
Clone and compile:
git clone https://github.com/dhodgson615/Lambda-Calculus-Interpreter-C.git
cd Lambda-Calculus-Interpreter-C
make
./lambda "+ 1 1"You should see similar output to this if you paste the above into a Unix terminal:
Cloning into 'Lambda-Calculus-Interpreter-C'...
remote: Enumerating objects: 148, done.
remote: Counting objects: 100% (148/148), done.
remote: Compressing objects: 100% (128/128), done.
remote: Total 148 (delta 83), reused 38 (delta 18), pack-reused 0 (from 0)
Receiving objects: 100% (148/148), 52.10 KiB | 2.17 MiB/s, done.
Resolving deltas: 100% (83/83), done.
gcc -std=c17 -Wall -Wextra -Werror -pedantic -O3 -march=native -flto -mtune=native -funroll-loops lambda.c -o lambda
Step 0: + (λf.(λx.f x)) (λf.(λx.f x))
Step 1 (δ): (λm.(λn.m ↑ n)) (λf.(λx.f x)) (λf.(λx.f x))
Step 2 (β): (λn.(λf.(λx.f x)) ↑ n) (λf.(λx.f x))
Step 3 (β): (λf.(λx.f x)) ↑ (λf.(λx.f x))
Step 4 (β): (λx.↑ x) (λf.(λx.f x))
Step 5 (β): ↑ (λf.(λx.f x))
Step 6 (δ): (λn.(λf.(λx.f (n f x)))) (λf.(λx.f x))
Step 7 (β): λf.(λx.f ((λf.(λx.f x)) f x))
Step 8 (β): λf.(λx.f ((λx.f x) x))
Step 9 (β): λf.(λx.f (f x))
→ normal form reached.
δ-abstracted: 2
You can run the interpreter in two ways:
- 
Without Arguments
Run the executable without arguments:
./lambda
You will be prompted to enter a lambda expression:
λ-expr> (λx.x) yThe interpreter will then show the reduction steps:
Step 0: (λx.x) y Step 1 (β): y → normal form reached. δ-abstracted: y - 
With Arguments
Pass the lambda expression as a command-line argument:
./lambda "(λx.x) y"./lambda "((λm.λn.m (λf.λx.f (n f x)) n) 2) 1" 
The interpreter has a couple of compile-time (actually, runtime, but set at the top of lambda.c)
configurations that can be modified in lambda.c:
- 
CONFIG_SHOW_STEP_TYPE: (Default:true) Iftrue, shows the type of reduction (β or δ) for each step. Iffalse, only shows "Step X: ...". - 
CONFIG_DELTA_ABSTRACT: (Default:true) Iftrue, attempts to convert Church numerals in the final normal form back to their integer representation. 
The interpreter predefines several common constants:
- 
⊤(True):λx.λy.x - 
⊥(False):λx.λy.y - 
∧(And):λp.λq.p q p - 
∨(Or):λp.λq.p p q - 
↓(Predecessor for Church numerals, used in subtraction):λn.λf.λx.n (λg.λh.h (g f)) (λu.x) (λu.u) - 
↑(Successor for Church numerals, used in addition):λn.λf.λx.f (n f x) - 
+(Addition):λm.λn.m ↑ n - 
*(Multiplication):λm.λn.m (+ n) 0 - 
is_zero:λn.n (λx.⊥) ⊤ - 
-(Subtraction):λm.λn.n ↓ m - 
≤(Less than or equal to):λm.λn.is_zero (- m n) - 
pair:λx.λy.λf.f x y 
These can be used directly in your lambda expressions.
Input:
λ-expr> (+ 2) 1
Output:
Step 0: + (λf.(λx.f (f x))) (λf.(λx.f x))
Step 1 (δ): (λm.(λn.m ↑ n)) (λf.(λx.f (f x))) (λf.(λx.f x))
Step 2 (β): (λn.(λf.(λx.f (f x))) ↑ n) (λf.(λx.f x))
Step 3 (β): (λf.(λx.f (f x))) ↑ (λf.(λx.f x))
Step 4 (β): (λx.↑ (↑ x)) (λf.(λx.f x))
Step 5 (β): ↑ (↑ (λf.(λx.f x)))
Step 6 (δ): (λn.(λf.(λx.f (n f x)))) (↑ (λf.(λx.f x)))
Step 7 (β): λf.(λx.f (↑ (λf.(λx.f x)) f x))
Step 8 (δ): λf.(λx.f ((λn.(λf.(λx.f (n f x)))) (λf.(λx.f x)) f x))
Step 9 (β): λf.(λx.f ((λf.(λx.f ((λf.(λx.f x)) f x))) f x))
Step 10 (β): λf.(λx.f ((λx.f ((λf.(λx.f x)) f x)) x))
Step 11 (β): λf.(λx.f (f ((λf.(λx.f x)) f x)))
Step 12 (β): λf.(λx.f (f ((λx.f x) x)))
Step 13 (β): λf.(λx.f (f (f x)))
→ normal form reached.
δ-abstracted: 3
- 
lambda.h: Header file defining structures (Expr,Parser,VarSet,strbuf), enums (ExprType), and function prototypes. - 
lambda.c: Main implementation file containing:- 
Configuration flags.
 - 
String buffer utilities (
sb_*). - 
Expression creation and manipulation functions (
make_*,free_expr,copy_expr,substitute, etc.). - 
Church numeral functions (
church,is_church_numeral,count_applications,abstract_numerals). - 
Expression to string conversion (
expr_to_buffer*). - 
Variable set utilities (
vs_*,free_vars,fresh_var). - 
Reduction logic (
delta_reduce,beta_reduce,reduce_once,normalize). - 
Parser logic (
parse*,peek,consume,skip_whitespace). - 
mainfunction: handles input, calls parser and normalizer, and prints results. 
 - 
 - 
Makefile: For building the project. 
To remove the compiled executable:
make clean| Metric | C Version | Python Version | Ratio (Python / C) | Increase over C | 
|---|---|---|---|---|
| Real time (wall-clock) | 3.60s | 45.81s | 12.72x | +1,172.50% | 
| User CPU time | 2.09s | 34.76s | 16.63x | +1,563.16% | 
| System CPU time | 0.30s | 6.74s | 22.47x | +2,146.67% | 
| Max RSS (peak resident set) | 2,490,368B | 137,674,752B | 55.28x | +5,428.29% | 
| Peak memory footprint | 1,655,616B | 115,574,528B | 69.81x | +6,880.76% | 
| Page reclaims | 354 | 1,809,444 | 5,111.42x | +511,042.37% | 
| Page faults | 1 | 8 | 8.00x | +700.00% | 
| Voluntary context switches | 91,206 | 1,059,633 | 11.62x | +1,061.80% | 
| Involuntary context switches | 20 | 4,172 | 208.60x | +20,760.00% | 
| Instructions retired | 39,850,552,397 | 733,017,604,485 | 18.39x | +1,739.42% | 
| Cycles elapsed | 8,801,869,644 | 151,602,293,038 | 17.22x | +1,622.39% | 
All of these benchmarks are from my M3 MacBook Air for the input "* 49 49". They're also outdated by a couple of versions.