The Urn Logo

Latest posts

Version 0.2.1 released

It’s friday, it’s five to five and it’s Crackerjack Urn update time. Well, only one of those are true but you should still be excited. There are a couple of exciting things in this update, so let’s get cracking.

Top-level unquote

If you’ve been following the tutorials you’ll recall that syntax-quote and unquote allow you to step up and down the “compilation ladder”, syntax-quote moving from code to data and unquote moving back up to code. With this update, you can unquote even further up the ladder, allowing you to execute arbitrary code at compile time without the use of macros.

For example, say you want to have a list of the first 10 square numbers. Previously, you could define it as

(define squares (map (cut ^ <> 2) (range 1 10)))

However, this means it’ll be executed every time you run your program. This could slow it down. Instead, let’s generate these at compile time:

(define squares ,(cons `list (map (cut ^ <> 2) (range 1 10))))

This compiles to

(define squares (list 1 4 9 16 25 36 49 64 81 100))

Of course, there are far more powerful applications of this which we won’t go into here.

Property checking

Property checking is a form of testing where you can write some form of assertion and it will generate random inputs, trying to break it. For instance:

(check [(list x)]
  (eq? x (reverse (reverse x))))

Will check if reversing a list twice always results in the same list. This means you can write general assertions for all inputs, rather than specific test cases. For more information, see the documentation.

Lambda-binding syntax

let and friends (let*, letrec, etc…) now allow creating functions just by specifying the arguments and function body - no lambda required!

(letrec [(factorial (x accum)
           (if (= x 0)
             accum
             (factorial (- x 1) (* accum x))))]
  (print! (factorial 3 1)))

Version 0.2 released

It’s sometime after 11o’clock (well, it is in my timezone) which means it must be time for an Urn update!

Line mappings

We’ve finally got round to adding line mapping, which means any code executed whilst using the Urn compiler will have its positions mapped back to the original source. For instance, consider the following code:

(defun foo (x y)
(+ x y))
(defun do-bar! (x y)
(succ (foo (succ y))))
(print! (do-bar! nil 23))

When executed (lua run.lua demo/fail.lisp --run), you’ll get something like:

demo/fail.lisp:2 (out.lua:24): attempt to perform arithmetic on a nil value (local 'y')
stack traceback:
demo/fail.lisp:2 (out.lua:24): in upvalue 'foo'
demo/fail.lisp:5 (out.lua:27): in local 'do-bar!'
demo/fail.lisp:7 (out.lua:29): in main chunk

As you can see, all line numbers are converted back to their positions in the root lisp file. In addition to that, names are un-mangles, meaning you get do-bar! rather than doBar_21_1. It is worth noting that this metadata is not persisted, so running the compiled code directly will not result in mapped lines.

Benchmarking

We’ve also added a fancy benchmarking and timing library, meaning you can wrap function definitions with (time! ...) in order to time every invocation. For instance:

(time! (defun foo (bar baz)
(with (sum 0)
(for i bar baz (set! sum (+ sum i))))))

Also in this release:

  • Add for-pairs macro for easier iteration over structs.
  • Remove pair library.

Version 0.1.4 released

Well, another release with a load more improvements! The highlights:

  • Add support for module level documentation.
  • Further improvements to how variables are inlined, meaning even more constant expressions get folded.
  • Pattern matching! Ahahahr. Hype! Hype!

Right, if you’ve never seen pattern matching before, then you can think of it as a switch statement on steroids. In short, it allows you to compare the specified object against a series of “patterns”: these can be constants, descriptions of a list, or arbitrary functions. For instance, the pattern:

(case x
  [((?x ?y) . ?xs) (print! "List where the head has two entries" x y (pretty xs))]
  [(?x . ?xs) (print! "List with head and tail" (pretty x) (pretty xs))]
  [() (print! "Empty list")])
  • If you run it against a list looking something like ((1 2) 3 4 5) then the first pattern will match, with x = 1, y = 2, xs = (3 4 5) as it has the same “layout”.
  • If it is a list with at least one entry (such as (1 2 3)) then the second pattern matches, giving x = 1, xs = (2 3).
  • If the above pattern didn’t work, then we must have an empty list, meaning the third pattern matches.

As you can see, this is much shorter than the equivalent Lua (or Lisp without match) code. For the full capabilities of the pattern matching library, see the documentation.

New documentation site

One of the common pieces of feedback we’ve had is that people don’t really know what they’re doing with Urn. Over the last couple of days I’ve been working on a series of tutorials for Urn: explaining some of the language features, introducing macros, and what not. Hopefully this make getting started with Urn a breeze.

However, that being said, getting documentation right is hard - especially if you are overly familiar with the language already. If you do struggle to understand something it means the docs are failing! Drop me a line (through the forums, GitHub, GitLab, Discord, Gitter, etc…) and I’d be happy to clarify things and fix the docs.

Many thanks to all those people who have given feedback on Urn so far. Go forth, and try out the docs!

Version 0.1.3 released

I’ve just pushed another release of Urn.

Firstly, good news for Windows users. Urn will attempt to work out whether your terminal supports ANSI colours, only using them if so. If your terminal is incorrectly detected, please create an issue on GitHub or GitLab. I’ve also worked in improving the optimiser. It now will correctly simplify variable accesses to their root value. Something like:

(define x <compile_expr>)
(define y x)
(define z y)
(print! z)

will simplify to

(define x <compile_expr>)
(print! x)

You can see the result of this optimisation on files like this.