Now I am become Death, the destroyer of programming languages
As you have probably guessed from the title, This post is about the journey that i have gone through to build this programming language.
Are you in a hurry here is the repo so you can check it out: JPL.#
The Why#
Why a Prog Lang#
Basically I was getting into learning Rust by advent of code Rustlings and it was not a big challenge into learing or let’s say consuming
Rust, and I mean about consuming is the philosophy and the syntax and the quirks of it.
So basically i had 2 reasons:
- I did not want to build an API because clearly it is so repetitive and yeah APIs are APIs in the end.
- I like making parsers and things that takes words and do other things.
- I wanted to get to now how programming languages are designed.
So there you have it a programming language was a fit I guess.
Why JPL#
The name is pretty much self explanatory JSON PROGRAMMING LANGUAGE , and now when I think of it I guess I should’ve called it “JAAP” JSON As A Programming language , but no problemo.
The Choice of the syntax was not easy for me, i had to think of something that can be a troll , something like the BrainrotLang
that has this syntax for the IF / ELSE for example:
edgy (i < 5) {
yapping("i is less than 5");
}
amogus {
yapping("i is 5 or more");
}
but I sticked to JSON because it is well known among devs
The What#
JPL is a programming language “Well I hope so” written on top of rust, it is not garbage collected, in fact its stack is basically a
HashMap<String,Varaible> which it does the job I guess for a small project.
It has I guess 9 keywords declare, redeclare, if, else, cond, range, loop, actions, print, string, int
It feels like writing JSON but waiting for logic to be executed instead of waiting an API response.
basic syntax:
{
"declare": ["factorial","int",1],
"declare": ["num_factorial","int",20],
"if": {
"cond": "$num_factorial <> 0",
"actions": {
"loop": {
"range": ["$i",1,"$num_factorial+1",1],
"actions": {
"if": {
"cond": "$i >= 15",
"actions": {
"print": "It calculated factorial for $i which an Unexpected behavior I guess"
}
},
"redeclare": ["factorial","int","$factorial * $i"],
"print": "$i! so far -> $factorial"
}
}
}
},
"else": {
"actions": {
"print": "Ofc i know factorial of 0 is 1 and negative numbers don't have factorial"
}
}
}
Oh yes this is a valid jpl. And i am not sorry for commiting this crime.
so for its output its
$ jpl test.json
1! so far -> 1
2! so far -> 2
3! so far -> 6
4! so far -> 24
5! so far -> 120
6! so far -> 720
7! so far -> 5040
8! so far -> 40320
9! so far -> 362880
10! so far -> 3628800
11! so far -> 39916800
12! so far -> 479001600
thread 'main' (472367) panicked at src/vm/vm.rs:136:16:
attempt to multiply with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The How#
I guess you have a good attention span if you reached this part. I am going to go about the implemntation
jpl’s compiler/interpreter has 5 components in order for him to make an output:
LEXER -> TOKENIZER -> PARSER -> IR -> VM
1. Tokenizer:#
is basically decomposing raw string/bytes into words based on rules you define like splitting the words by spaces and new lines or by brackets… Tokens generated are related to the programming language philosophy and design.
2. Lexer:#
the words generated by The Tokenizer are iterated through to make tokens or lets say IDs for example an Else Lex or StringLiteral("jpl").
this is a code snippet about the kinds of tokens jpl has JPL/src/lexer/types.rs
```Rust
pub enum Kind {
OPEN_CURLY_BRACE,
CLOSE_CURLY_BRACE,
OPEN_BRACKET,
CLOSE_BRACKET,
DOTS,
COMMA,
NUMBER,
STRING(String),
}
```
3. Parser:#
is the core of the programming language, it takes all the Tokens generated and it regroups them int trees AST(Abstract syntax tree) based on rules you define like for example:DECLARE_STMT = DECLARE + DOTS + OPEN_BRACKET + 3 * UNTREATED + CLOSE_BRACKETS + COMMA
Where we start by capturing the keyword in this case declare and we keep going to the next token until it is fulfilled or panic otherwise.
we obtain in the end something like this
Ast {
keyword: MAIN,
legs: [
Complex(
Ast {
keyword: DECLARE,
legs: [
Simple(
UNTREADED(
"sum",
),
),
Simple(
UNTREADED(
"int",
),
),
Simple(
UNTREADED(
"3 + 14",
),
),
],
},
),
]
}
...
4. IR:#
The Intermidiate Representation, which is a step between the parser and the vm, its goal is to simplify the AST so that it will be easy for us to execute it, think of it as a middleware that takes raw JSON string and prettifies it. so the AST Will look like this after it pases on the IR kinda like a set of operations thae compiler will do:
RegisterVaraiable("sum", INT, Action(PLUS(Value(Int(3)), Value(Int(14)))))
RegisterVaraiable("new", INT, Value(Int(4)))
AlterVariable("sum", INT, Value(Int(100)))
PRINT(Value(String("hello there $sum + $new")))
IF(3)
COND(Action(Compare(Action(VARIABLE("$sum")), Value(Int(10)), "==")))
PRINT(Value(String("yes sum -> $sum is bigger than 10")))
FI(0)
5. VM:#
Virtual machine: reads the IR produced and act upon it, for example this is the evaluation code for comapring :
fn eval_compare(a: Value, b: Value, c: String) -> bool {
let left_arm = match a {
Value::Int(i) => i,
Value::String(_) => {
fatal!("Cannot Compare String")
}
};
let right_arm = match b {
Value::Int(i) => i,
Value::String(_) => {
fatal!("Cannot Compare String")
}
};
match c.as_str() {
">" => left_arm > right_arm,
"<" => left_arm < right_arm,
"==" => left_arm == right_arm,
">=" => left_arm >= right_arm,
"<=" => left_arm <= right_arm,
"!=" | "<>" => left_arm != right_arm,
_ => fatal!("Unknown comparison operator: {}", c),
}
}
The End#
If you wanna check it out you can visit the repo mentioned in the beginning here, you don’t need to thank me for taking you to the beginning. oh you didnot ask me about it’s utility ? ok i will tell you
I don’t actually know,
But I can tell you that I am not going to use it or look at it again,
And you shouldn’t either.
Fix Typos || Suggest Edit ( I promise I will look to it )