Release v1.4.0
Risor v1.4.0 is here! This release includes several interesting new features that you also might find familiar if you know Go. For anyone new to Risor, it's a scripting language written in Go that interoperates well with all the existing Go libraries and its overall ecosystem.
For a quick taste, here's an example of an HTTP server one-liner using the Risor CLI:
risor -c 'http.listen_and_serve(":8085", func(w, r) { "OK" })'
New Features
This release adds support for the go
and defer
keywords, channels, HTTP
servers, improved Risor VM cloning, CLI utilities, and range
and from-import
statement improvements. More details on all of this below.
New Risor Keywords
The go
keyword is now supported, with a direct correspondence to the underlying
capability from the Go runtime. This allows you to run Risor code concurrently.
Under the hood, when go
is used, the active Risor VM is cloned in order to
have a separate Risor frame and data stack for the new goroutine. Since Risor
VMs are lightweight, this is in-fitting with Go's own lightweight goroutines.
go func(n) {
for i := 0; i < n; i++ {
print("count:", i)
}
}(4)
time.sleep(1)
Outputs:
count: 0
count: 1
count: 2
count: 3
Similarly, there is now Risor support for the Go defer
keyword, again with a
fairly direct correspondence to the underlying Go feature. Multiple defer
statements can be made and they are executed in reverse order when the function
returns.
func() {
defer print("world")
defer print("hello")
}()
Outputs:
hello
world
Channels
Channels are now supported and are the primary means of communication between
Risor goroutines. All channels are typed to Risor's object.Object
type.
Channel buffering is configurable as in Go, and channels can be closed. The
<-
operator is used to send and receive values.
As a convenience, a new channel can be created simply by calling chan()
with
an optional buffer size. Creation using make(chan)
is also supported. Note that
there is no type parameter since all channels are typed to object.Object
.
Iteration over channels behaves as in Go.
ch := chan(2)
ch <- 1
ch <- 2
close(ch)
for _, value := range ch {
print(value)
}
HTTP servers
Risor can now be used to run HTTP servers and it leverages the new HTTP mux behavior from the Go 1.22 release. I think this really nicely shows how Risor's concise syntax can be used to quickly put together a simple backend.
As a convenience in Risor, handler return values may be used as the response body. If the return value is a string or byte slice, it is written to the body as-is. If the return value is a map or list, it is marshaled to JSON automatically and written to the body.
Since the return
statement can also be omitted in Risor, you can also just
have the handler implicitly return the value of the last expression.
http.handle("/", func(w, r) {
return "OK"
})
// Explicit write_header and write calls
http.handle("POST /messages", func(w, r) {
w.write_header(201)
w.write("message sent")
})
// Using path value feature from Go 1.22, with implicit JSON response marshaling
http.handle("/animals/{name}", func(w, r) {
return { animal: r.path_value("name") }
})
http.listen_and_serve(":8081")
CLI Apps
It was already possible to write simple CLIs in Risor, but this release adds more sophisticated support by wrapping urfave/cli (opens in a new tab).
In your Risor script, use the following shebang line to ensure that CLI args are passed correctly to the CLI app:
#!/usr/bin/env risor --
Building the CLI app then looks very similar to how you would do it in Go when
using urfave/cli
. Create an app, add commands and flags, then run the app.
#!/usr/bin/env risor --
cli.app({
name: "encode",
help_name: "encode",
description: "Encode input text using various codecs",
commands: [
cli.command({
name: "base64",
flags: [
cli.flag({
name: "verbose",
aliases: ["v"],
usage: "Print verbose output",
env_vars: ["VERBOSE"],
value: false,
}),
],
action: func(ctx) {
if ctx.bool("verbose") {
// yadda yadda
}
for _, text := range ctx.args() {
print(encode(text, "base64"))
}
}
}),
]
}).run()
Range Over Integers
The range
statement now supports ranging over integers, as is now available in
Go 1.22:
for i := range 5 {
print(i)
}
Note that Risor is a bit more flexible with range statements than Go, since
Risor's range
statement can be used with any object that implements the
Risor object.Iterable
interface. That's why all these examples work in Risor:
for i := range [1, 2, 3] {
print(i)
}
for i, s := range "hello" {
print(i, s)
}
it := iter(5)
for i := range it {
print(i)
}
From-Import Improvement
The from-import statement now supports importing multiple names from a package with optional name aliasing. This is quite similar to Python's from-import behavior.
from cli import (
app,
command,
flag,
)
Wrapping Up
Thanks for reading about the new release. As usual, please drop in on the GitHub discussions (opens in a new tab) to share any feedback or ask questions.
If you're new to Risor, the quickest way to install it is using Homebrew:
brew install risor
Happy scripting!