Nanoclj: A Tiny Clojure Interpreter
GitHub - rekola/nanoclj: A Tiny Clojure Interpreter
nanoclj


A Tiny Clojure Interpreter
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.
Features

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

2D Graphics
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:

Differences to Clojure:

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

Dependencies

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

Building
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.
Ubuntu
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

Arch Linux PKGBUILD
curl https://raw.githubusercontent.com/rekola/nanoclj/main/PKGBUILD -O
makepkg -si

Windows
Windows support is in progress.
Missing functionality

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...

