This is a small Clojure language implementation written in C language. It is based on TinyScheme which was based on MiniScheme. The project is in the early stages yet, and much functionality is still missing. The project has two main goals:
- Provide data visualization capabilities for the REPL in a terminal or GUI.
- Provide an embedded Clojure interpreter for C++ applications as an alternative for Lua and other scripting languages.
An additional long term goal is to bring AI assistance right in to the REPL.
- Terminal graphics (Kitty or Sixel protocols) with HiDPI support
- Image, audio, Shapefile, XML, CSV and GraphML loading
- Simple image operations (blur, transpose etc.) and 2D canvas
- REPL output is colored by type
- Callback Writer for printing into a GUI instead of stdout
- Class and namespace names try to imitate Java and Clojure names when possible (e.g.
(type 1) ;=> java.lang.Long
) - BigInts and Ratios
- Tensors are used for representing most data structures. For example, a vector is a 1D tensor of doubles (with NaN-packing for other data types).
- Test framework
Since Clojure has no standard way for 2D drawing, a new system has
been created for nanoclj. The principle of minimality suggests that
there should only be a single way to output text, which means that
println must work with canvas. Therefore, an active canvas is bound to
*out*
and it's the target of all canvas operations. For normal *out*
the canvas commands are ignored (apart from color and restore), but
one could imagine that they could be used to draw vector graphics on a
Tektronix terminal, or move-to could set the cursor position in a text
mode program. The canvas interface has been modeled after Cairo.
In GUI program the resulting canvas can be displayed in a REPL using the GUI callbacks for output Writer. In terminal program Kitty or Sixel protocol is used to print the results of a function call when the function returns a canvas or image. The terminal must, of course, need to support Kitty or Sixel graphics and support must be enabled. The following terminals have been tested:
Terminal | Graphics | Mouse | Notes |
---|---|---|---|
foot | OK | OK | Wayland only, no undercurls |
kitty | OK | ? | True color images, but window resizing has bugs (as of 0.26.5) |
wezterm | OK | OK | Buggy (as of 20230712) |
mlterm | OK | ? | linenoise output flickers when updated, no undercurls |
Konsole | OK | ? | True color images, but on HiDPI system images are upscaled |
contour | Inline image layout doesn't work | ? | |
xterm | No true color | OK | No undercurls, Sixel support must be enabled in .Xresources , and images have maximum size 1000x1000 |
Black Box | Inline image layout doesn't work | ? | On HiDPI system the images are upscaled, and the terminal and the flatpak system use too much CPU time when idling. |
Alacritty | None | ? | |
GNOME Terminal | None | ? | Sixel support is not enabled by default |
mintty | ? | ? | Not tested yet. |
The plot function returns a canvas, which can be saved with nanoclj.image/save or modified using other functions in the nanoclj.image namespace.
As well as printing images in block mode like the plot function does, they can also be printed in inline mode which is shown in the following example:
- Characters are 32 bit, strings are UTF-8 (count is O(n)), UTF8 validity is checked and char-array creates a byte array
- Strings are sequences, and they are compared and sorted as such.
- List count has complexity of O(n)
- Macros use the TinyScheme syntax
- Tail-call optimization
- sort is not stable and doesn't support custom compare function
- License is BSD 3-Clause License instead of EPL
- Symbols are interned but are temporarily boxed when metadata is added
- Primitives such as doubles and small integers are passed by value, and are in effect, interned
- Regular expressions are interned, but strings are not:
(identical? "abc" "abc") ;=> false
rationalize
returns exact result for doubles:(rationalize 0.1) ;=> 3602879701896397/36028797018963968
- No chunked or buffered lazy sequences
- Data structures are only partially persistent, and while vectors, maps and sets allow fast reading and insertion, deletion and modification is slow.
- Namespaces can only contain Vars, not Classes:
(resolve 'Math) ;=> #'java.lang.Math
- Arrays are compared by value, can only contain primitive values (including Objects) and multidimensional arrays cannot be ragged
- No type hints
- Unbound Vars cannot be created
- Dividing Long/MIN_VALUE by -1 doesn't fail
- java.net.URL doesn't resolve the hostname for calculating hashcode
- No transient data structures
- linenoise (included, extra functionality such as UTF8 support and brace highlighting added)
- stb_image, stb_image_resize, stb_image_write (included)
- dr_wav (included)
- zlib
- libxml2
- pcre2
- cairo
- libsixel
- utf8proc
- shapelib
C11 support is required for building nanoclj, and it has been tested with both gcc and clang. Both 32-bit and 64-bit systems are supported.
sudo apt install libutf8proc-dev libsixel-dev libpcre2-dev libcairo2-dev libshp-dev libcurl4-gnutls-dev libxml2-dev libz-dev
mkdir build
cd build
cmake ..
make
sudo make install
curl https://raw.githubusercontent.com/rekola/nanoclj/main/PKGBUILD -O
makepkg -si
Windows support is in progress.
- Interfaces, Records, StructMaps, Protocols and Multi-methods
- Dynamic variables
- Transducers
- Refs, Agents, Atoms, Validators
- Reader Conditionals
- BigDecimals, 32-bit floats and hexadecimal floating point literals
- Locals cleaning
- Multithreading, transactions, STM and locking
- Pre- and post-conditions for functions
- Readers and Writers do not accept options such as :encoding or :append
- Associative destructuring
- Map namespace syntax
*print-length*
,*print-level*
,*clojure-version*
,*load-tests*
- Missing core functions and macros
- doseq, for
- sorted-set-by, sorted-set, sorted-map, sorted-map-by
- update-in, merge-with
- doto, ->, -->, some->, some->>
- map-indexed, mapcat, lazy-cat
- partition-by
- memoize
- condp
- load
- when-let, letfn, if-let, if-some
- reduced, reduced?
- with-local-vars, var-set, find-var, alter-var-root, declare, binding, with-bindings
- sequence, subseq, rsubseq
- make-hierarchy, ancestors, supers, bases, underive
- re-groups, re-matcher, re-seq, re-matches
- assert-args
- trampoline
- shuffle, random-sample
- unchecked-byte, unchecked-short, unchecked-char, unchecked-int, unchecked-long, unchecked-float, unchecked-double
- vary-meta, alter-meta!
- aset-char, aset-long, amap, areduce, to-array-2d, make-array, long-array, bytes?
- remove-ns, ns-imports, ns-interns, ns-aliases, ns-unalias, ns-unmap
- clojure.java.io
- resource
- make-parents
- delete-file
- clojure.repl
- dir
- clojure.test
- deftest, set-test, with-test
- clojure.math
- clojure.core.async (thread, thread-call etc.)
- clojure.core.reducers
- clojure.main (load-script etc.)
- clojure.pprint (print-table etc.)
- clojure.data
- clojure.walk
- clojure.zip
- clojure.spec
- ...and lots more...