Some demos of Janet features listed at the main page, in order, on a Linux box.
Links to official docs should be provided along the way, highly recommended to check them out to start the assimila^H^H^H^H^H^H^H^H familiarization process :)
Hint: if reading this on GitHub, consider using the Table of Contents icon to navigate to different sections.
The intent is to touch on the features listed at the main page. This is a work-in-progress. Currently the list includes:
- Minimal setup - one binary and you are good to go!
- Builtin support for threads, networking, and an event loop
- First class closures
- Garbage collection
- First class green threads (continuations)
- Mutable and immutable arrays (array/tuple)
- Mutable and immutable hashtables (table/struct)
- Mutable and immutable strings (buffer/string)
- Macros
- Tail call optimization
- Direct interop with C via abstract types and C functions
- Dynamically load C libraries
- Lexical scoping
- REPL and interactive debugger
- Parsing Expression Grammars built in to the core library
- 500+ functions and macros in the core library
- Export your projects to standalone executables with a companion build tool, jpm
- Add to a project with just janet.c and janet.h
Note that this list is not quite the same as the one in the repository README.
Some additional items from the longer list:
- Configurable at build time - turn features on or off for a smaller or more featureful build
- Python-style generators (implemented as a plain macro)
One way to experience this is to download one of the files from the GitHub releases page.
At the time of this writing, for a Linux environment, I got
janet-v1.25.1-linux-x64.tar.gz,
uncompressed it, and was able to run the janet
binary that lived in
the bin
subdirectory:
wget --quiet https://github.com/janet-lang/janet/releases/download/v1.25.1/janet-v1.25.1-linux-x64.tar.gz
tar xf janet-v1.25.1-linux-x64.tar.gz
cd janet-v1.25.1-linux
./bin/janet
Personally, I prefer a non-root local install of the master branch (later content in this document will be using the local installation), but may be the earlier method is better if you want a quick taste. Some distributions carry packages, but I'm not sure how up-to-date they are.
Here's a snippet from the Multithreading docs:
(ev/spawn-thread
(print "New thread started!"))
(ev/do-thread
(print "New thread started!"))
Trying that at the repl:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (ev/spawn-thread (print "Thread one"))
nil
repl:2:> Thread one
repl:3:> (ev/do-thread (print "Thread the second"))
Thread the second
nil
Not so clear in text perhaps -- I pressed the Enter key after the
Thread one
text appeared for aesthetic purposes.
Let's ask for some help from our snake friend:
mkdir /tmp/fun
cd /tmp/fun
python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Here's a snippet adapted from the Networking docs:
(with [conn (net/connect "127.0.0.1" "8000" :stream)]
(printf "Connected to %q!" conn)
(:write conn "GET / HTTP/1.0\n\n")
(print "Wrote to connection...")
(def res (:read conn 1024))
(pp res))
Trying that at the repl:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (with [conn (net/connect "127.0.0.1" "8000" :stream)]
repl:2:(> (printf "Connected to %q!" conn)
repl:3:(> (:write conn "GET / HTTP/1.0\n\n")
repl:4:(> (print "Wrote to connection...")
repl:5:(> (def res (:read conn 1024))
repl:6:(> (pp res))
Connected to <core/stream 0x55B39596BF30>!
Wrote to connection...
@"HTTP/1.0 200 OK\r\nServer: SimpleHTTP/0.6 Python/3.10.6\r\nDate: Sat, 31 Dec 2022 01:37:29 GMT\r\nContent-type: text/html; charset=utf-8\r\nContent-Length: 607\r\n\r\n"
nil
Servers are doable too.
Here is some code from The Event Loop docs:
(defn worker
"Does some work."
[name n]
(for i 0 n
(print name " working " i "...")
(ev/sleep 0.5))
(print name " is done!"))
# Start bob working in a new task with ev/call
(ev/call worker "bob" 10)
(ev/sleep 0.25)
# Start sally working in a new task with ev/go
(ev/go (fiber/new |(worker "sally" 20)))
(ev/sleep 11)
(print "Everyone should be done by now!")
Pasting the above to the repl a bit at a time.
First we make a function:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (defn worker
repl:2:(> "Does some work."
repl:3:(> [name n]
repl:4:(> (for i 0 n
repl:5:((> (print name " working " i "...")
repl:6:((> (ev/sleep 0.5))
repl:7:(> (print name " is done!"))
<function worker>
We pass the function to ev/call
which gives us back a fiber.
repl:8:> (ev/call worker "bob" 10)
<fiber 0x55C44E732CC0>
To get it to start, we need to let it run by temporarily relinquishing
control via ev/sleep
:
repl:9:> (ev/sleep 0.25)
bob working 0...
nil
Note the bob working 0...
.
Now arrange for another:
repl:10:> (ev/go (fiber/new |(worker "sally" 20)))
<fiber 0x55C44E736470>
Relinquish control again:
repl:11:> (ev/sleep 11)
sally working 0...
bob working 1...
sally working 1...
bob working 2...
sally working 2...
bob working 3...
sally working 3...
bob working 4...
sally working 4...
bob working 5...
sally working 5...
bob working 6...
sally working 6...
bob working 7...
sally working 7...
bob working 8...
sally working 8...
bob working 9...
sally working 9...
bob is done!
sally working 10...
sally working 11...
sally working 12...
(print "Everyone should be done by now!")sally working 13...
sally working 14...
sally working 15...
sally working 16...
sally working 17...
sally working 18...
sally working 19...
sally is done!
nil
I was able to paste (print "Everyone should be done by now!")
while
output was appearing from ongoing activity. Notice how it appears
between the text sally working 12...
and sally working 13...
.
We'll make an "adder" maker:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (defn adder-maker [x] (fn [y] (+ x y)))
<function adder-maker>
Now use it to make an adder and call it:
repl:2:> (def eight-adder (adder-maker 8))
<function 0x5648F3BF95B0>
repl:3:> (eight-adder 1)
9
Finally, pass the adder to another function for use:
repl:4:> (defn use-adder-to-increment [an-adder value] (an-adder value))
<function use-adder-to-increment>
repl:5:> (use-adder-to-increment eight-adder 3)
11
Not sure how to demonstrate this...how about some evidence?
The Janet's Memory Model docs have some details.
In Janet, I believe these are called "fibers".
Adapting some code from the beginning of the Fibers docs:
(def f
(fiber/new (fn []
(yield 1)
(yield 2)
(yield 3)
(yield 4)
5)))
(fiber/status f)
(resume f)
(resume f)
(resume f)
(resume f)
(fiber/status f)
(resume f)
(fiber/status f)
(resume f)
Stepping through:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def f
repl:2:(> (fiber/new (fn []
repl:3:(((> (yield 1)
repl:4:(((> (yield 2)
repl:5:(((> (yield 3)
repl:6:(((> (yield 4)
repl:7:(((> 5)))
<fiber 0x55D01BE6D050>
Can get the status of a fiber:
repl:7:> (fiber/status f)
:new
Resume the fiber ("resume" also means start):
repl:8:> (resume f)
1
Resume 3 more times:
repl:9:> (resume f)
2
repl:10:> (resume f)
3
repl:11:> (resume f)
4
Check the fiber's status:
repl:12:> (fiber/status f)
:pending
Resume it again:
repl:13:> (resume f)
5
Check the status again:
repl:14:> (fiber/status f)
:dead
What if resume
is called now?
repl:15:> (resume f)
error: cannot resume fiber with status :dead
in _thunk [repl] (tailcall) on line 15, column 1
Oops :)
See the Fibers docs and the Fiber Module docs for more info.
There are two kinds of array-like data structures in Janet, mutable (array) and immutable (tuple).
The Arrays docs cover mutable arrays. In Janet, "array" refers to the mutable type of array data structure.
Mutability is typically expressed using a leading @
character:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def an-array @[1 3 8])
@[1 3 8]
Parentheses can be used as well, but this does not appear common:
repl:2:> (def another-array @(2 7 9))
@[2 7 9]
Let's add something:
repl:3:> (array/push an-array 2)
@[1 3 8 2]
Now get something back:
repl:4:> (get an-array 0)
1
...in a number of other ways:
repl:5:> (in an-array 0)
1
repl:6:> (an-array 0)
1
repl:7:> (0 an-array)
1
get
and
in
are ordinary
functions.
See the Array docs and the Array Module docs for more info.
The Tuples docs cover imumutable arrays. In Janet, "tuple" refers to an immutable type of array data structure. Note that this is not a "persistent" data structure as in Clojure.
Let's make a tuple:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def a-tuple [2 7 9])
(2 7 9)
It's also possible to make a tuple "directly" using parentheses, but quoting is necessary:
repl:2:> (def another-tuple '(3 8 0))
(3 8 0)
Retrieval can be done like:
repl:3:> (get a-tuple 0)
2
or in a number of other ways:
repl:4:> (in a-tuple 0)
2
repl:5:> (a-tuple 0)
2
repl:6:> (0 a-tuple)
2
get
and
in
are ordinary
functions.
See the Tuples docs and the Tuple Module docs for more info.
There are two kinds of hash table / associative array-like data structures in Janet, mutable (table) and immutable (struct).
The Tables docs cover mutable hash tables. In Janet, "table" refers to the mutable type of hash table data structure.
Mutability is typically expressed using a leading @
character:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def a-table @{:a 1 :b 2})
@{:a 1 :b 2}
Let's add something:
repl:2:> (put a-table :c 3)
@{:a 1 :b 2 :c 3}
repl:3:> a-table
@{:a 1 :b 2 :c 3}
To get something out:
repl:4:> (get a-table :a)
1
This can also be accomplished by:
repl:5:> (in a-table :a)
1
repl:6:> (a-table :a)
1
Note that the following doesn't work in the same way as for arrays and tuples:
repl:7:> (:a a-table)
nil
For a hint as to why this is, see the Object-Oriented Programming
docs. In short,
(:a a-table)
becomes ((get a-table :a) a-table)
which in turn
leads to (1 a-table)
.
So if 1
were added to the table as a key with an associated value:
repl:8:> (put a-table 1 :surprise)
@{1 :surprise :a 1 :b 2 :c 3}
and another attempt is made, we would get:
repl:9:> (:a a-table)
:surprise
get
and
in
are ordinary
functions.
See the Tables docs and the Table Module docs for more info.
The Structs docs cover immutable hash tables. In Janet, "struct" refers to an immutable type of hash table data structure. Note that this is not a "persistent" data structure as in Clojure.
Let's make a struct:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def a-struct {:a 1})
{:a 1}
Retrieval can be done like this:
repl:2:> (get a-struct :a)
1
or by either of:
repl:3:> (in a-struct :a)
1
repl:4:> (a-struct :a)
1
Note that the following doesn't work in the same way as for arrays and tuples:
repl:5:> (:a a-struct)
nil
For a hint as to why this is, see the Object-Oriented Programming
docs. In short,
(:a a-struct)
becomes ((get a-struct :a) a-struct)
which in turn
leads to (1 a-struct)
.
If a-struct
had been defined to contain a key of value 1
with an
associated value:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def a-struct {:a 1 1 :relax})
{1 :relax :a 1}
and an attempt similar to the earlier one is made, we should get:
repl:2:> (:a a-struct)
:relax
get
and
in
are ordinary
functions.
See the Structs
docs and
the struct
-related calls starting at the docs for
struct for more info.
There are two kinds of string-like data structures in Janet, mutable (buffer) and immutable (string).
The Buffers docs cover mutable strings. In Janet, "buffer" refers to a mutable type of string data structure.
Mutability is typically expressed using a leading @
character:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def a-buffer @"hello")
@"hello"
Let's add a character to the end of the buffer:
repl:2:> (put a-buffer (length a-buffer) 33)
@"hello!"
How about multiple characters at once?
repl:3:> (buffer/push-string a-buffer " world?")
@"hello! world?"
Retrieval of an individual character can be done by:
repl:4:> (get a-buffer 5)
33
repl:5:> (chr "1")
33
Or:
repl:6:> (in a-buffer 5)
33
repl:7:> (a-buffer 5)
33
repl:8:> (5 a-buffer)
33
A "sub" buffer (though it's a different buffer) can be retrieved by:
repl:9:> (buffer/slice a-buffer 7)
@"world?"
get
and
in
are ordinary
functions.
See the Buffers docs and the Buffer Module docs for more info.
The Strings docs cover immutable strings. In Janet, "string" refers to the immutable type of string as in a number of other programming languages.
One can make a string by:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def a-string "sit")
"sit"
To get a character:
repl:2:> (get a-string 2)
116
repl:3:> (chr "t")
116
Or:
repl:4:> (in a-string 2)
116
repl:5:> (a-string 2)
116
repl:6:> (2 a-string)
116
Substrings can be retrieved by:
repl:7:> (string/slice a-string 1)
"it"
repl:8:> (string/slice a-string 1 2)
"i"
get
and
in
are ordinary
functions.
See the Strings docs and the String Module docs for more info.
Adapted from the Macros docs:
(defmacro my-defn
"Defines a new function."
[name args & body]
~(def ,name (fn ,name ,args ,;body)))
Let's try it out.
Define the macro first:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (defmacro my-defn
repl:2:(> "Defines a new function."
repl:3:(> [name args & body]
repl:4:(> ~(def ,name (fn ,name ,args ,;body)))
<function my-defn>
Now call it:
repl:5:> (my-defn my-name [x] (+ x 1))
<function my-name>
repl:6:> (my-name 3)
4
Note that unlike Common Lisp, Scheme, or Clojure, Janet uses ~
for
quasi-quoting / syntax-quoting. There is a corresponding special form
named quasiquote
.
;
is an abbreviation for using the splice
special form.
,
is an abbreviation for using the unquote
special form.
See the Special Forms
docs for more details about
~
(quasiquote
), ;
(splice
) , and ,
(unquote
).
For more info on macros, see the Macros docs.
As a placeholder, here is some evidence:
- https://github.com/janet-lang/janet/blob/a8a78d452506fdbfbf877379eba969e4efbef842/src/core/vm.c#L379
- https://github.com/janet-lang/janet/blob/a8a78d452506fdbfbf877379eba969e4efbef842/src/core/vm.c#L1008-L1052
- https://github.com/janet-lang/janet/blob/a8a78d452506fdbfbf877379eba969e4efbef842/src/core/vm.c#L270-L276
- https://github.com/janet-lang/janet/blob/a8a78d452506fdbfbf877379eba969e4efbef842/src/core/vm.c#L606-L616
See The Janet C API docs for more details.
There may be more than one way to interpret what this means. Since this item has been in the list longer than the recent addition of Janet's FFI capability, perhaps it didn't use to refer to FFI.
I think this refers to Janet's native module capability.
For a smallish example, see spork
's utf8
module,
and perhaps start by looking at the following code at the bottom of
the file:
JANET_MODULE_ENTRY(JanetTable *env) {
JanetRegExt cfuns[] = {
JANET_REG("decode-rune", cfun_utf8_decode_rune),
JANET_REG("encode-rune", cfun_utf8_encode_rune),
JANET_REG("prefix->width", cfun_utf8_prefixtowidth),
JANET_REG_END
};
janet_cfuns_ext(env, "utf8", cfuns);
}
Then take a look at:
JANET_FN(cfun_utf8_prefixtowidth,
"(utf8/prefix->width c)",
"Given the first byte in an UTF-8 sequence, get the number of bytes that the codepoint sequence takes up, including the prefix byte.") {
janet_fixarity(argc, 1);
uint32_t c = (uint32_t)janet_getinteger(argv, 0);
int32_t n = ((c & 0xF8) == 0xF0) ? 4 :
((c & 0xF0) == 0xE0) ? 3 :
((c & 0xE0) == 0xC0) ? 2 :
1;
return janet_wrap_integer(n);
}
Pieces that might be worth investigating further include:
janet_fixarity
janet_getinteger
janet_wrap_integer
See the Writing a Module section of The Janet C API docs, the Writing C Functions docs, and the Wrapping Types docs for details.
For a taste, consider the following from the FFI docs:
(ffi/context nil)
(ffi/defbind memcpy :ptr
[dest :ptr src :ptr n :size])
(def buffer1 @"aaaa")
(def buffer2 @"bbbb")
(memcpy buffer1 buffer2 4)
(print buffer1)
First, the path to the dynamic library (note that nil
means to use
the current binary) to bind is specified via ffi/context
:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (ffi/context nil)
@{:map-symbols <function default-mangle> :native <core/ffi-native 0x562FB8BAEFE0>}
Use ffi/defbind
to generate bindings for a native function (in this
case memcpy
). We specify information about the name, return type and
parameters of the function:
repl:2:> (ffi/defbind memcpy :ptr [dest :ptr src :ptr n :size])
<function memcpy>
Note that the return value of ffi/defbind
is a function. This new
function will be used shortly.
Now prepare some buffers, buffer1
as a destination and buffer2
as a source:
repl:3:> (def buffer1 @"aaaa")
@"aaaa"
repl:4:> (def buffer2 @"bbbb")
@"bbbb"
Note that buffer1
is initially @"aaaa"
.
Invoke memcpy
(the function newly defined by ffi/defbind
above):
repl:5:> (memcpy buffer1 buffer2 4)
<pointer 0x562FB8BB22C0>
This should have copied from buffer2
to buffer1
. The return value
is not interesting in this case so we ignore it.
Now observe the content of buffer1
:
repl:8:> (print buffer1)
bbbb
nil
Note that the original "aaaa" is gone (nil
is the return value of
calling print
).
The content of buffer
could have been determined as follows too:
repl:9:> buffer1
@"bbbb"
For a more in-depth example, see the gtk example.
Note, I had to tweak the location of libgtk-3.so
in gtk.janet
to
be:
/usr/lib/x86_64-linux-gnu/libgtk-3.so
but after that, I had success via the invocation:
janet gtk.janet
See the Foreign Function Interface docs and the FFI Module docs for more information.
Does the following count as evidence?
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def a 2)
2
repl:2:> (defn b [] (def a 3) a)
<function b>
repl:3:> (b)
3
repl:4:> a
2
Calling janet
at the command line gives a repl:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> 1
1
There is a sometimes-helpful delimiter reminder feature:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (defn my-fn
repl:2:(> [x]
repl:3:(> (+ x 1
repl:4:((> )
repl:5:(> )
<function my-fn>
Note that in the repl's prompt after the last colon :
character and
before the greater-than >
character, there are one or more opening
delimiters in some of the above lines:
repl:3:(> (+ x 1
repl:4:((> )
These are reminders that there are unmatched opening delimiters waiting to be closed.
Simple completion is possible via the Tab key:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (do
do doc doc* doc-format doc-of
dofile
To get the above output, I pressed the Tab key after typing "(do".
There is a way to quickly see docstrings for various things:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> array/push
cfunction
src/core/array.c on line 179, column 1
(array/push arr x)
Insert an element in the end of an array. Modifies the input array and
returns it.
After typing "array/push", I entered the sequence Ctrl-G (holding the Ctrl key down and pressing and releasing the G key).
Some other key sequences such as Ctrl-A, Ctrl-E, etc. behave in similar ways to what one might use in a shell such as bash. The janet(1) man page has a longer listing. I don't know of a nicely viewable list of the key sequences on the web, but there's this bit from the main repository.
One way to get access to the bytecode debugger is to use the -d
command line option to janet
, then use the debug
function:
$ janet -d
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (debug)
debug:
in _thunk [repl] (tailcall) on line 1, column 1
entering debug[1] - (quit) to exit
debug[1]:1:> (quit)
nil
exiting debug[1]
nil
Putting a call to debug
in a function and calling that function can
be handy too:
$ janet -d
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (defn my-fn [x] (debug) (+ x 1))
<function my-fn>
repl:2:> (my-fn 8)
debug:
in my-fn [repl] on line 1, column 17
in _thunk [repl] (tailcall) on line 2, column 1
entering debug[1] - (quit) to exit
We can get a bytecode listing via .ppasm
:
debug[1]:1:> (.ppasm)
signal:
status: debug
function: my-fn [repl]
constants: @[]
slots: @[8 <function my-fn> nil nil]
lds 1 # line 1, column 1
ldn 3 # line 1, column 17
> sig 2 3 2
addim 3 0 1 # line 1, column 25
ret 3
nil
Execute one bytecode instruction via .step
:
debug[1]:2:> (.step)
nil
debug[1]:3:> (.ppasm)
signal:
status: debug
function: my-fn [repl]
constants: @[]
slots: @[8 <function my-fn> nil nil]
lds 1 # line 1, column 1
ldn 3 # line 1, column 17
sig 2 3 2
> addim 3 0 1 # line 1, column 25
ret 3
nil
...and keep going to see the function return:
debug[1]:4:> (.step)
nil
debug[1]:5:> (.ppasm)
signal:
status: debug
function: my-fn [repl]
constants: @[]
slots: @[8 <function my-fn> nil 9]
lds 1 # line 1, column 1
ldn 3 # line 1, column 17
sig 2 3 2
addim 3 0 1 # line 1, column 25
> ret 3
nil
debug[1]:6:> (.step)
9
Note that the value 9
is the return value of the function call.
When we're done we can exit the debugger to return to the original repl context:
debug[1]:7:> (quit)
nil
exiting debug[1]
See this part of boot.janet for which functions are available for the debugger and The Janet Abstract Machine docs for more on Janet bytecode and bytecode interpreter.
Also of interest might be the Debug Module docs.
From the Parsing Expression Grammars docs is:
(def ip-address
'{:dig (range "09")
:0-4 (range "04")
:0-5 (range "05")
:byte (choice
(sequence "25" :0-5)
(sequence "2" :0-4 :dig)
(sequence "1" :dig :dig)
(between 1 2 :dig))
:main (sequence :byte "." :byte "." :byte "." :byte)})
Note that this expression refers to a plain Janet struct.
Let's define it at the repl:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> (def ip-address
repl:2:(> '{:dig (range "09")
repl:3:({> :0-4 (range "04")
repl:4:({> :0-5 (range "05")
repl:5:({> :byte (choice
repl:6:({(> (sequence "25" :0-5)
repl:7:({(> (sequence "2" :0-4 :dig)
repl:8:({(> (sequence "1" :dig :dig)
repl:9:({(> (between 1 2 :dig))
repl:10:({> :main (sequence :byte "." :byte "." :byte "." :byte)})
{:0-4 (range "04") :0-5 (range "05") :byte (choice (sequence "25" :0-5) (sequence "2" :0-4 :dig) (sequence "1" :dig :dig) (between 1 2 :dig)) :dig (range "09") :main (sequence :byte "." :byte "." :byte "." :byte)}
Typically one might use this via the function peg/match
:
repl:11:> (peg/match ip-address "1")
nil
A nil
return value means there was no match.
For a match:
repl:12:> (peg/match ip-address "127.0.0.1")
@[]
One gets back an array of captures. In this case there was a match, but nothing was captured, so the array is empty.
For an example of a capturing PEG expression, try:
repl:13:> (peg/match ~(capture 3) "127.0.0.1")
@["127"]
The first 3 characters were captured as a unit and returned as an element of the "capture stack".
A slightly more complicated example is:
repl:14:> (def p ~(sequence (any :s) (capture (some :d)) (any :s) (capture (some :d))))
(sequence (any :s) (capture (some :d)) (any :s) (capture (some :d)))
repl:15:> (peg/match p " 12 89.")
@["12" "89"]
The PEG might be more readily perceived as:
(def p
~(sequence (any :s)
(capture (some :d))
(any :s)
(capture (some :d))))
Above, initial whitespace is skipped (matched but not captured), a sequence of digits is captured, more whitespace is skipped and then finally a sequence of digts is captured again.
Note that there are 2 captures on the capture stack "12" and "89" and the trailing period has not been captured.
Also note that the definition of ip-address
from earlier can be
condensed (and the key-value pairs reordered) as follows:
(def ip-address
'{:main (* :byte "." :byte "." :byte "." :byte)
:byte (+ (* "25" :0-5)
(* "2" :0-4 :d)
(* "1" :d :d)
(between 1 2 :d))
:0-5 (range "05")
:0-4 (range "04")})
*
is an alias for sequence
.
+
is an alias for choice
.
:d
is defined in default-peg-grammar
...along with a number of other things:
$ janet
Janet 1.26.0-dev-a8a78d45 linux/x64 - '(doc)' for help
repl:1:> default-peg-grammar
@{:A (if-not :a 1) :D (if-not :d 1) :H (if-not :h 1) :S (if-not :s 1) :W (if-not :w 1) :a (range "az" "AZ") :a* (any :a) :a+ (some :a) :d (range "09") :d* (any :d) :d+ (some :d) :h (range "09" "af" "AF") :h* (any :h) :h+ (some :h) :s (set " \t\r\n\0\f\v") :s* (any :s) :s+ (some :s) :w (range "az" "AZ" "09") :w* (any :w) :w+ (some :w)}
FWIW, its definition in
boot.janet
is prettier.
Especially while learning I recommend using the longer names as:
-
some of the aliases might be confusing if you are used to regular expressions (I'm looking at you
*
and+
-- though I take it these are based on those from Lua's LPEG), and -
if you return to looking at Janet code after not having looked in a while you might not immediately recall the aliases
See the Parsing Expression Grammars docs and the PEG Module for official docs.
Highly recommended is pyrmont's excellent How-To: Using PEGs in Janet article.
For simple examples and docs, follow individual links at this listing of the PEG specials along with some extracted real-world usage.
Yes, that last bit is a shameless plug :)
See the Core API docs, but also JanetDocs, a community documentation site.
Install jpm first.
Create a new project:
cd /tmp
jpm new-project yo
Answer some questions (if you want):
author? yours truly
description? yo
creating project directory for yo
Look inside:
cd yo
tree yo
yo
├── bin
├── CHANGELOG.md
├── LICENSE
├── project.janet
├── README.md
├── test
│ └── basic.janet
└── yo
└── init.janet
3 directories, 6 files
Make an executable with jpm's quickbin
subcommand:
jpm quickbin yo/init.janet executable-yo
generating executable c source executable-yo.c from yo/init.janet...
compiling executable-yo.c to build/executable-yo.o...
linking executable-yo...
Try it out:
./executable-yo
Hello!
Get curious:
cat yo/init.janet
(defn hello
`Evaluates to "Hello!"`
[]
"Hello!")
(defn main
[& args]
(print (hello)))
See the jpm docs, the jpm repository, and the generated JPM reference for more info.
Some examples of doing this include:
- The janet-lang.org website
- ahungry's puny-gui - a small cross-platform (native) GUI setup (GNU/Linux + Windows)
- greenfork's jzignet - Zig library to connect Janet and Zig together
- ianthehenry's bauble - for composing signed distance functions in a high-level language (Janet), compiling them to GLSL, and rendering them via WebGL
- MikeBeller's janet-playground - A WebAssembly based playground for Janet
- s5bug's sys-script - Controlling the Nintendo Switch with Lisp
- sepisoad's super-janet-typist - a short typing game made with janet lisp
- jaylib-wasm-demo - A demo of using jaylib in a web browser
See The Janet C API docs and the Embedding docs for more details.
-
Configurable at build time - turn features on or off for a smaller or more featureful build
See the Configuration docs.
-
Python-style generators (implemented as a plain macro)
See the generate macro and at least one example at JanetDocs.