β‘ It's alive!
_____ _ _
| ___| __ __ _ _ __ | | _(_) ___
| |_ | '__/ _` | '_ \| |/ / |/ _ \
| _|| | | (_| | | | | <| | __/
|_| |_| \__,_|_| |_|_|\_\_|\___|
A procedural programming language lovingly stitched together from Ruby, Python, R, and Fortran. Zero dependencies. Proudly monstrous.
βΌ scroll
What it can do
Proudly procedural β functions, data, loops, logic. No classes, no self, no inheritance. Runs on Python 3.10+ with zero external dependencies.
+= -= *= /= //= **= %= β all operators, including Fortran-style integer division and exponentiation. Works on variables and vector elements.
rescue ZeroDivisionError e, rescue TypeError e β multiple typed clauses on one beginβ¦end block. Catch exactly what you mean.
Return the first element where the block is true, or nil. Works on any vector including vectors of hashes. Chains naturally with .select, .sort_by, and the pipe operator.
Built-in test runner. assert_true, assert_eq, assert_neq, assert_raises β no imports. Live β / β output per assertion. Exits 1 on failure for CI.
Built-in mean, stdev, median, variance. Vectorised arithmetic. Pipe operator |> for clean data pipelines.
Sinatra-style routing with web_app(). Path params, JSON bodies, before/after filters, custom 404 β zero external deps.
Open databases, run queries, insert rows with clean Frankie syntax. No ORM, no driver, no setup required.
Make HTTP requests, parse JSON and CSV, work with DateTime β all in the stdlib, all zero-dependency.
rand, rand_int, rand_float, shuffle, sample, rand_seed β everything for games and simulations.
.map, .select, .find, .sort_by, .reduce, .min_by, .max_by with do |x| end blocks.
next to skip iterations, break to exit early, break value to exit with a result. Postfix forms work too.
UPPER_SNAKE_CASE names are constants. Reassignment warns and preserves the original value automatically.
The language
# Compound assignment operators score = 0 score += 10 # score = 10 score *= 2 # score = 20 score -= 3 # score = 17 score **= 2 # score = 289 (Fortran!) score %= 100 # score = 89 # Works on vector elements too v = [10, 20, 30] v[0] += 5 # [15, 20, 30] v[2] //= 4 # [15, 20, 7] # Idiomatic loop accumulation total = 0 [3, 7, 2, 9].each do |n| total += n end puts total # 21
# Typed rescue clauses def safe_divide(a, b) begin return a // b rescue ZeroDivisionError e puts "division by zero" return 0 rescue TypeError e puts "wrong type: #{e}" return 0 rescue e puts "unexpected: #{e}" return 0 ensure puts "always runs" end end puts safe_divide(10, 2) # 5 puts safe_divide(10, 0) # 0
nums = [3, 7, 2, 11, 4, 9] # First element matching the block big = nums.find do |n| n > 8 end puts big # 11 # .detect is an alias even = nums.detect do |n| n % 2 == 0 end puts even # 2 # Returns nil when nothing matches huge = nums.find do |n| n > 100 end p huge # (Nil) nil
# frankiec test β built-in test runner # No imports needed. Run: frankiec test x = 10 x += 5 assert_eq(x, 15, "+= works") result = [1, 3, 5, 7].find do |n| n > 4 end assert_eq(result, 5, ".find works") caught = nil begin y = 1 // 0 rescue ZeroDivisionError e caught = "zero" end assert_eq(caught, "zero", "typed rescue") # β += works # β .find works # β typed rescue # β All 3 test(s) passed (1.4ms)
# R-style stats + pipe operator data = [23, 45, 12, 67, 34, 89] puts mean(data) # 45.0 puts stdev(data) # 27.9 puts median(data) # 39.5 data |> sum |> puts # 270 top3 = data.sort_by do |x| -x end.take(3) puts top3 # [89, 67, 45]
words = ["banana", "fig", "cherry"] long = words.select do |w| w.length > 4 end # [banana, cherry] # next / break [1,2,3,4,5,6].each do |n| next if n % 2 == 0 break if n > 5 puts n # 1 3 5 end # Constants MAX_SIZE = 100 PI = 3.14159
Web server
Sinatra-inspired routing built into the language. Define routes with blocks, handle JSON, path params, filters and custom 404s β all zero external dependencies.
app = web_app() app.get("/") do |req| html_response("<h1>Hello from Frankie! π§</h1>") end app.get("/greet/:name") do |req| name = req.params["name"] response("Hello, #{name}!") end app.get("/api/status") do |req| json_response({status: "ok", version: "1.6"}) end store = {notes: []} app.post("/notes") do |req| data = req.json if data == nil return halt(400, "Expected JSON") end note = {id: length(store["notes"]) + 1, text: data["text"]} store["notes"].push(note) json_response(note, 201) end app.not_found do |req| halt(404, "No route for #{req.path}") end app.run(3000) # π§ Frankie web server running on http://0.0.0.0:3000
response(body)
html_response(html)
json_response(data)
redirect(url)
halt(status, body)
The donors
Get started
Requirements: Python 3.10+. That's it.
# Clone the repo
git clone https://github.com/atejada/Frankie && cd Frankie# Install (creates bin/frankiec)
python3 install.py
export PATH="/path/to/Frankie/bin:$PATH"# Run a program, launch the REPL, or run your tests
frankiec run examples/hello.fk
frankiec repl
frankiec test| Version | Highlights | Status |
|---|---|---|
| v1.6 | Compound assign +=/-=/*= etc., typed rescue, .find/.detect, frankiec test | Latest |
| v1.5 | next / break, constants, randomness, sort_by, sleep, unzip | Stable |
| v1.4 | Built-in web server, routes, JSON API, filters, custom 404 | Old |
| v1.3 | JSON, CSV, DateTime, HTTP client, project scaffolding | Old |
| v1.2 | SQLite built-in, transactions, multi-file with require | Old |
| v1.1 | Rich iterators, REPL, case/when, destructuring | Old |
| v1.0 | Initial release β core language, stdlib, error handling | Old |