NICOLAUROCA.DEV / FIELD NOTES

How to Create Your Own Programming Language and Why You Absolutely Should

Featuring NicoScript: the retro-geek language nobody asked for, but everyone needs to build

ProjectsNicolau Roca
How to Create Your Own Programming Language and Why You Absolutely Should

🧠 What is a programming language, really?

Before you can build a language, you have to understand what one actually is. That might sound obvious—after all, we use them every day—but the reality is more philosophical (and cooler) than you might expect.

At its core, a programming language is just a formal way of expressing instructions. It's how we, humans, communicate our logic to machines. But like any language—spoken or written—it has structure, rules, quirks, and dialects.

🧱 Syntax vs Semantics: the form and the meaning

When we talk about a language, we usually break it into two fundamental layers:

  • Syntax: The form—how code looks. The grammar, the punctuation, the indentation.
  • Semantics: The meaning—what code does. The logic, the behavior, the intent.

You can think of syntax as the skin and semantics as the soul. A program with perfect syntax but no meaningful semantics is like a beautifully written novel that says absolutely nothing.

In NicoScript, we'll make the syntax so readable that it borders on parody—but the semantics will still be valid and executable.

🔄 Interpreters vs Compilers vs Transpilers

Every programming language needs an engine to run. That engine is usually one of these three:

  • Interpreter: Reads and executes code line by line. Like Python or Bash.
  • Compiler: Translates code into machine language before execution. Like C or Rust.
  • Transpiler: Converts code into another language. Like TypeScript to JavaScript.

Most modern ecosystems blur these lines. Java, for example, compiles to bytecode, which is then interpreted by the JVM. And modern JavaScript engines use Just-in-Time (JIT) compilation to optimize interpreted code.

For NicoScript, we’ll use an interpreter built in Python. Why? Because it’s simple to prototype, easy to debug, and great for education. You’ll see exactly how raw text becomes living logic.

🧬 What makes a language “real”?

Some developers think a programming language needs a full compiler, standard library, IDE support, and runtime to be "real". We disagree.

If you can:

  • Define a syntax
  • Parse it into something meaningful
  • Execute that meaning in a controlled way

…then congratulations, you've built a language.

Whether it runs on a production server or just prints “beep boop” in your terminal doesn’t matter. The moment you write code that your machine understands—and you designed that code—you've entered the realm of language creators.

🖖 NicoScript: simple, expressive, and proud of it

Our mission with NicoScript isn’t to rival Python or invent a revolutionary paradigm. It’s to show, step by step, how anyone can create a new language from scratch—even if that language only knows how to say "Hello, world" and loop three times.

We’ll define its rules. We’ll code the interpreter. We’ll make it say silly things. And by the end of this journey, you won’t just understand what a programming language is — you’ll have created one yourself.

🧪 Designing NicoScript: the simplest and geekiest language ever invented

Before we write a single line of interpreter code, we need to decide what kind of language we’re building. And since this is NicoScript, we’re aiming for something gloriously retro, sci-fi flavored, and ridiculously easy to use.

Think of it as a programming language that a 1989 arcade terminal might understand. A language so basic, even your fridge could compile it. A language that proudly embraces both simplicity and silliness.

📖 Defining the syntax: words from the future

Let’s start by defining the commands our language will understand. NicoScript is designed to be minimal and human-readable, with a structure that’s almost conversational. Here’s what a sample script might look like:


RUN "Hello, world"
SET mood TO "retro"
IF mood = "retro" THEN RUN "Booting cassette drive..."
LOOP 3 TIMES:
    RUN "Processing..."

Each instruction is a clear command:

  • RUN executes a line of output or action.
  • SET assigns values to variables.
  • IF ... THEN handles conditions.
  • LOOP ... TIMES: runs repeated actions.

We won’t support classes, functions, or chaos. Just basic logic, variables, conditionals, and loops — enough to build something fun, understandable, and complete.

🌌 Retro sci-fi inspiration

