Dasturlash

Python interpreter 1024‑byte C implementation

7-sentabr, 2026, 04:521 ko'rish6 daqiqa o'qish
Python interpreter 1024‑byte C implementation

Creating a working Python interpreter that fits into a single kilobyte of C code sounds like a paradox, yet a recent code‑golf project proves it possible. The author set out to preserve the look‑and‑feel of Python—indentation, colons, and keyword syntax—while stripping away every nonessential byte.

Why a tiny interpreter matters

Beyond the novelty of fitting a language into 1024 bytes, the exercise highlights the overhead hidden in conventional interpreters. CPython tokenizes source, builds an abstract syntax tree, optimizes, emits bytecode, and finally runs a virtual machine. Each stage consumes memory and processing time, which is acceptable on modern hardware but becomes a constraint in embedded or sandboxed environments.

A screenshot of a terminal checking the byte length of the golfed code, compiling it, and running fizzbuzz with it.

Core design choices

The implementation abandons the classic compilation pipeline. Instead, it reads the source directly from a fixed‑size buffer (char src[999]) and evaluates expressions on the fly using a recursive‑descent parser. Global variables hold the symbol table (int vars[256]) and the current character position, eliminating the need for dynamic allocation.


def buzz():
    for n in range(101):
        if n % 15 == 0:
            print("FizzBuzz")
        else:
            if n % 3 == 0:
                print("Fizz")
            else:
                if n % 5 == 0:
                    print("Buzz")
                else:
                    print(n)
buzz()

Only a single‑character, lowercase variable name is allowed. This restriction lets the interpreter index the symbol table directly with the ASCII code of the identifier, saving both space and lookup time.


char src[999];       /* Entire program without most spaces. */
int  vars[256];      /* Symbol table.                       */
int  pos;            /* Next character in src.              */
int  ch;             /* Current character in src.           */
int  line_start;     /* Where the current line starts.      */

Parsing arithmetic and control flow

Arithmetic is handled by a compact parse_sum function that repeatedly parses terms and applies + or -. Comparison operators are omitted; truthiness is inferred from numeric results, which reduces the byte count dramatically.


int parse_sum(void) {
    int value = parse_term();
    while (ch == '+' || ch == '-') {
        if (ch == '+')
            value = value + parse_term();
        else
            value = value - parse_term();
    }
    return value;
}

