A Scots programming language - pure havers, but working havers!
mdhavers is a dynamically-typed programming language that uses Scots vocabulary for its keywords and produces error messages in Scots dialect. It's a fully-featured language with an interpreter, a JavaScript compiler, and a friendly REPL.
# Build the project
cargo build --release
# Run a .braw file
./target/release/mdhavers examples/hello.braw
# Start the interactive REPL
./target/release/mdhavers repl
# Compile to JavaScript
./target/release/mdhavers compile examples/fizzbuzz.braw -o fizzbuzz.js
node fizzbuzz.js
# Compile to WebAssembly Text format
./target/release/mdhavers wasm examples/functions.braw -o functions.wat
# Try the web playground
cd playground && ./build.sh && cd web && python3 -m http.server 8080
# Play Tetris!
open games/tetris/index.htmlUse ken (meaning "to know") to declare variables:
ken name = "Hamish"
ken age = 42
ken pi = 3.14159
ken is_scottish = aye
ken is_english = nae
ken nothing = naething
- Integers:
42,-17,0 - Floats:
3.14,-0.5 - Strings:
"Hello, Scotland!"or'single quotes too' - Booleans:
aye(true),nae(false) - Null:
naething - Lists:
[1, 2, 3] - Dictionaries:
{"name": "Hamish", "age": 42}
gin temperature < 10 {
blether "It's pure Baltic oot there!"
} ither gin temperature < 20 {
blether "It's a wee bit nippy."
} ither {
blether "Rare weather fer Scotland!"
}
ken count = 1
whiles count <= 5 {
blether count
count = count + 1
}
# Loop over a range
fer i in 1..10 {
blether i
}
# Loop over a list
ken cities = ["Edinburgh", "Glasgow", "Aberdeen"]
fer city in cities {
blether city
}
fer i in 1..100 {
gin i > 10 {
brak # Break out of loop
}
gin i % 2 == 0 {
haud # Continue to next iteration
}
blether i
}
Use dae (meaning "to do") to define functions, and gie (meaning "to give") to return values:
dae greet(name) {
blether "Hullo, " + name + "!"
}
dae add(a, b) {
gie a + b
}
dae factorial(n) {
gin n <= 1 {
gie 1
}
gie n * factorial(n - 1)
}
greet("Hamish")
blether add(5, 3)
blether factorial(5)
Functions can hae default values fer parameters (staundart values):
# Parameter with default value
dae greet(name, greeting = "Hullo") {
blether f"{greeting}, {name}!"
}
greet("Hamish") # Uses default: "Hullo, Hamish!"
greet("Morag", "Och aye") # Custom: "Och aye, Morag!"
# Multiple defaults
dae make_order(item, quantity = 1, price = 10) {
gie quantity * price
}
make_order("haggis") # 1 * 10 = 10
make_order("whisky", 3) # 3 * 10 = 30
make_order("shortbread", 5, 2) # 5 * 2 = 10
# Defaults with lambda values
dae process(items, transform = |x| x) {
gie gaun(items, transform)
}
Note: Parameters wi' defaults must come efter parameters wi'oot defaults.
Unpack lists and strings intae individual variables:
# Basic destructuring
ken [a, b, c] = [1, 2, 3]
blether a # 1
# With rest pattern (...) tae capture remaining elements
ken [first, ...rest] = [1, 2, 3, 4, 5]
blether first # 1
blether rest # [2, 3, 4, 5]
# Rest in the middle
ken [head, ...middle, tail] = [1, 2, 3, 4, 5]
blether middle # [2, 3, 4]
# Ignore elements with _
ken [_, second, _] = ["skip", "take", "skip"]
blether second # "take"
# Destructure strings intae characters
ken [a, b, c] = "ABC"
blether a # "A"
# Practical: process function returns
dae get_bounds(list) {
ken sorted = sort(list)
gie [heid(sorted), bum(sorted)]
}
ken [min_val, max_val] = get_bounds([5, 2, 8, 1])
Use kin (meaning "family/type") to define classes:
kin Animal {
dae init(name, sound) {
masel.name = name
masel.sound = sound
}
dae speak() {
blether masel.name + " says: " + masel.sound
}
}
ken dug = Animal("Dug", "Woof!")
dug.speak() # Output: Dug says: Woof!
Use fae (from) for class inheritance:
kin Animal {
dae init(name) {
masel.name = name
}
dae speak() {
blether masel.name + " makes a noise"
}
}
kin Dog fae Animal {
dae speak() {
blether masel.name + " says: Woof!"
}
dae wag() {
blether masel.name + " wags tail!"
}
}
ken rex = Dog("Rex")
rex.speak() # Rex says: Woof!
rex.wag() # Rex wags tail!
Define special methods tae customise how operators work wi' yer classes:
| Method | Operator | Scots Meaning |
|---|---|---|
__pit_thegither__(that) |
+ |
Put together (add) |
__tak_awa__(that) |
- |
Take away (subtract) |
__times__(that) |
* |
Multiply |
__pairt__(that) |
/ |
Divide |
__lave__(that) |
% |
Remainder (what's left) |
__same_as__(that) |
== |
Equal |
__differs_fae__(that) |
!= |
Not equal |
__wee_er__(that) |
< |
Smaller (less than) |
__wee_er_or_same__(that) |
<= |
Smaller or same |
__muckle_er__(that) |
> |
Bigger (greater than) |
__muckle_er_or_same__(that) |
>= |
Bigger or same |
kin Vector {
dae init(x, y) {
masel.x = x
masel.y = y
}
dae __pit_thegither__(that) {
gie Vector(masel.x + that.x, masel.y + that.y)
}
dae __times__(scalar) {
gie Vector(masel.x * scalar, masel.y * scalar)
}
}
ken v1 = Vector(3, 4)
ken v2 = Vector(1, 2)
ken v3 = v1 + v2 # Vector(4, 6)
ken v4 = v1 * 2 # Vector(6, 8)
Use hae_a_bash (have a bash/try) and gin_it_gangs_wrang (if it goes wrong/catch):
hae_a_bash {
ken result = 10 / 0
blether result
} gin_it_gangs_wrang err {
blether "Caught error: " + err
}
# Nested error handling
hae_a_bash {
blether "Trying something risky..."
hae_a_bash {
ken x = undefined_var
} gin_it_gangs_wrang inner {
blether "Inner catch: " + inner
}
blether "Continuing..."
} gin_it_gangs_wrang outer {
blether "Outer catch: " + outer
}
Use keek (peek/look at) and whan (when) for pattern matching:
ken day = 3
keek day {
whan 1 -> { blether "Monday" }
whan 2 -> { blether "Tuesday" }
whan 3 -> { blether "Wednesday" }
whan _ -> { blether "Another day" }
}
# Match with variable binding
ken mystery = 42
keek mystery {
whan 0 -> { blether "Zero" }
whan x -> { blether "Got: " + tae_string(x) }
}
# Match in a function
dae describe(n) {
keek n {
whan 0 -> { gie "naething" }
whan 1 -> { gie "ane" }
whan 2 -> { gie "twa" }
whan _ -> { gie "mony" }
}
gie "unknown"
}
Anonymous functions use pipe syntax:
ken add = |a, b| a + b
ken square = |x| x * x
blether add(3, 4) # 7
blether square(5) # 25
Use ... tae skail (scatter/spread) lists and strings:
# Combine lists
ken a = [1, 2, 3]
ken b = [4, 5, 6]
ken combined = [...a, ...b] # [1, 2, 3, 4, 5, 6]
# Insert elements
ken middle = [0, ...a, 99] # [0, 1, 2, 3, 99]
# Spread in function calls
dae sum_three(x, y, z) {
gie x + y + z
}
ken nums = [10, 20, 30]
blether sum_three(...nums) # 60
# Spread strings intae characters
ken letters = [..."abc"] # ["a", "b", "c"]
Use |> fer fluent function chaining. The value on the left gets passed as the argument tae the function on the right:
# Basic pipe: value |> function
ken result = 5 |> |x| x * 2 # 10
# Chain multiple operations
dae add_one(x) { gie x + 1 }
dae triple(x) { gie x * 3 }
ken chained = 5 |> add_one |> triple |> add_one # 19
# Same as: add_one(triple(add_one(5)))
# Works with built-in functions
ken text = " hello " |> wheesht |> upper # "HELLO"
# Data processing pipeline
ken numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
dae get_evens(list) { gie sieve(list, |x| x % 2 == 0) }
dae double_all(list) { gie gaun(list, |x| x * 2) }
ken total = numbers |> get_evens |> double_all |> sumaw # 60
Scots-named functional programming:
ken nums = [1, 2, 3, 4, 5]
# gaun - map (Scots: "going over")
ken doubled = gaun(nums, |x| x * 2) # [2, 4, 6, 8, 10]
# sieve - filter
ken evens = sieve(nums, |x| x % 2 == 0) # [2, 4]
# tumble - reduce/fold (Scots: "tumble together")
ken sum = tumble(nums, 0, |acc, x| acc + x) # 15
# ilk - for-each (Scots: "each")
ilk(nums, print_func)
Use mak_siccar (meaning "make sure" - famously said by Robert the Bruce!) for testing and validation:
ken x = 5
mak_siccar x == 5, "x should be 5"
mak_siccar x > 0 # without message
dae factorial(n) {
mak_siccar n >= 0, "n must be non-negative"
gin n <= 1 { gie 1 }
gie n * factorial(n - 1)
}
Format strings with {expression} interpolation:
ken name = "Hamish"
ken age = 42
blether f"Hullo, {name}! Ye are {age} years auld."
blether f"Twa plus three is {2 + 3}"
blether f"Cities: {["Edinburgh", "Glasgow"]}"
# Print output
blether "Hullo, World!"
# Get user input
ken name = speir "Whit's yer name? "
blether "Nice tae meet ye, " + name
Use fetch to import other .braw files:
# Import a module (all exports available directly)
fetch "lib/maths"
blether square(5) # 25
# Import with an alias (namespace)
fetch "lib/strings" tae str
blether str["capitalize"]("hello") # "Hello"
Modules are resolved relative to the current file's directory. The .braw extension is optional.
Scots-Flavored Functions (the guid stuff!):
| Function | Scots Word Meaning | Description |
|---|---|---|
heid(x) |
head | Get first element |
bum(x) |
backside | Get last element |
tail(x) |
tail | All but first element |
scran(x, start, end) |
food/grab | Slice a portion |
slap(a, b) |
friendly slap | Concatenate lists/strings |
sumaw(list) |
sum all | Sum all numbers |
coont(x, y) |
count | Count occurrences |
wheesht(str) |
be quiet | Trim whitespace |
shuffle(list) |
- | Randomly shuffle |
jammy(min, max) |
lucky | Random integer in range |
the_noo() |
the now | Current timestamp |
clype(x) |
tell/snitch | Debug print with type |
drap(list, n) |
drop | Drop first n elements |
tak(list, n) |
take | Take first n elements |
grup(list, n) |
grip/group | Group into chunks of n |
pair_up(list) |
- | Create pairs from list |
fankle(a, b) |
tangle | Interleave two lists |
stoater(list) |
great one | Get best/max element |
braw(x) |
good/fine | Check if value is "good" |
clarty(x) |
dirty/messy | Check for duplicates |
dreich(str) |
dull/boring | Check if string is monotonous |
scottify(str) |
- | Convert English to Scots |
snooze(ms) |
- | Sleep for milliseconds |
indices_o(x, val) |
indices of | Find all indices of value |
braw_date(ts) |
braw date | Format date in Scottish style |
grup_up(list, fn) |
group up | Group elements by function |
pairt_by(list, fn) |
part by | Partition by predicate |
haverin(x) |
talking havers | Check if value is empty/nonsense |
scunner(x) |
disgusting | Check if value is negative/empty |
bonnie(x) |
pretty | Decorate value: "~~~ x ~~~" |
is_wee(x) |
is wee | Check if value is small |
is_muckle(x) |
is big | Check if value is large |
crabbit(n) |
grumpy | Check if number is negative |
roar(str) |
shout | Uppercase with "!": shout |
cannie(x) |
careful | Check if value is safe/valid |
glaikit(x) |
silly | Check if value is empty/zero/invalid |
wrang_sort(x, type) |
wrong sort | Check if wrong type |
tattie_scone(s, n) |
potato scone | Repeat string with | separator |
haggis_hunt(s, needle) |
haggis hunt | Find all occurrences of substring |
sporran_fill(s, w, c) |
sporran fill | Center-pad string |
blether_format(s, d) |
format | Format string with dict placeholders |
ceilidh(l1, l2) |
dance | Interleave two lists like dancers |
dram(list) |
wee drink | Get random element from list |
birl(list, n) |
spin | Rotate list by n positions |
stooshie(str) |
chaos | Shuffle string characters |
clype(x) |
tell tales | Get debug info about a value |
sclaff(list) |
hit flat | Fully flatten nested lists |
Scottish Exclamation Functions:
| Function | Description |
|---|---|
och(msg) |
Express disappointment: "Och! {msg}" |
jings(msg) |
Express surprise: "Jings! {msg}" |
crivvens(msg) |
Express astonishment: "Crivvens! {msg}" |
help_ma_boab(msg) |
Express extreme surprise: "Help ma boab! {msg}" |
roar(str) |
Shout: uppercase with "!" |
mutter(str) |
Whisper: "...{lowercase}..." |
blooter(str) |
Scramble string randomly |
numpty_check(x) |
Validate input with Scots feedback |
Timing/Benchmarking Functions:
| Function | Description |
|---|---|
noo() |
Current timestamp in milliseconds ("now") |
tick() |
High-precision timestamp in nanoseconds |
bide(ms) |
Sleep for milliseconds ("bide" = wait) |
File I/O Functions:
| Function | Description |
|---|---|
scrieve(path, content) |
Write to file (Scots: write) |
read_file(path) |
Read entire file |
read_lines(path) |
Read file as list of lines |
append_file(path, content) |
Append to file |
file_exists(path) |
Check if file exists |
Standard Functions:
| Function | Description |
|---|---|
len(x) |
Length of string, list, or dict |
whit_kind(x) |
Type of value |
tae_string(x) |
Convert to string |
tae_int(x) |
Convert to integer |
tae_float(x) |
Convert to float |
tae_bool(x) |
Convert to boolean |
shove(list, x) |
Append to list (push) |
yank(list) |
Pop from list |
keys(dict) |
Get dictionary keys |
values(dict) |
Get dictionary values |
sort(list) |
Sort a list |
reverse(x) |
Reverse list or string |
contains(x, y) |
Check if x contains y |
split(str, delim) |
Split string |
join(list, delim) |
Join list to string |
upper(str) |
Convert to uppercase |
lower(str) |
Convert to lowercase |
pad_left(s, w, c) |
Pad string on left |
pad_right(s, w, c) |
Pad string on right |
is_a(x, type) |
Type checking |
Math Functions:
| Function | Description |
|---|---|
abs(n) |
Absolute value |
min(a, b) |
Minimum value |
max(a, b) |
Maximum value |
sqrt(n) |
Square root |
floor(n) |
Floor |
ceil(n) |
Ceiling |
round(n) |
Round |
pooer(x, y) |
Power/exponent (x^y) |
sin(n) |
Sine (radians) |
cos(n) |
Cosine (radians) |
tan(n) |
Tangent (radians) |
asin(n) |
Arc sine |
acos(n) |
Arc cosine |
atan(n) |
Arc tangent |
atan2(y, x) |
Two-argument arc tangent |
hypot(x, y) |
Hypotenuse (sqrt(x² + y²)) |
log(n) |
Natural logarithm |
log10(n) |
Base 10 logarithm |
exp(n) |
e raised to the power |
degrees(rad) |
Convert radians to degrees |
radians(deg) |
Convert degrees to radians |
sign(n) |
Sign of number (-1, 0, or 1) |
clamp(n, min, max) |
Constrain value between min and max |
lerp(a, b, t) |
Linear interpolation |
gcd(a, b) |
Greatest common divisor |
lcm(a, b) |
Least common multiple |
factorial(n) |
Calculate factorial (max 20) |
is_even(n) |
Check if number is even |
is_odd(n) |
Check if number is odd |
is_prime(n) |
Check if number is prime |
PI |
Pi constant (3.14159...) |
E |
Euler's number (2.71828...) |
TAU |
Tau constant (2π) |
Bitwise Operations:
| Function | Description |
|---|---|
bit_an(a, b) |
Bitwise AND |
bit_or(a, b) |
Bitwise OR |
bit_xor(a, b) |
Bitwise XOR |
bit_nae(n) |
Bitwise NOT |
bit_shove_left(n, shift) |
Left shift |
bit_shove_right(n, shift) |
Right shift |
bit_coont(n) |
Count set bits (popcount) |
tae_binary(n) |
Convert to binary string |
tae_hex(n) |
Convert to hexadecimal string |
tae_octal(n) |
Convert to octal string |
fae_binary(s) |
Parse binary string to integer |
fae_hex(s) |
Parse hex string to integer |
Dictionary Functions:
| Function | Description |
|---|---|
dict_merge(d1, d2) |
Merge two dictionaries |
dict_get(d, key, default) |
Get value with default |
dict_has(d, key) |
Check if key exists |
dict_remove(d, key) |
Remove key from dictionary |
dict_invert(d) |
Swap keys and values |
items(d) |
Get list of [key, value] pairs |
fae_pairs(list) |
Create dict from pairs |
List Statistics:
| Function | Description |
|---|---|
average(list) |
Calculate mean |
median(list) |
Calculate median |
product(list) |
Multiply all numbers |
minaw(list) |
Find minimum in list |
maxaw(list) |
Find maximum in list |
range_o(list) |
Calculate range (max - min) |
Assertion Functions:
| Function | Description |
|---|---|
assert(cond, msg) |
Assert condition is true |
assert_equal(a, b) |
Assert two values are equal |
assert_nae_equal(a, b) |
Assert two values are not equal |
mak_siccar(cond, msg) |
Assert (like Robert the Bruce!) |
More String Functions:
| Function | Description |
|---|---|
center(s, width, fill) |
Center string in field |
is_upper(s) |
Check if all uppercase |
is_lower(s) |
Check if all lowercase |
swapcase(s) |
Swap case of letters |
strip_left(s, chars) |
Strip leading characters |
strip_right(s, chars) |
Strip trailing characters |
replace_first(s, from, to) |
Replace first occurrence |
substr_between(s, start, end) |
Get substring between markers |
| Scots | English | Usage |
|---|---|---|
ken |
know | Variable declaration |
gin |
if | Conditional |
ither |
other/else | Else clause |
whiles |
while | While loop |
fer |
for | For loop |
gie |
give | Return |
blether |
chat/talk | |
speir |
ask | Input |
dae |
do | Function definition |
kin |
family/type | Class definition |
thing |
thing | Struct definition |
aye |
yes | True |
nae |
no | False / Not |
naething |
nothing | Null |
an |
and | Logical AND |
or |
or | Logical OR |
brak |
break | Break from loop |
haud |
hold | Continue in loop |
masel |
myself | Self reference |
in |
in | For loop iteration |
hae_a_bash |
have a bash | Try block |
gin_it_gangs_wrang |
if it goes wrong | Catch block |
keek |
peek/look | Match statement |
whan |
when | Match case |
fetch |
fetch | Import module |
tae |
to | Module alias (fetch ... tae name) |
mak_siccar |
make sure | Assert (like Robert the Bruce!) |
... |
skail (scatter) | Spread operator fer lists |
|> |
pipe | Pipe operator fer chaining |
# Run a file
mdhavers run program.braw
mdhavers program.braw # shorthand
# Start REPL
mdhavers repl
mdhavers # shorthand
# Compile to JavaScript
mdhavers compile program.braw
mdhavers compile program.braw -o output.js
# Compile to WebAssembly Text format (WAT)
mdhavers wasm program.braw
mdhavers wasm program.braw -o output.wat
# Check for errors
mdhavers check program.braw
# Format code (makes it look braw!)
mdhavers fmt program.braw
mdhavers fmt program.braw --check # check only, dinnae modify
# Show tokens (debug)
mdhavers tokens program.braw
# Show AST (debug)
mdhavers ast program.braw
# Trace execution (step-by-step with Scottish commentary!)
mdhavers trace program.braw # statements only
mdhavers trace program.braw -v # verbose mode (shows values too)mdhavers can compile yer code tae multiple targets fer running in different environments.
Compile mdhavers code tae JavaScript fer running in browsers or Node.js:
# Compile to JavaScript
mdhavers compile fizzbuzz.braw -o fizzbuzz.js
# Run with Node.js
node fizzbuzz.jsExample - FizzBuzz in mdhavers:
fer i in 1..101 {
gin i % 15 == 0 {
blether "FizzBuzz"
} ither gin i % 3 == 0 {
blether "Fizz"
} ither gin i % 5 == 0 {
blether "Buzz"
} ither {
blether i
}
}
Compiled JavaScript output:
for (let i = 1; i < 101; i++) {
if ((i % 15) === 0) {
console.log("FizzBuzz");
} else if ((i % 3) === 0) {
console.log("Fizz");
} else if ((i % 5) === 0) {
console.log("Buzz");
} else {
console.log(i);
}
}Compile tae WAT fer high-performance execution:
# Compile to WAT
mdhavers wasm maths.braw -o maths.wat
# Convert WAT to WASM using wat2wasm (from wabt toolkit)
wat2wasm maths.wat -o maths.wasmExample - Simple maths function:
dae add(a, b) {
gie a + b
}
Compiled WAT output:
(module
(func $add (param $a i64) (param $b i64) (result i64)
(i64.add
(local.get $a)
(local.get $b)))
(export "add" (func $add)))You can also compile programmatically using mdhavers as a library:
use mdhavers::{compile_to_js, compile_to_wat};
fn main() {
let source = r#"
dae greet(name) {
blether "Hello, " + name + "!"
}
greet("World")
"#;
// Compile to JavaScript
let js = compile_to_js(source).unwrap();
println!("JavaScript:\n{}", js);
// Compile to WAT
let wat = compile_to_wat(source).unwrap();
println!("WAT:\n{}", wat);
}mdhavers includes a web-based playground fer experimenting with code directly in yer browser.
- Live Code Execution: Run mdhavers code client-side using WebAssembly
- Syntax Highlighting: Beautiful dark theme with JetBrains Mono font
- Code Formatting: Auto-format yer code with one click
- JavaScript Compilation: View the compiled JavaScript output
- Example Code: Built-in examples covering all language features
- Share Links: Share yer code via URL
# Navigate to the playground directory
cd playground
# Build the WASM module (requires wasm-pack)
./build.sh
# Or manually:
wasm-pack build --target web
cp -r pkg web/
# Serve the playground
cd web
python3 -m http.server 8080
# Open http://localhost:8080 in your browser| Shortcut | Action |
|---|---|
Ctrl+Enter |
Run code |
Ctrl+Shift+F |
Format code |
Tab |
Insert 4 spaces |
Escape |
Close modal |
The playground/web/ directory contains everything needed fer static hosting (GitHub Pages, Netlify, Vercel, etc.).
A fully-featured Tetris implementation written in mdhavers! The game demonstrates classes, control flow, and complex game logic.
Browser Version (recommended):
# Open the game directly in your browser
open games/tetris/index.html
# Or serve it locally
cd games/tetris
python3 -m http.server 8080
# Visit http://localhost:8080Controls:
| Key | Action |
|---|---|
← / → |
Move piece left/right |
↓ |
Soft drop (move down faster) |
↑ |
Rotate piece |
Space |
Hard drop (instant drop) |
P |
Pause/Resume |
On mobile devices, use the on-screen buttons.
- Classic Tetris gameplay with all 7 tetromino pieces
- Scottish-themed colours:
- I (Turquoise) - Like the Scottish sea
- O (Gold) - Like whisky
- T (Purple) - Like heather
- S (Green) - Like the Highlands
- Z (Orange-red) - Like a sunset
- J (Royal blue) - Like the Saltire
- L (Orange) - Like Irn-Bru
- Ghost piece showing where yer piece will land
- Wall kicks fer rotation near edges
- Level progression (speeds up every 10 lines)
- Scoring system:
- 1 line: 100 points × level
- 2 lines: 300 points × level
- 3 lines: 500 points × level
- 4 lines (Tetris!): 800 points × level
- Hard drop bonus: 2 points per cell
The game logic is written entirely in mdhavers (games/tetris/tetris.braw):
# Tetris piece definitions
ken SHAPES = {
"I": [[0, 0], [0, 1], [0, 2], [0, 3]],
"O": [[0, 0], [0, 1], [1, 0], [1, 1]],
"T": [[0, 0], [0, 1], [0, 2], [1, 1]],
# ... more shapes
}
# Game class
kin TetrisGame {
dae init() {
masel.board = masel.create_empty_board()
masel.score = 0
masel.level = 1
masel.spawn_piece()
}
dae move_down() {
gin nae masel.check_collision(1, 0) {
masel.current_row = masel.current_row + 1
} ither {
masel.lock_piece()
masel.clear_lines()
masel.spawn_piece()
}
}
# ... more game logic
}
mdhavers includes a tracer mode that shows step-by-step execution with Scottish commentary. It's pure dead brilliant for debugging!
# Basic trace - shows statements as they execute
mdhavers trace program.braw
# Verbose trace - shows values and more detail
mdhavers trace program.braw -vExample output:
════════════════════════════════════════════════════════════
🏴 mdhavers Tracer - Watchin' Yer Code Like a Hawk!
Mode: Verbose (showin' everything)
════════════════════════════════════════════════════════════
🏴 [line 5] ken name = ...
→ name is noo Hamish
🏴 [line 6] gin (if) statement
→ condition is aye
🏴 [line 6] condition is aye - takin' then branch
🏴 [line 6] enterin' block
🏴 [line 7] blether (print): Welcome!
🏴 [line 6] leavin' block
🏴 [line 10] fer (for) loop: i in ...
→ iteratin' ower 3 items
→ iteration 1: i = 1
🏴 [line 10] fer loop done after 3 iterations
════════════════════════════════════════════════════════════
🏴 Trace complete - Pure dead brilliant!
════════════════════════════════════════════════════════════
The tracer shows:
- Variable declarations with their values
- Control flow (if/gin, loops with iteration counts)
- Function calls and returns
- Try/catch blocks with error details
- Pattern matching with which arm matched
The REPL (Read-Eval-Print Loop) provides an interactive environment for experimenting with mdhavers:
mdhavers repl # Start the REPL
mdhavers # Also starts REPL if no file given| Command | Description |
|---|---|
help |
Show help message |
quit / haud yer wheesht |
Exit the REPL |
clear |
Clear the screen |
reset |
Reset interpreter (clear all variables) |
wisdom |
Get a Scottish proverb |
examples |
Show example code snippets |
trace |
Toggle trace mode (see execution step-by-step) |
trace v |
Toggle verbose trace mode |
vars / env |
Show all defined variables |
You can enable tracing directly in the REPL to debug code interactively:
mdhavers> trace
🏴 Trace mode ON - watchin' yer code like a hawk!
mdhavers[trace]> ken x = 42
🏴 [line 1] ken x = ...
mdhavers[trace]> trace
Trace mode OFF - back tae normal.
mdhavers gives you error messages in Scots:
Och! Ah dinnae ken whit 'xyz' is at line 5, column 3
Haud yer wheesht! Unexpected '}' at line 10 - ah wis expectin' ')'
Awa' an bile yer heid! 'foo' hasnae been defined yet at line 7
Ye numpty! Tryin' tae divide by zero at line 12
That's pure mince! Type error at line 8: Cannae add integer an' string
Yer bum's oot the windae! Function 'greet' expects 1 arguments but ye gave it 3
See the examples/ directory for sample programs:
examples/hello.braw- Hello Worldexamples/fizzbuzz.braw- Classic FizzBuzz (Scottish style!)examples/functions.braw- Functions, defaults, recursion, lambdas
examples/basics/variables.braw- Variables and basic typesexamples/basics/control_flow.braw- If/else, loops, break/continueexamples/basics/classes.braw- Classes and methodsexamples/basics/inheritance.braw- Inheritance withfaeexamples/basics/operator_overload.braw- Operator overloadingexamples/basics/match.braw- Pattern matching withkeekexamples/basics/fstrings.braw- F-string interpolationexamples/basics/pipe.braw- Pipe operator (|>)examples/basics/defaults.braw- Default parametersexamples/basics/spread.braw- Spread operator and destructuringexamples/basics/higher_order.braw- Lambdas and HOF builtins
examples/basics/assert_demo.braw- Assertions withmak_siccarexamples/basics/file_io.braw- File I/O and filesystem helpersexamples/basics/modules_demo.braw- Importing modules withfetch
examples/features/audio_basics.braw- Audio playbackexamples/features/graphics_basics.braw- 2D graphicsexamples/features/graphics3d_basics.braw- 3D graphics (tri)examples/features/threading_basics.braw- Threads, atomics, channelsexamples/features/logging_basics.braw- Logging stdlib demoexamples/features/logging_keywords.braw- log_* statements and spansexamples/features/try_catch.braw- Try/catch with hurl
examples/stdlib/collections_demo.braw- Stacks, queues, sets
examples/showcases/soond_showcase.braw- Audio and music showcaseexamples/showcases/sip_rtp_echo.braw- SIP/RTP echo demoexamples/showcases/tri_showcase/tri_showcase.braw- 3D scene demo
examples/lib/collections.braw- Data structures and utilitiesexamples/lib/functional.braw- Higher‑order helpersexamples/lib/logging.braw- Logging helpersexamples/lib/testing.braw- Simple testing helpersexamples/lib/strings.braw- String helpers
games/tetris/- Browser Tetris demo (HTML/JS)
You can compile any example tae JavaScript:
# Compile FizzBuzz to JavaScript
mdhavers compile examples/fizzbuzz.braw -o fizzbuzz.js
node fizzbuzz.js
# Compile to WAT
mdhavers wasm examples/functions.braw -o functions.watTry it in the playground at playground/web/ tae see live compilation!
The default build includes all features (LLVM, graphics, audio). First install dependencies:
Linux/WSL:
# Clone and enter the repository
git clone <repo-url>
cd mdhavers
# Install all dependencies (LLVM, graphics, audio)
./scripts/setup-deps.sh
# Build with all features (default)
cargo build --release
# Run tests
cargo testWindows (PowerShell):
# Clone and enter the repository
git clone <repo-url>
cd mdhavers
# Install dependencies (run as Administrator for best results)
.\scripts\setup-deps.ps1
# Build for Windows (graphics + audio via raylib)
cargo build --release --no-default-features --features "cli,native,graphics"
# Build with standalone audio (no graphics)
cargo build --release --no-default-features --features "cli,native,audio"
# Build with LLVM (requires LLVM 15 installed)
cargo build --release --no-default-features --features "cli,native,llvm"
# Full build with all features
cargo build --release --features full
# Run tests
cargo test --no-default-features --features minimalNote: On Windows, use graphics for graphics + audio (raylib's built-in audio), or
audio for standalone audio (miniaudio). LLVM native compilation is supported (requires LLVM 15).
If you don't need LLVM, graphics, or audio:
# Minimal build with just CLI + native networking
cargo build --release --no-default-features --features minimal
# Or even more minimal (CLI only, no native networking)
cargo build --release --no-default-features --features cli| Feature | Description | Dependencies |
|---|---|---|
full |
All features (default) | LLVM 15, raylib deps, ALSA (Linux) |
full-no-llvm |
All except LLVM | raylib deps |
minimal |
CLI + native networking | None (just Rust) |
llvm |
Native code compilation | LLVM 15, libzstd |
graphics |
2D/3D graphics + audio (raylib) | cmake, X11 dev libs (Linux) |
audio |
Standalone audio (miniaudio) | ALSA (Linux) |
Audio backend selection:
graphics→ uses raylib's built-in audio (no conflicts)audiowithoutgraphics→ uses miniaudio backendgraphics+audio→ raylib audio (graphics takes precedence)
Platform notes:
- Linux/WSL: Use
fullfor all features including LLVM native compilation - Windows: Full support including LLVM. Install LLVM 15 and set
LLVM_SYS_150_PREFIX
If you prefer to install dependencies manually:
Ubuntu/Debian (LLVM):
sudo apt install llvm-15 llvm-15-dev libpolly-15-dev lld-15 libzstd-dev
export LLVM_SYS_150_PREFIX=/usr/lib/llvm-15Ubuntu/Debian (Graphics):
sudo apt install cmake libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libgl1-mesa-devUbuntu/Debian (Audio):
sudo apt install libasound2-devWindows (LLVM):
- Install LLVM 15 from releases.llvm.org or via
choco install llvm --version=15.0.7 - Set environment variable:
LLVM_SYS_150_PREFIX=C:\Program Files\LLVM
Verify LLVM detection:
make statusAudio is optional and independent of graphics. Audio (interpreter + native) uses miniaudio and does not require X11. X11 deps are only needed for graphics.
Backend support: Interpreter, LLVM/native, JavaScript, and WAT/WASM.
For JavaScript/WASM, audio uses WebAudio and a small rustysynth WASM helper. You must host the following assets alongside your compiled output (or set overrides):
assets/wasm/mdh_rustysynth.wasmassets/soundfonts/MuseScore_General.sf2
See assets/wasm/README.md (rebuild the WASM helper) and assets/soundfonts/README.md (download the SoundFont).
Optional overrides (set before running audio code):
globalThis.__havers_audio_base = "/static/"; // prefix for audio assets
globalThis.__havers_soundfont = "/static/sf2/custom.sf2";
globalThis.__havers_midi_wasm = "/static/wasm/mdh_rustysynth.wasm";For WAT/WASM in the browser, wire audio imports via the helper runtime:
// Load the audio runtime + WASM host helpers first.
import "./runtime/js/audio_runtime.js";
import "./runtime/js/wasm_audio_host.js";
const imports = {
env: {
memory,
// print_i32/print_f64/print_str, etc.
...mdh_wasm_audio_imports(memory),
},
};If you see an error like:
RandR headers not found; install libxrandr development package
install the X11 dev packages:
sudo apt install cmake libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libgl1-mesa-devIf you hit:
Unable to find libclang
install clang + libclang and retry:
sudo apt install clang libclang-dev llvm-dev
export LIBCLANG_PATH=$(llvm-config --libdir)If you want to avoid raylib entirely, build without graphics:
cargo build --release --no-default-features --features cli,llvm,audioDrop audio too if you want a CLI-only build.
Quick example:
soond_stairt()
ken ding = soond_lade("assets/audio/ding.wav")
soond_spiel(ding)
Use soond_ready(handle) to check SFX load status on web backends.
Streaming (MP3 + MIDI) needs updates on web backends:
ken tune = muisic_lade("assets/audio/theme.mp3")
muisic_spiel(tune)
whiles aye {
soond_haud_gang() # keep streams flowing on backends that need it
}
MIDI uses a bundled default SoundFont at assets/soundfonts/MuseScore_General.sf2 when you pass naething as the soundfont path:
ken song = midi_lade("assets/audio/wee_tune.mid", naething)
midi_spiel(song)
mdhavers includes a Language Server Protocol (LSP) implementation for rich editor features:
- Real-time error diagnostics
- Hover documentation for keywords and built-ins
- Auto-completion with Scottish-flavored suggestions
- Syntax highlighting
# Build the LSP server
cargo build --release
# The binary will be at target/release/mdhavers-lsp
# Add it to your PATH or configure your editor to find it- Copy the
editor/vscodefolder to your VS Code extensions directory - Install dependencies:
cd editor/vscode && npm install && npm run compile - Reload VS Code
- The extension will automatically start the LSP server
Configuration options in VS Code settings:
mdhavers.lsp.path- Path to mdhavers-lsp executable (default: "mdhavers-lsp")mdhavers.lsp.enable- Enable/disable the language server (default: true)
Add to your vim config:
" Add to your .vimrc or init.vim
au BufNewFile,BufRead *.braw set filetype=mdhaversThen copy the syntax files:
cp -r editor/vim/* ~/.vim/
# Or for Neovim:
cp -r editor/vim/* ~/.config/nvim/For LSP support in Neovim, add to your config (requires nvim-lspconfig):
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')
configs.mdhavers = {
default_config = {
cmd = { 'mdhavers-lsp' },
filetypes = { 'mdhavers' },
root_dir = lspconfig.util.find_git_ancestor,
single_file_support = true,
},
}
lspconfig.mdhavers.setup({})Use the TextMate grammar file at editor/mdhavers.tmLanguage.json.
Any editor with LSP support can use mdhavers-lsp. Configure it to:
- Run command:
mdhavers-lsp - File types:
*.braw - Communication: stdio
MIT
"This is havers, but it's working havers!"