blk is a dynamically typed, interpreted language focused on simplicity, expression-oriented design, and minimal syntax. Inspired by Jai, Zig, Odin, and C — but reimagined with flexible semantics and runtime evaluation at its core. Designed for quick scripting, tooling, and prototyping with low ceremony and high expressiveness.
- Expression-oriented: every block returns a value
- Minimal syntax: easy to read and parse
- Dynamically typed, no explicit type declarations
- Interpreted: fast feedback, no build steps required
- Structs, enums, maps, arrays — all built-in
- Powerful block scoping and control flow
- Unified declaration model using
::and:=
import "math"
User :: struct {
name,
age,
greet: fn(self) {
print("Hi, I'm " + self.name)
}
}
fn main() {
u := User{ name: "Ali", age: 22 }
u.greet()
msg := if u.age > 18 {
"Adult"
} else {
"Minor"
}
print(msg)
}
- Dynamic values: no static type annotations
- All variables declared with
:= - Top-level constants via
:: - Structs with inline methods
- Enums
- Pattern matching via
matchexpression - Expression-based blocks and control flow
- Unified literals: maps and structs share
{}syntax - No distinction between expressions and statements
x := 42
msg :: "Welcome to blk"
greet :: fn(name) {
print("Hello " + name)
}
Vec2 :: struct {
x := 0.0,
y := 0.0,
len: fn() {
sqrt(x * x + y * y)
}
}
v := Vec2{
x: 3,
y: 4
}
print(v.len())
Result :: enum {
Ok,
Error
}
# regular if
name := if loggedIn {
"User"
} else {
"Guest"
}
# ternary-like if
user := User{ name: "Alice", age: 22 }
# with use/else tokens
age := if user.age > 18 use "Adult" else "Minor"
# with ?/: tokens
age := if user.age > 18 ? "Adult" : "Minor"
kind := match x {
0 => "zero",
1 => "one",
_ => "other"
}
i := 0
while i < 5 {
print(i)
i += 1
}
for idx, val in [1, 2, 3] {
print(idx, val)
}
for k, v in {a: 1, b: 2} {
print(k, v)
}
idea of name next suggested by @gaurangrshah
while true {
if shouldSkip() {
next
}
doStuff()
}
import "math"
import "utils"
for aliasing use the as keyword:
import "custom.blk" as mod
nums := [1, 2, 3]
names := ["foo", "bar"]
config := {
"host": "localhost",
"port": "8080"
}
person := Person{
name: "Zed",
age: 20
}
this is a special value representing the absence of a value, similar to null/nil in other languages. It can be used in any context where a value is expected.
idea of name nul suggested by @unmarine
x := nul # Represents a null value
Every code block is an expression. The last expression is the return value of the block — no return keyword required.
double := fn(x) {
x * 2
}
result := fn(x, y) {
if x > y {
x
} else {
y
}
}
print(result(10, 20)) # 20
Types are tracked at runtime via introspection:
typeOf(x) == types.INTEGER
Note: types can be found in the types module.
- Lexer and Tokenizer
- Parser and AST
- Core Interpreter Engine
- REPL
- Built-in Modules (math, strings, hashmap, array, types)
- Error System and Stack Traces
blk run -f ./main.blkNOTE: the project ins't finished yet. Expect bugs and breaking changes, don't use it for production.