Control structures such as if, while, and for are recognized by checking the first character of the keyword. The parser then jumps back to the condition expression after executing the block, effectively re‑parsing the source each iteration. No intermediate bytecode is generated.


    if (ch == 'w' || ch == 'i' || ch == 'f') {
        /* ---- while / if / for ---- */
        int keyword = ch;
        int loop_var = 0;

        if (keyword == 'f') {             /* "for K in range(N):" */
            pos += 2;                     /* skip "or"             */
            loop_var = next();            /* the loop variable     */
            pos += 8;                     /* skip "inrange("       */
            vars[loop_var] = 0;
        } else if (keyword == 'w')
            pos += 4;                     /* skip "hile"           */
        else 
            pos += 1;                     /* skip "f" of "if"      */

Function handling without a call stack

When a def statement is encountered, the interpreter records the start position of the function body in the global symbol table. A function call saves the caller's position, jumps to the recorded location, runs the block, and restores the original position on return. This technique reuses the C call stack for temporary storage, avoiding a separate frame structure.


    if (ch > 96) {
        value = vars[ch];
        next();
    }

Code‑golf techniques that shrink the interpreter

  • Single‑letter identifiers: All variables and functions are named with one character, cutting dozens of bytes.
  • Implicit int: The code relies on C89 rules where omitted type specifiers default to int, removing explicit declarations.
  • ASCII arithmetic: Characters are compared using their numeric codes (e.g., c-43u for '+'), eliminating quotation marks.
  • Ternary and comma operators: Complex statements are collapsed into single expressions, reducing line breaks and braces.
  • Global zero‑initialization: All globals start as zero, so explicit initialization code is unnecessary.

For example, the readable parse_sum function becomes e(){for(z=t();c-43u<3;)y=44-c,z+=y*t();return z;} after golfing. Each transformation saves a few bytes, and the cumulative effect reaches the 1024‑byte target.


void run_block(int min_indent) {
    for (;;) {
        int indent = read_indent();

        if (ch == '\n')                       
            continue;

        if (indent < min_indent || ch == 0) {
            pos = line_start;
            return;
        }

Limitations of the 1 KB interpreter

The interpreter supports only a tiny subset of Python: single‑character variables, integer arithmetic, basic if/while/for loops, and function definitions without arguments. Error handling is nonexistent; any deviation from the expected syntax leads to undefined behavior.


char s[999];v[256],p,c,x,y,z,w,u;G(){return c=s[p++];}I(){for(u=p;G()==32;);return p-u;}Y(){c&&c-10&&Y(G());}f(){x=0;if(G()>96)x=v[c],G();for(;c-48u<10;G())x=x*10+c-48;return x;}t(g,h){for(g=f();c==42|c==37;)h=c,g=h-42?g%f():g*f();return g;}e(){for(z=t();c-43u<3;)y=44-c,z+=y*t();return z;}E(a,q){a=e();if(c-60u>2)return a;w=c-61;q=G()==61;p-=!q;x=e();return w?(a-x)*w>-q:a==x;}S(i){for(;I()>i|c==10;)Y();p=u;}Q(){for(G();G()-34;)putchar(c);G();}B(i,q,j,k,a,m,n){for(;;){j=I();if(c==10)continue;if(j<i|!c){p=u;return;}if(c==119|c==105|c==102){k=c;k-102?p+=k/4-25:(p+=2,m=G(),p+=8,v[m]=0);q=p;for(;;){a=k-102?E():v[m]<E();p+=k==102;G();if(!a){S(j);break;}B(j+1);if(k==105)break;k-102||v[m]++;p=q;}I()-j|c-101?p=u:(p+=4,G(),a?S(j):B(j+1));}else if(c==100){p+=2;k=G();Y();v[k]=p;S(j);}else{if(c>96){k=c;while(G()>96);c==40?k-112?(G(),n=p,p=v[k],B(2),p=n,G()):(s[p]-34?printf("%d",E()):Q(),puts(""),G()):(v[k]=E());}Y();}}}main(q,m,h){for(h=m=q=0;~(c=getchar());){c=c-9?c:32;h^=c==34;s[q]=c;q+=c-32?1:!m|h;m=c>32|m&&c-10;}B(0);}

String literals are accepted but are not processed beyond preserving indentation. No modules, classes, or advanced data structures are available, making the interpreter unsuitable for real‑world applications but perfect for demonstrating minimalism.

What the project teaches about language design

By stripping away layers, the project reveals which features of a language are essential for a usable interpreter and which are luxury. The reliance on indentation for block delimitation, for instance, can be implemented with a simple line‑indent counter, while tokenizing whitespace is optional if the source is pre‑processed.

Moreover, the approach shows that a language's perceived complexity often stems from its tooling rather than the core semantics. A lean interpreter can be built with a handful of global arrays and a few dozen functions.

Potential extensions and open questions

Future work could explore adding a minimal object model, supporting multi‑character identifiers, or introducing a tiny bytecode layer to improve loop performance. Each addition would increase the byte count, prompting a trade‑off analysis between feature richness and size.

Another open question is how the interpreter would behave on non‑ASCII input or on platforms with different integer sizes. Portability tests could uncover hidden assumptions in the current C89‑centric implementation.

Conclusion

The 1024‑byte Python interpreter demonstrates that a functional subset of a high‑level language can exist within extreme size constraints. While it is far from a production‑ready tool, the project serves as an educational sandbox for understanding parsing, execution, and the art of code golfing. It also invites developers to reconsider how much infrastructure is truly necessary for language execution.

Read the original write‑up at austinhenley.com/blog/python1024.html for the full source and a deeper look at the golfing process.

Asl manba: austinhenley.com

Manba: Hacker News
#python #interpreter #code golf #c
Telegram da muhokama qilish