Obsidian Lang
- 8 Devlogs
- 12 Total hours
my own programming language
my own programming language
I have finally finished coding the first release of Obsidian!
To install it, head over to the Releases tab I tried to make the setup process as simple as possible. If you run into any trouble, please leave a comment or open an issue and I’ll help you out!
This is the first release, so there will absolutely be rough edges please open an issue with:
Thanks for checking out Obsidian Lang, and happy coding!
going to keep this one short and sweet because I honestly didn’t add much, butt his is what I added:
print "lambda functions and function calls now work!"
func test() -> int {2 + 2};
return test();
Assembly Code For Example:
global obsidian_program
extern GetStdHandle
extern WriteConsoleA
extern ExitProcess
section .text
test:
mov eax, 2
mov ebx, 2
add eax, ebx
ret
obsidian_program:
sub rsp, 40
mov ecx, -11
call GetStdHandle
mov rcx, rax
lea rdx, [rel str0]
mov r8d, 47
lea r9, [rel written]
mov qword [rsp+32], 0
call WriteConsoleA
add rsp, 40
call test
mov ecx, eax
sub rsp, 40
call ExitProcess
section .bss
written resd 1
section .data
str0 db "lambda functions and function calls now work!", 13, 10
Finally got basic lambda style return functions working.
Right now these both compile all the way through the tokenizer, IR, NASM generation, and actually run correctly:
func test() -> int {2 + 2};
func anothertest() -> int {2 - 2};
test currently spits out:
mov eax, 2
mov ebx, 2
add eax, ebx
ret
So the entire pipeline is finally working end to end.
I ended up changing the function syntax halfway through development.
Originally it looked like this:
func test() -> { 2 + 2 }
The problem is -> was basically trying to do two different jobs at once. It was acting like “here comes the function body” instead of what it should actually mean: “here comes the return type.”
That would’ve forced the parser to guess whether the next token was a type or the start of a block, which is just unnecessary pain when the fix is simple.
Now it’s:
func name() -> type { body }
Way cleaner, no ambiguity, and honestly it reads a lot better too.
Under the hood
Parentheses are now their own tokens (_OPEN_PAREN / _CLOSE_PAREN) instead of getting lumped together with braces.
Expressions are currently evaluated by folding left-to-right through (operator, operand) pairs.
+, -, *, and / all work with integer literals.
One limitation right now is there’s no operator precedence yet.
So:
2 + 3 * 4
currently evaluates as:
((2 + 3) * 4) = 20
instead of:
2 + (3 * 4) = 14
Not really a bug, just something I haven’t implemented yet. The real fix is building an expression tree instead of another parser hack.
Right now everything also assumes the operands are integer literals. Variables aren’t hooked into expression evaluation yet, so identifiers basically don’t work inside expressions.
Function bodies are also limited to a single expression for now no multiple statements or nested expressions yet.
Obsidian Lang v1 Dev Log (this is a progress update, not a release announcement)
Obsidian Lang is a programming language I’m building from scratch. The primary goal isn’t the language itself, it’s mastering parsing, interpretation, and assembly generation. So far the project has forced me to get much more comfortable with C++ and x86-64 assembly, which has been the real payoff.
Most languages marketed as “beginner friendly” fall into one of two traps. Some oversimplify to the point of being a dead end (Scratch). Others let you write things that work today but teach habits that fall apart later (Python). Obsidian Lang is my attempt at something different: a language designed so that it effectively cannot crash. Whatever the input, even malformed input, the program keeps running.
The interpreter is built to absorb mistakes rather than halt on them. Errors get logged, execution continues, and the user still gets a running program out the other end.
The pipeline is two passes:
Tokenize. Raw source text is scanned character by character into a flat token stream (Tokens.h / Tokenize.cpp). Keywords (return, print, let, const, func, int), operators (+ - * / = ->), literals, and identifiers are all recognized here.
Interpret. The token stream is walked once to build an intermediate representation, a flat list of Line structs, each tagged with a category (Create, Header, Info, Program, Section, Function), a label, and the emitted text. That IR is then walked a second time in a fixed order defined in docs/archecture.md to produce the final NASM x64 output: global directive, extern headers, .text section, function bodies, the obsidian_program entry point, program body, .bss, .data, and .rdata, in that order.
Currently supported:
print "string" compiles down to a direct Win32 WriteConsoleA call, with the string byte count computed at compile time (including the CRLF).return <int> maps to ExitProcess. Since there’s no branching yet, whichever return is lexically last in the source is the one that determines the actual exit code, the exit sequence itself is always emitted once, at the very end, so later print calls can’t clobber ecx before the process actually exits.const <type> <name> = <value> allocates a .rdata entry.func <name> -> <int> emits a minimal function body with a mov/ret sequence.Operators (+ - * /), let bindings, and full function bodies are tokenized already but not yet wired into codegen, that’s the next chunk of work.
Keeping the IR as one flat, tagged struct instead of a proper AST was a deliberate simplification for v1. It made the two-pass emit order easy to reason about and easy to debug (the interpreter dumps every function name and variable name it collected at the end of a run). It won’t scale forever, but for getting a first working pipeline from source text to a runnable Windows executable, it did the job.
Obsidian Lang – Added functions defined with func <name>(params) I also added a return opperater that kinda mimics a lambda function the token is called _RET_OP same function called but instead you do func<name>(params) -> <return_code>
Obsidian Lang - Fully dynamic coding language written in c++ & assembly. I have added a couple more tokens, but the thing I want to focus on in this dev post is the variable scopes. I know have a let and a const variable scope that allow me to do different things. According to my design const will be mutable. More progress soon
Obsidian Lang Devlog v2
You can now print hello world! Yes you heard that right with our proprietary enterprise insane level 9000 code by typing print "hello world"; it prints hello world!.
This is a huge step because to even get here and be able to use print multiple times etc. there’s a whole sorting algorithm I had to make for my interrupter to sort and build the correct assembly code dynamically. More information here: https://github.com/veefs/obsidian-lang/blob/main/docs/archecture.md
Obsidian Lang v1 - Got the basic assembly code conversation working for my programming language, dealing with a lot of annoying logic stuff related to the assembly code generation, but other than that this is an incredibly fun project already to work on I can’t want to sink in many hours. Obsidian Lang will hopefully be a capable little toy language