(defn bench `Feed bench wrapped func (thunk) and repetitions, receive time in ns`
[func times]
(def start (os/clock :cputime :tuple))
(loop [_ :range [times]]
(func))
(def end (os/clock :cputime :tuple))
(/ (+ (* (- (end 0) (start 0)) 1e9)
(- (end 1) (start 1)))
times))
# it turns out os/clock is pretty darn fast (comparatively)
(def iterations 2000000)
(bench |(os/clock :cputime :tuple) iterations) # 1283.30053 ns
(bench |(slurp "/proc/self/schedstat") iterations) # 7881.451760 ns
# these basically benchmark slurp
(bench |(do (def f (file/open "/proc/self/schedstat" :r))
(def content (file/read f :all))
(file/close f))
iterations) # 4894.832760 ns
# even without opening and closing the file, reading in Janet's slower than os/clock
(def f (file/open "/proc/self/schedstat" :r))
(bench |(do (file/seek f :set 0)
(def content (file/read f :all))) iterations) # 1802.511470 ns
(file/close f)
# Of course bench has some overhead, but I think it's amortized across iterations anyway
(bench (fn []) 10000000) # 42.030338 ns
os/clockveqqqPlayground# from https://janet.guide/control-flow/
(defn calculate [expr]
(match expr
[:add x y] (+ x y)
[:subtract x y] (- x y)
[:multiply x y] (* x y)
([:divide x y] (= y 0)) (error "division by zero!")
[:divide x y] (/ x y)))
(calculate [:subtract 5 10])
# Note, it checks prefixes, so you must order things this way:
(match [1 2]
[x y z] "three elements"
[x y] "two elements"
[x] "one element"
[] "no elements")
matchveqqqPlayground# read inpu at compile-time and create a custom func to check whether a number is in one of the
# given ranges. This turned out to be almost 4x faster than writing it as a func
# from https://abhinavsarkar.net/notes/2025-aoc-1/#day-2
(defmacro in-range? [n & _]
(def ranges (parse-input (slurp input-path)))
~(or ,;(map (fn [[s e]] ~(<= ,s ,n ,e)) ranges)))
defmacroveqqqPlayground(defn capitalize [str]
(string (string/ascii-upper (string/slice str 0 1)) (string/slice str 1)))
string/ascii-upperveqqqPlayground# due to 0 indexing, you will often want to add 1 to things:
(defn short-date [d]
(let [{:year y :month mon :month-day d} d]
(string y "-" (+ 1 mon) "-" (+ 1 d))))
# Makes os/date like 2025-12-25
(short-date (os//date))
# From: https://codeberg.org/veqq/deforester/src/branch/master/deforester.janet
(defn- time-string
``Gives current time as ISO 8601 string: 2025-10-12T11:43:14 https://en.wikipedia.org/wiki/ISO_8601
This accounts for `os/date` 0-indexing month and days which are 1-indexed in ISO 8601.``
[]
(let [{:year y :month mon :month-day d :hours h :minutes min :seconds s} (os/date)]
(string y "-" (+ 1 mon) "-" (+ 1 d) "T" h ":" min ":" s)))
os/dateveqqqPlayground# *doc-width* is bound to the keyword :doc-width - it can be used in place of
# :doc-width in a call to `dyn`, `setdyn`, or `with-dyns` and is preferable
# to using the keyword directly. When set to a number, it indicates the
# maximum width (in columns) of a row of text returned by `doc-format`.
# - Like *doc-color*, *doc-width* can be used to adjust the output of
# `doc-format`.
# - When the :doc-width dynamic binding is not set, the default width is 80.
# - By default, `doc-format` adds 4 space indentation and subtracts 8 from
# the value of the :doc-width dynamic binding to calculate a max width.
# Default:
# repl> (doc doc)
#
#
# macro
# boot.janet on line 3573, column 1
#
# (doc &opt sym)
#
# Shows documentation for the given symbol, or can show a list of
# available bindings. If sym is a symbol, will look for documentation
# for that symbol. If sym is a string or is not provided, will show
# all lexical and dynamic bindings in the current environment
# containing that string (all bindings will be shown if no string is
# given).
# With *doc-width*:
# repl> (with-dyns [*doc-width* 40] (doc doc))
#
#
# macro
# boot.janet on line 3573, column 1
#
# (doc &opt sym)
#
# Shows documentation for the
# given symbol, or can show a
# list of available bindings.
# If sym is a symbol, will
# look for documentation for
# that symbol. If sym is a
# string or is not provided,
# will show all lexical and
# dynamic bindings in the
# current environment
# containing that string (all
# bindings will be shown if
# no string is given).
*doc-width*quexxonPlayground# When the :doc-color dynamic binding referenced by *doc-color* is truthy,
# the doc-format function replaces a minimal subset of Markdown markup with
# the corresponding ANSI escape codes.
#
# The following markup is supported:
# - *this will be underlined*
# - **this will be bold**
# - `(+ 1 2 3)` <- backticks for code
#
# You may be surprised by *underline* since the same markup is used to
# indicate italics in Markdown. This is likely a tradeoff for compatibility;
# historically, the italic attribute has not been widely supported by
# terminal emulators.
#
# The best way to see the effect of *doc-color* is try the following examples
# in the Janet REPL.
# By default, *doc-color* is enabled.
(print (doc-format "*underline*. **bold**. `(code)`."))
# Set the dynamic binding to a falsy value to disable doc-format's ANSI
# escape code substition.
(with-dyns [*doc-color* false]
(print (doc-format "*underline*. **bold**. `(code)`.")))
# N.B.: At the time of writing, no docstrings in the core API take advantage of
# the bold or underline markup As a result, you may not see any difference in
# the doc formatting if your terminal theme uses the same hue for white and
# bright white (a few terminals that I tested on Linux make no distinction
# between the two colors in their default configuration).
*doc-color*quexxonPlayground# In Linux, "everything is a file" so:
(def pid (os/getpid))
pid
# =>
367537
(def statm-path (string "/proc/" pid "/statm"))
statm-path
# =>
"/proc/367537/statm"
(slurp statm-path)
# =>
@"1380 971 683 82 0 362 0\n"
slurpsogaiuPlayground# in the file ex.janet
(main [_ & args]
(print "write something and I'll echo it")
(def x (getline))
(print x))
# in the terminal:
# $ janet ex.janet
# write something and I'll echo it
# bla <- you wrote this
# bla
getlineveqqqPlayground# sh/$ can't expand "~" so you must build it:
# (def key "D8E4DB18BF87FLEW7402BBE3AA91B16F4A65C4C9") # use your gpg key ID
(defn copy-and-encrypt-password-store [key-id]
(with [out (file/open "pass-backup.tar.gz.gpg" :w)]
(sh/$ tar -czf - ,(string (os/getenv "HOME") "/.password-store")
| gpg --encrypt --recipient ,key-id > ,out)))
# tar -cz ~/.password-store/ | gpg --encrypt --recipient YOUR_KEY_ID > pass-backup.tar.gz.gpg
sh/$veqqqPlayground
# sh/$'s contents are quasiquoted, allowing direct or string arguments
# so you need to unquote , variables:
(def out (file/open "trust-db.txt" :w))
(sh/$ "gpg" "--export-ownertrust" > ,out) # > requires an opened file object
(file/close out)
# note how > requires an opened file object
(with [out (file/open "trust-db.txt" :w)]
(sh/$ gpg --export-ownertrust > ,out))
sh/$veqqqPlayground# sorted allocates the full memory of its input
(def d2 (range 10000000 0 -1)) # 87mb
(sorted d2) # now 167 mb
sortedveqqqPlayground# Upgrade mutates, but I wasn't sure whether it'd matter using the pure sorted or mutative sort. So I
# did a simple test.
(import spork)
# create data before test
(def d @{:data (range 10000000 0 -1)})
(spork/test/timeit (update d :data sort)) # 4.32930135726929 seconds and 87.4 MB
(def d @{:data (range 10000000 0 -1)}) # 87.4 MB
(spork/test/timeit (update d :data sorted)) # 4.49482655525208 seconds and 167 MB
# Where did those memory numbers come from? With only the data, the Janet process
# uses 87.4 MB. Timewise, they take the same amount of time but on starting, sorted
# prepares memory equivalent to its input. To check ram within Janet:
(def pid (os/getpid)) # => "/proc/367537/statm"
(def statm-path (string "/proc/" pid "/statm")) # => 367537
(slurp statm-path) # => @"40444 40038 710 82 0 39426 0\n"
# Collecting garbage:
(gccollect)
(slurp statm-path) # => @"20912 20503 695 82 0 19894 0\n"
spork/test/timeitveqqqPlayground# Consider this problem: As a module author, I want to define a dynamic
# binding `:debug` to provide additional diagnostic information when an
# error occurs in a module function. I'll use `defdyn` to create a public
# alias for my dynamic binding:
# In the file: my-mod.janet
# The `defdyn` macro creates the dynamic binding :debug, and explicitly
# exposes it in the module's API as `my-mod/*debug*`.
(defdyn *debug*)
# contrived function - the important thing is that it enables additional
# diagnostic logging when the `*debug*` alias's reference dynamic binding
# is true.
(defn my-func [& params]
(try
(do
(when (empty? params)
(error "missing required argument")))
([err fib]
(when (dyn *debug*)
(print "[diagnostics] ..."))
(error (string "error occurred: " err)))))
# Let's test our module from the REPL.
# $ repl:1:> (import /my-mod)
# $ repl:2:> (setdyn my-mod/*debug* true) # enable debug mode
# $ repl:3:> (my-mod/my-func)
# [diagnostics] ...
# error: error occured: missing required argument
# in my-func [my-mod.janet] on line 11, column 7
# in thunk [repl] (tail call) on line 4, column 1
# entering debug[1] - (quit) to exit
# debug[1]:1:>
# We see our diagnostic message, but we also entered Janet's built-in
# debugger! That's not what we intended. Why did this happen?
# It turns out that our module's `my-mod/*debug*` alias is namespaced, but the
# referenced `:debug` dynamic binding is not. We've created a collision with
# the `:debug` dynamic binding in the core API that activates the debugger
# on error.
# $ repl> *debug*
:debug
# $ repl> my-mod/*debug*
:debug
# The solution is to set the :defdyn-prefix dynamic binding to add a namespace
# for all dynamic bindings in our module. We should use a reasonably unique
# prefix - let's go with the name of the module's public git repo. It's okay
# for the prefix to be long - it will be used as the key for the dynamic
# binding in the current environment, but the user won't typically interact
# with it directly. The prefix is effectively "hidden" from the user. We'll
# prepend this line at the top of our module:
(setdyn *defdyn-prefix* "codeberg.org/quexxon/my-mod/")
# Now our `my-mod/*debug*` alias refers to an isolated, namespaced binding:
# $ repl:1:> (import /my-mod)
# $ repl:2:> my-mod/*debug*
:codeberg.org/quexxon/my-mod/debug
# $ repl:3:> (setdyn my-mod/*debug* true) # enable debug mode
# $ repl:4:> (my-mod/my-func)
# [diagnostics] ...
# error: error occured: missing required argument
# in my-func [my-mod.janet] on line 11, column 7
# in thunk [repl] (tail call) on line 4, column 1
# $ repl:5:>
# Much better! We see our diagnostic message, but we didn't trigger Janet's
# debugger. As a general rule, always set `*defdyn-prefix*` to a unique
# value when defining dynamic bindings for your module.
*defdyn-prefix*quexxonPlayground# Note that the earlier sort func is executed later
(update @{:data @[:cherry :orange]} :data (comp sort |(array/push $ :apple)))
# => @{:data @[:apple :cherry :orange]}
compveqqqPlayground