Designing Your Own Programming Language — “Flare” as a Guide

Design Your Own Programming Language - "Flare"

Step-by-step guide On How to design your own programming language with Flare. Learn types, syntax, functions, and compiler basics for beginners.

Design Your Own Programming Language - "Flare"

Why Create a New Language?

Have you ever thought:

“Why does Python care about indentation so much?”
“Why does C++ make me write so many semicolons and type declarations?”

If these thoughts have ever crossed your mind, you’ve already taken the first step toward designing your own programming language.

Building a language isn’t just for experts. It’s for anyone who wants to understand how code works at its deepest level. In this blog post, you’ll learn how to begin your language design journey, and we’ll walk together through building a new programming language from scratch.

We call our language Flare — and you can build your own version alongside us.

Programming Languages: Same Goals, Different Designs

Every language has the same job: turn human instructions into something computers can execute. But how they do it — and how you write in them — varies wildly.

Let’s compare a few examples:

C++

#include <iostream>
int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

Python

print("Hello, World!")

Ruby

puts "Hello, World!"

Rust

fn main() {
    println!("Hello, World!");
}

Notice the difference in syntax, verbosity, and structure — and yet they all do the same thing.Before writing any code, you need to wear the hat of a language designer.

Designing a language is about decisions — and those decisions will shape everything else. We’ll walk through each of them step by step, and you’ll make your own choices for the language you’ll build in this series. We’ll also share how we made those same decisions in Flare, our language for this series.

First Things First: Name Your Language

Before making rules or writing syntax, give your language a name.

  • Is it fast? Elegant? Friendly?
  • Should the name sound serious (like Rust) or playful (like Lua)?

We chose Flare because it evokes speed, simplicity, and brightness — like a small but powerful burst of energy.

By naming your language, you’re shaping its personality.

Key Decisions in Language Design

Now that you have a name, let’s talk about major design choices you’ll need to make. Each of these defines how your language looks, behaves, and feels to use.

We’ll introduce each decision, explain your options, show how popular languages handle them, and reveal the choice we made in Flare.

1. Typing: Static vs Dynamic

What is typing?
Typing defines how your language handles types like int, string, etc. Does the language require the user to define types ahead of time (static), or does it figure them out at runtime (dynamic)?

Static Typing:

  • Variables have fixed types.
  • Type errors are caught at compile time.
  • Example: C++, Java, Rust
int x = 5;

Dynamic Typing:

  • Variables can hold any type at any time.
  • Errors may occur at runtime.
  • Example: Python, JavaScript
x = 5
x = "hello"  # still valid

Which should you choose?

  • Static typing is easier to implement and leads to better performance.
  • Dynamic typing is more flexible and user-friendly, but harder to compile efficiently.

Flare’s decision: Dynamic typing, with optional type hints and type inference. This gives us flexibility while still allowing optimization.

2. Paradigm: Procedural, Functional, or Object-Oriented

What is a paradigm?
It defines how code is structured:

  • Procedural languages group instructions into functions (like C).
  • Functional languages focus on expressions and immutability (like Haskell).
  • Object-Oriented languages organize code into objects and classes (like Java, C++).

Flare’s decision: Procedural. It’s the simplest to understand and implement. No classes, no inheritance.

3. Syntax Style

Do you want your language to use colons and indentation like Python, or braces like C++?

Examples:

  • Python:
def hello():
    print("Hi")
  • C++:
void hello() {
    cout << "Hi";
}

Flare’s decision: C-style braces with readable keywords and minimal punctuation.

create hello() {
    display "Hi";
}

4. Scoping Rules

Lexical scoping means variables are visible within the block they’re declared in.
Global scoping means variables are accessible everywhere.

Most modern languages use lexical/block scoping.

Flare’s decision: Lexical scoping. It makes reasoning about variables simpler and more predictable.

What Kind of Language Are You Building?

Programming languages serve different purposes:

  • Scripting (e.g., Bash, Python)
  • Embedded systems (e.g., C)
  • Web dev (e.g., JavaScript)
  • Academic (e.g., Haskell, Lisp)
  • General-purpose (e.g., Python, C++)

In this series, we’re building a general-purpose language — something like Python or C++ that can solve common problems and run standalone.

You can choose your own direction. But whatever your goal, your language must support certain core features.

The Essentials: What Every Language Needs

Let’s explore the essential features that every general-purpose language must have. For each feature, we’ll help you understand:

  • What decisions you’ll need to make as the language designer
  • How popular languages implement them
  • What we’ve chosen for Flare, our language for this series

Remember, every design choice you make will influence how your compiler works. Simpler choices usually mean easier implementation. But don’t be afraid to challenge yourself.

