cowlang is a small programming language and compiler written from scratch in Java.
The compiler uses a hand-written lexer and recursive-descent parser to build an abstract syntax tree, then translates the program into LLVM IR. The generated LLVM can be assembled and compiled into a native executable using LLVM and Clang.
The project is primarily built as a way to learn how compilers work internally, from source code all the way down to native machine code.
x <- 3 > 4;
x <- x + (4 > 3);
yell x;
Output:
1
Comparisons evaluate to integers:
false -> 0
true -> 1
cowlang currently supports:
- Integer literals
- Variables
- Assignment using
<- - Arithmetic expressions
- Comparison expressions
- Parentheses
yellfor output with a newlinewhisperfor output without a newlinewhen/otherwisecontrol flow//comments
x <- 10;
y <- x + 5;
Variables are created when they are first assigned.
x <- 10 + 5;
y <- x * 2;
z <- (x + y) / 3;
Supported operators:
+
-
*
/
Normal operator precedence is used.
For example:
x <- 10 + 5 * 2;
is interpreted as:
10 + (5 * 2)
x <- 10 > 5;
y <- 3 == 4;
Supported comparison operators:
==
!=
<
<=
>
>=
Comparison results are represented as integers in cowlang:
true = 1
false = 0
This means comparisons can be used inside other expressions:
x <- 5 + (10 > 3);
which evaluates to:
6
yell prints a value followed by a newline:
yell 42;
whisper prints without a newline:
whisper 42;
The compiler currently consists of four main stages:
cowlang source code
|
v
Lexer
|
v
Tokens
|
v
Parser
|
v
AST
|
v
LLVM IR Generator
|
v
LLVM IR (.ll)
|
v
LLVM
|
v
Clang
|
v
Native executable
The lexer converts source code into tokens.
For example:
x <- 10 + 5;
becomes conceptually:
IDENTIFIER("x")
ASSIGN("<-")
INTEGER("10")
PLUS("+")
INTEGER("5")
SEMICOLON(";")
The parser is a hand-written recursive-descent parser.
Expression precedence is handled through separate parsing levels for comparisons, addition/subtraction and multiplication/division.
For example:
x <- 10 + 20 * 3;
produces an AST equivalent to:
Assignment
└── x
└── Add
├── 10
└── Multiply
├── 20
└── 3
The backend traverses the AST and emits textual LLVM IR.
A cowlang expression such as:
x <- 3 > 4;
can generate LLVM IR similar to:
%tmp.0 = icmp sgt i32 3, 4
%tmp.1 = zext i1 %tmp.0 to i32
%var.x = alloca i32
store i32 %tmp.1, ptr %var.xLLVM comparisons return an i1, which is converted to i32 so that cowlang can represent boolean results as 0 and 1.
The project requires:
- Java
- Gradle
- LLVM
- Clang
The Gradle wrapper is included in the repository.
On Linux/macOS:
./gradlew buildOn Windows:
.\gradlew.bat buildA cowlang source file uses the .cow extension.
Example:
app/examples/hello.cow
The Java compiler can be run through Gradle.
On Linux:
./gradlew run --args="examples/hello.cow"On Windows:
.\gradlew.bat run --args="examples\hello.cow"The compiler generates an LLVM .ll file.
The generated LLVM IR can be assembled using llvm-as:
llvm-as program.ll -o program.bcThe resulting LLVM bitcode can then be compiled using Clang:
clang program.bc -o programRun the resulting executable:
./programOn Windows, the resulting file can instead be compiled as an .exe.
The repository contains helper scripts intended to automate the compilation pipeline.
compile.ps1
The PowerShell script automates the process from:
.cow
-> .ll
-> .bc
-> .exe
compile.sh
The Bash version provides the equivalent compilation workflow on Linux.
The compiler can currently compile basic integer programs from cowlang source code into LLVM IR and then into native executables.
Working features include:
- Lexical analysis
- Recursive-descent parsing
- Abstract syntax tree generation
- Integer variables
- Arithmetic expressions
- Expression precedence
- Parenthesized expressions
- Comparison expressions
- Integer output
- LLVM IR generation
- Native compilation through LLVM and Clang
- Conditional control flow using LLVM basic blocks and branches
Some syntax is already represented in the frontend but does not yet have complete LLVM code generation.
Future work includes:
whileloopsloop N times- Functions using
~> - Function parameters
return- String code generation
- Variable scopes
- Semantic analysis
- Improved type checking
- Better compiler diagnostics
- AST and token dump options
- Compiler optimizations
- Automated tests
cowlang is not intended to compete with production programming languages.
The goal of the project is to explore and implement the major components of a compiler:
- Lexical analysis
- Parsing
- Syntax trees
- Semantic analysis
- Symbol handling
- Intermediate representations
- Control flow
- Code generation
- Native compilation
Rather than relying on a parser generator or an existing compiler frontend, the major compiler stages are implemented manually in order to better understand how they work.
The official LLVM documentation is the best reference for LLVM IR syntax, instructions, types, control flow, and other language details:
It is especially useful when implementing the LLVM backend and looking up instructions such as icmp, br, zext, load, and store.
A useful resource for learning how to build a first compiler frontend with LLVM is the official LLVM tutorial:
The tutorial covers many of the same core concepts used in this project, including lexing, parsing, AST construction, control flow, and LLVM IR generation.
For a deeper understanding of compiler design and implementation:
- Andrew W. Appel — Modern Compiler Implementation in C (1998)
Although the book uses C rather than Java (there exists a book by the same author for Java, but I do not own that book unfortunately), its discussion of compiler fundamentals is largely language-agnostic. It covers topics such as lexical analysis, parsing, abstract syntax trees, semantic analysis and more.