NicoScript is born from the neon-glow dreams of the 80s and 90s: movies where hackers wore trench coats and AI ran on CRT monitors. The language aesthetics take cues from:

  • TRON – programs as sentient entities.
  • The Matrix – command-line control of reality.
  • Neuromancer – hacking as a mystical art.
  • Star Trek – speaking directly to machines.

Every command in NicoScript should feel like you're about to launch a nuclear submarine or reboot the mainframe of a space station.

🎯 Philosophy: clarity over cleverness

Too many modern languages reward cleverness. NicoScript rewards clarity. There are no symbols like != or ++. No semicolons. No parentheses. Just readable commands that make sense at a glance.

This design choice helps us in two ways:

  1. It keeps the parser simple and the syntax predictable.
  2. It makes the language feel accessible — even fun — for anyone new to programming.

If you can read English, you can read NicoScript. And by the end of this series, you'll be able to execute it, extend it, and maybe even design your own flavor of it.

Next up: we stop imagining and start building. Let’s dive into how to create the machinery behind NicoScript — from lexers to interpreters — and turn this silly sci-fi syntax into working code.

🧰 Building NicoScript Step by Step

Now that we’ve imagined how NicoScript should look and feel, it’s time to bring it to life. To do that, we’ll build a mini interpreter using Python — the perfect language for prototyping without headaches.

Here are the core steps we’ll follow:

  1. Lexing — breaking raw text into tokens.
  2. Parsing — converting tokens into a structured syntax tree.
  3. AST — building an Abstract Syntax Tree to represent the logic.
  4. Interpreting — executing the tree as live behavior.

🔤 Step 1: Creating the Lexer (Tokenizer)

The lexer is the first part of any language engine. It reads raw text and converts it into tokens — tiny classified pieces like KEYWORD, STRING, IDENTIFIER, etc.

Here’s a simple lexer for NicoScript in Python:


import re

TOKEN_REGEX = [
    ("RUN", r"\bRUN\b"),
    ("SET", r"\bSET\b"),
    ("TO", r"\bTO\b"),
    ("IF", r"\bIF\b"),
    ("THEN", r"\bTHEN\b"),
    ("LOOP", r"\bLOOP\b"),
    ("TIMES", r"\bTIMES\b"),
    ("STRING", r'"[^"]*"'),
    ("NUMBER", r"\b\d+\b"),
    ("IDENTIFIER", r"\b[a-zA-Z_][a-zA-Z0-9_]*\b"),
    ("EQUAL", r"="),
    ("NEWLINE", r"\n"),
    ("WHITESPACE", r"[ \t]+"),
]

def tokenize(code):
    pos = 0
    tokens = []
    while pos < len(code):
        match = None
        for token_type, regex in TOKEN_REGEX:
            regex_obj = re.compile(regex)
            match = regex_obj.match(code, pos)
            if match:
                if token_type != "WHITESPACE" and token_type != "NEWLINE":
                    tokens.append((token_type, match.group(0)))
                pos = match.end(0)
                break
        if not match:
            raise SyntaxError(f"Unexpected character: {code[pos]}")
    return tokens

This tokenizer doesn’t support indentation or nested blocks yet — we’ll get there. For now, it reads a simple NicoScript line like:


RUN "Hello, world"

And turns it into a clean list of tokens:


[("RUN", "RUN"), ("STRING", '"Hello, world"')]

🌳 Step 2: Building the Parser (Syntax Tree)

Once we have tokens, we need to group them into meaningful structures — statements. That’s the parser’s job.

Here’s a very simple parser for NicoScript:


def parse(tokens):
    position = 0
    ast = []

    while position < len(tokens):
        token_type, token_value = tokens[position]
        
        if token_type == "RUN":
            _, value = tokens[position + 1]
            ast.append(("RUN", value.strip('"')))
            position += 2

        elif token_type == "SET":
            var_name = tokens[position + 1][1]
            position += 3  # skip SET, name, TO
            value_type, value = tokens[position]
            if value_type == "STRING":
                ast.append(("SET", var_name, value.strip('"')))
            elif value_type == "NUMBER":
                ast.append(("SET", var_name, int(value)))
            position += 1

        else:
            raise SyntaxError(f"Unexpected token: {token_type}")
    
    return ast