1. Data Types

 What You Must Decide:

  • What primitive types will your language support?
  • Will it allow compound types like lists, dictionaries, sets?
  • Will types be statically declared or inferred dynamically?
  • How will users declare variables and assign values?
  • Will types be enforced at compile time or checked at runtime?

 Examples:

  • C++: Statically typed. You must declare types explicitly:
    int x = 5;
  • Python: Dynamically typed. Types are inferred:
    x = 5
  • Python lists can hold mixed types. C++ arrays are fixed and typed.

Flare’s Design:

  • Fundamental typesint (Integer), frac (Floating point), flag (Boolean), text (String), letter (Character)
  • Compound typeslist, dict (key-value pairs)
  • Typing model:
    • Dynamically typed, but users can optionally give type hints
    • Inference is used if no hint is provided
  • Declaration syntax:
claim x = 42;               // int inferred
claim y: text = "hello";    // explicit type hint

You’ll need to implement a way to store type info internally — either inferred from values, or parsed from hints.

2. Declarations and Assignments

 What You Must Decide:

  • Do variables need to be declared before use?
  • Will assignment and declaration use different syntax?
  • Are variables mutable, or is immutability the default?

Examples:

  • C++:
    • Declaration: int x;
    • Assignment: x = 10;
    • Combined: int x = 10;
  • Python:
    • Declaration + assignment in one: x = 10

Flare’s Design:

  • Variables must be declared using the claim keyword
  • Supports both implicit and explicit type declarations

Flare:

claim count = 0;                // inferred as int
claim greeting: text = "Hi";    // explicit
claim userlist: list = [1, 2, 3];

3. Conditional Statements

What You Must Decide:

  • What syntax will be used for if, else, and elif (or similar)?
  • Will you include switch/match constructs?
  • Will conditions need to be boolean expressions?
  • Will indentation or braces determine scope?

Examples:

  • Python:
if x > 0:
    print("Positive")
else:
    print("Non-positive")
  • C++:
   if (x > 0) {
    std::cout << "Positive";
} else {
    std::cout << "Non-positive";
}

Flare’s Design:

  • Keywords: check, alt, and block-based scoping using { }
check (score > 90) {
    display("A Grade");
} altcheck(score > 50) {
    display("Pass");
} alt {
    display("Try again");
}

Want to simplify parsing? Avoid using colons/indentation-based blocks like Python. Curly braces ({}) make AST construction easier.

4. Loops

What You Must Decide:

  • Will you support while, for, or both?
  • Will for loop use range-based or iterator-style syntax?
  • Do you need loop control keywords like break and continue?

Examples:

  • C++:
for (int i = 0; i < 5; i++) { ... }
while (x < 10) { ... }
  • Python:
for i in range(5): ...
while x < 10: ...

Flare’s Design:

  • Uses a single loop construct with the during keyword (like while)
  • Block required ({}), condition must be a boolean expression

Flare:

claim i = 0;
during i < 5 {
    display i;
    claim i = i + 1;
}

Simpler to implement than for, but you can always add iterator-based loops later.

5. Functions

What You Must Decide:

  • How are functions declared?
  • How do parameters and return types work?
  • Do you support recursion, default parameters, or overloading?

Examples:

  • C++:
int add(int a, int b) {
    return a + b;
}
  • Python: 
def add(a, b):
    return a + b 

Flare’s Design:

  • Functions are declared using the create keyword
  • drop is used to return a value
  • Supports type hints on parameters and return value
  • No overloading or default params yet

Flare:

create add(a, b): int {
    drop a+b;
}

Behind the scenes, you’ll store function names, parameter types, and return types in a symbol table.

6. Input/Output and Inbuilt Functions

What You Must Decide:

  • How will users print output?
  • How will they read input?
  • Will you provide utility functions for strings, lists, math, etc.?

Examples:

  • Python: print(), input(), len(), append()
  • C++: cout, cin, <cmath>, etc.

Flare’s Design:

  • Uses display keyword for printing

Flare:

read(name);
display("hello", name);
  • Reading input will be added later using a simple read() inbuilt
  • Has inbuilt utility functions: len, push, insert, contains etc.

You can internally map these to C++ standard library calls or define your own runtime functions.

Flare — Our Language for This Series

Here’s a quick overview of the design decisions we made for Flare:

Feature

Flare’s Choice

Typing

Dynamic + type hints

Scoping

Lexical

Paradigm

Procedural

Syntax

C-style blocks, readable keywords

Data Types

int, frac, flag, text, letter, list, dict

Loops

loop, during

Functions

create and drop keywords

Variable Decl.

claim for inference, type hints optional

We designed Flare to be:

  • Simple
  • Practical
  • Easy to implement in a compiler

You’ll learn the full internals of how it works as we build it.

What’s Next?

In the next post, we’ll begin writing the lexer — the component that turns code into tokens.

Get ready to:

  • Define your language’s tokens
  • Build a lexer in C++
  • Prepare for parsing and AST construction

This is where your language starts to come alive. See you there!