🧠 Step 3: Creating the AST (Abstract Syntax Tree)

In our case, we’re keeping it simple. Our parser returns a list of tuples — each one a clean, high-level instruction. That is our AST.

Example:


[
    ("RUN", "Hello, world"),
    ("SET", "mood", "retro")
]

Later, we’ll improve this with real node classes or tree structures, but for now, this is enough to interpret logic.

⚙️ Step 4: Building the Interpreter

Now that we have a working AST, let’s run it.


def interpret(ast):
    context = {}

    for node in ast:
        if node[0] == "RUN":
            print(node[1])
        
        elif node[0] == "SET":
            _, name, value = node
            context[name] = value

With this, we can now fully parse and execute a NicoScript file like:


SET mood TO "retro"
RUN "Booting..."
RUN mood

Which will produce:


Booting...
retro

🧪 Step 5: Running It All Together

Let’s tie it all into one flow:


code = '''
SET answer TO 42
RUN "The answer is:"
RUN answer
'''

tokens = tokenize(code)
ast = parse(tokens)
interpret(ast)

This little stack of functions is the complete core of a language. From text to behavior, NicoScript is alive.

Up next: we’ll add conditionals, loops, and more powerful constructs to NicoScript — and we’ll test it in full with actual scripts.

🔧 Tools for Language Creation

You don’t need to start from scratch with punch cards and dreams. There’s a whole universe of tools, libraries, and frameworks designed to help you build programming languages — whether you’re crafting something as simple as NicoScript or aiming to dethrone C++ (good luck).

🐍 Languages for building languages

These are the most common choices for building interpreters and compilers:

  • Python: Excellent for prototyping. Tons of libraries. Easy to read and write. Perfect for building interpreters like ours.
  • Rust: High performance and memory-safe. Ideal for building fast, robust compilers. Slightly masochistic to start with, but very rewarding.
  • C: The classic. Raw power, no guardrails. Most “real” compilers (GCC, Clang) are written in C or C++. If you want to learn how memory management and performance truly work, this is your dojo.

📚 Lexer & Parser Libraries

These libraries help you handle the dirty work of tokenizing and parsing. Most of them use grammars or parsing expressions to define how your language works:

  • lark (Python) – Fast, modern, and easy-to-use. Supports LALR, Earley, and more.
  • PLY (Python Lex-Yacc) – Classic parser built on lex/yacc-style grammars. Old-school and solid.
  • ANTLR (Java, Python, C#, etc.) – Industrial-strength. Generates parsers from grammar files. Very powerful, great ecosystem.
  • parsy (Python) – Small, functional parser combinator library. Great for custom mini languages.

If you want to go full pro, learn how to write your own recursive descent parser — it’s a rite of passage.

🧠 Compiling to something useful

If you're not just interpreting but compiling your language, you might want to target one of the following:

  • LLVM: An industrial-strength compiler backend. You write your frontend and feed it into LLVM IR. Used by Rust, Clang, and others. Not for the faint of heart.
  • WASM (WebAssembly): A modern bytecode format designed for browsers, but usable anywhere. You can compile to WASM and run your language on the web. Perfect for futuristic NicoScript demos!

🎯 What we’re using for NicoScript

To keep it fun and accessible, we’re building NicoScript using:

  • Python – for its clarity, speed of development, and low barrier to entry.
  • Manual parsing – to understand every moving part. No black boxes.

Later, we’ll show how to upgrade NicoScript to use lark or even compile to another language. But for now, we stay raw and real — just code, strings, and glorious structure.

Next up: it’s time to put NicoScript to the test. We’ll build a REPL, interpret actual files, and write the most important program ever created: a space-age “Hello, World”.

🕹️ Testing NicoScript: REPL and Real Scripts

We’ve got the core mechanics of NicoScript up and running — lexer, parser, AST, interpreter. Now it’s time to interact with it like a real language: through a terminal, with scripts, in glorious ASCII silence.

🖥️ Building a Mini-REPL

REPL stands for Read-Eval-Print Loop. It lets you enter code line by line, execute it immediately, and see the results — just like Python’s interactive shell.

Here’s a tiny REPL for NicoScript:


def repl():
    print("NicoScript v0.1 — Type 'exit' to quit.")
    context = {}

    while True:
        try:
            line = input(">>> ")
            if line.strip().lower() == "exit":
                break
            tokens = tokenize(line)
            ast = parse(tokens)
            interpret(ast)
        except Exception as e:
            print(f"Error: {e}")

This REPL supports single-line expressions for now. But it’s a great testing ground for quick ideas and syntax checks.

📄 Running NicoScript Files

Let’s also run full scripts from a file — because nobody writes enterprise apps one line at a time.

Here’s a basic runner:


def run_file(path):
    with open(path, "r") as f:
        code = f.read()
    tokens = tokenize(code)
    ast = parse(tokens)
    interpret(ast)

Now create a hello.nico file with this content:


SET name TO "Nico"
RUN "Welcome to NicoScript"
RUN name

And run it from Python:


run_file("hello.nico")

Output:


Welcome to NicoScript
Nico

🎮 Geeky Exercises and References

Let’s write some fun, geek-inspired scripts to test our interpreter:

1. Star Wars intro:


RUN "A long time ago in a galaxy far, far away..."
SET hero TO "Luke"
RUN hero

2. Matrix glitch detector:


SET cat TO "black"
RUN "You see a" 
RUN cat 
RUN "cat walk by. Again."

3. Retro startup sequence:


RUN "Loading cassette OS..."
LOOP 3 TIMES:
    RUN "beep"
SET status TO "Ready"
RUN status

You can create dozens of short programs like these to test conditions, variables, and eventually loops or nested logic as we add support.

🧪 Test like a true NicoScript engineer

If you really want to stretch your language, try writing actual micro tools in NicoScript:

  • A calculator that prints results (coming soon: ADD 3 TO 5)
  • A dialogue tree engine
  • A simulation of a retro computer booting up

As long as you keep it fun, readable, and “feels like hacking a spaceship,” you’re doing it right.

In the next part, we’ll look at the hard truths: what really breaks when a language grows, what performance looks like, and why error handling is a cruel god.

💣 The Real Challenges of Creating Your Own Language

So far, building NicoScript has been fun. You wrote code that talks to machines in your own syntax. But now, we step into the shadowy part of language design — where real-world complexity starts to punch back.

🚧 Performance: the speed trap

Interpreters are great for simplicity and rapid prototyping, but they're not fast. Every line is parsed and executed on the fly, and that adds overhead. If you scale up, performance becomes a serious concern.

Some real-world solutions:

  • Bytecode compilation: Convert your language into an intermediate format and run that instead of raw strings.
  • Just-In-Time (JIT) compiling: Compile parts of the code during execution for faster performance.
  • Native compilation: Generate machine code via LLVM or C as output.

For NicoScript? It’s fine to be slow — it’s here to teach, not run the space shuttle.

🧨 Error Handling: where dreams go to die

Nothing ruins the joy of a custom language faster than a vague error. Imagine debugging this:


SyntaxError: Unexpected token 'TO'

Helpful? Barely. Good languages have solid error reporting with line numbers, context, and clear messages. That takes extra work, especially in custom parsers.

Tips:

  • Track line/column position during tokenization.
  • Raise custom exceptions for specific syntax and semantic problems.
  • Always assume the user (even if it's you) will write broken code.

🧰 Tooling: the invisible mountain

If your language gets serious, you’ll need:

  • Syntax highlighting
  • Code formatters
  • Linting and static analysis
  • Debuggers
  • REPLs and CLI runners

Every tool is its own tiny project. That’s why language ecosystems take years to mature. NicoScript skips most of this — but even a basic REPL already counts as tooling!

👥 Adoption: how to get people to use your language

Let’s be honest — building a language is cool. Getting someone else to use it is wizardry.

To make a language useful (or viral), it needs:

  • Clear purpose or niche
  • Readable syntax
  • Good documentation
  • Real-world examples
  • A welcoming community (even if it’s just a Discord server)

NicoScript embraces the opposite: it’s designed to be unserious, playful, and educational. But even then, making it feel like a "real" language — with docs, a REPL, a logo, maybe a t-shirt — helps people connect with it.

🔥 Final Boss: Feature Creep

You start with:


RUN "Hello"

…and before you know it you’re adding:

  • Modules
  • Functions
  • Object systems
  • Concurrency
  • Exceptions

Know your limits. It's better to have 5 working features than 20 half-broken ones. Languages are like software: scope is everything.

Next up, we’ll explore how to take your language further: compiling, transpiling, and running it on the web. Because every geek deserves to say, “I wrote a language… and it runs in the browser.”

🚀 Beyond NicoScript: Compiling, Transpiling, and Extending

So, you’ve built a language, written some programs, maybe even showed it off at your local hacker meetup. Now you’re wondering: how far can this go?

The answer: surprisingly far. If you want to take NicoScript to the next level — making it faster, more powerful, or more compatible — here are your main paths.

⚙️ Option 1: Compile to C

C is like the assembly language of language creators. If you can generate valid C code from NicoScript, you instantly gain:

  • Native performance
  • Cross-platform support
  • Access to existing C tooling and compilers

Here’s how that might look:


RUN "Hello"

Could compile to:


#include <stdio.h>

int main() {
    printf("Hello\n");
    return 0;
}

This isn’t easy — you’ll need to write a code generator — but it’s a powerful milestone.

🔁 Option 2: Transpile to Python or JavaScript

Transpiling means converting code from one high-level language to another. This is much easier than compiling to machine code, and perfect for getting your language to run anywhere.

NicoScript code like:


SET name TO "Nico"
RUN name

Could transpile to:


name = "Nico"
print(name)

Or in JavaScript:


let name = "Nico";
console.log(name);

Transpilers are great for integrating with other ecosystems and for quickly getting features you don’t want to implement (like math functions or string formatting).

📦 Option 3: Generate Bytecode

This one’s more advanced: instead of interpreting or transpiling, you translate your code into a compact, lower-level format — called bytecode — which your interpreter can execute more efficiently.

Think of it as making a “compiled” version of NicoScript that still runs in your interpreter, but faster and more safely.

  • Create an instruction set (e.g., LOAD, STORE, PRINT)
  • Compile code into bytecode
  • Run it in a custom virtual machine (VM)

It’s how Python, Java, and Lua do it — and it can be a fun project on its own.

🌐 Option 4: Run in the Browser (WebAssembly)

Want to show off your language online? Compile it to WebAssembly (WASM) and run it in the browser — or use a JavaScript-based interpreter.

Some approaches:

  • Use tools like pyodide to run your Python interpreter in the browser
  • Port your NicoScript engine to JavaScript (Node.js or browser)
  • Write a transpiler from NicoScript to JS and execute it natively

Now anyone with a browser can use your language without installing anything. Bonus points if you design a cool terminal UI in neon green.

➕ Bonus: Adding Real Features

Want to upgrade NicoScript’s powers? Here are natural next steps:

  • Functions: let users define reusable code blocks
  • Math operations: ADD 3 TO 5 or expressions like SET x TO 3 + 5
  • Nested conditionals and loops
  • String interpolation: RUN "Hello, ${name}"
  • Standard Library: built-in functions, helpers, constants

You don’t need to build all of them — just pick what fits your language’s spirit. NicoScript should stay readable, friendly, and a little weird. But that doesn’t mean it can’t grow.

Next: where to go from here — the best resources, communities, and books to become a true language architect.

📚 Resources to Become a Language Creator

By now, you’ve walked through every core concept of language design — and even built a working prototype in the form of NicoScript. If you’re hungry for more, here’s your curated toolkit for becoming a full-fledged language architect.

📘 Must-Read Books

  • Crafting Interpreters by Robert Nystrom
    A hands-on, modern guide that walks you through building a language from scratch (Lox). Beautifully written, beginner-friendly, and deep.
  • Programming Language Pragmatics by Michael L. Scott
    Theory-heavy but comprehensive. Covers design decisions, runtime models, and deep language internals.
  • Compilers: Principles, Techniques, and Tools (The Dragon Book)
    The legendary classic. Very theoretical. Great if you want to understand compiler design from first principles.

🌐 Online Courses and Tutorials

  • Let’s Build a Simple Interpreter by Ruslan Spivak – Great step-by-step Python tutorial, perfect for beginners.
  • Writing a C Compiler on GitHub – Build a working C compiler in under 1,000 lines of code.
  • MIT OpenCourseWare – Programming Languages – Free university-level course. Deep dive into language concepts.

🧪 Inspiring Open Source Projects

  • Lox (from Crafting Interpreters) – Complete interpreter in Java and C. Amazing learning resource.
  • TinyLang – A tiny Python-based language with clean code.
  • BASIC interpreters – Retro interpreters with surprisingly elegant core logic.
  • MonkeyLang – Another minimal, expression-based interpreter you can dissect and fork.

💬 Communities and People

  • r/ProgrammingLanguages – Reddit hub for sharing new languages, ideas, and feedback.
  • Dev.to – Search for “language design” or “interpreter” articles. Tons of gold.
  • Stack Overflow – Great for troubleshooting parser bugs and regex disasters.
  • GitHub – Search for “interpreter”, “compiler”, or “parser” projects to learn by reading code.

📈 How to Keep Growing

Building a language is like building a game engine or an operating system: you’re never really “done.” There’s always another feature, another bug, another design pattern you want to test.

Here’s how to keep the momentum going:

  • Write a blog or devlog about your language journey
  • Open-source NicoScript and invite others to fork it
  • Try building a language with a different syntax model (Lisp, postfix, YAML-like…)
  • Start thinking about building your own VM (yes, really)

You're not just a developer anymore. You're a language designer. And now you know how deep the rabbit hole goes.

Let’s wrap this up with a final thought — and a challenge.

🔮 Conclusion: Creating a Language is Like Creating a Universe

You started this journey with a silly idea: inventing your own programming language. Maybe it was out of curiosity, boredom, or the irresistible geek urge to say “I made this”. But what you’ve built is more than just syntax and logic.

You’ve created a system where your thoughts become rules, where your design becomes law, and where machines follow instructions that never existed before. That’s not just programming — that’s world-building.

NicoScript began as a joke. A neon-colored tribute to retro sci-fi, designed to be absurdly readable and minimally powerful. But along the way, it taught you:

  • How languages parse and process code
  • How to structure logic into trees, tokens, and rules
  • How to build tools that others can use to think

And maybe, just maybe, it opened the door to a much deeper understanding of what programming really is: a form of expression, like writing, music, or art — but with strict syntax and infinite potential.

🌌 Final Thoughts

You don’t need permission to build a language. You don’t need a PhD in compilers or a grant from some tech foundation. You just need an idea, a text editor, and the willingness to turn chaos into structure.

The next big thing might not be written in Python, JavaScript, or Rust. It might be written in your language.

And who knows? Maybe one day, developers will open their terminals and type:


RUN "Welcome to NicoScript v1.0"

...and smile, because they know someone dared to build a universe from scratch — and then shared it with the world.

Now go. Build yours.