# Godrick Godrick is a Lisp that evaluates to Nix. This guide covers how to use Godrick. A working knowledge of Nix is expected. --- ## Contents 1. [Getting started](#getting-started) 2. [A first file](#a-first-file) 3. [The basics](#the-basics) 4. [Functions](#functions) 5. [Bodies: `def` and `do`](#bodies-def-and-do) 6. [Attrsets](#attrsets) 7. [Patterns](#patterns) 8. [Logic](#logic) 9. [Working with lists](#working-with-lists) 10. [Loading other files](#loading-other-files) 11. [Macros](#macros) 12. [NixOS configuration](#nixos-configuration) 13. [Packages](#packages) 14. [Shell scripts](#shell-scripts) 15. [Errors and warnings](#errors-and-warnings) 16. [The `godrick` command](#the-godrick-command) 17. [Building ahead of time](#building-ahead-of-time) 18. [Things that may surprise you](#things-that-may-surprise-you) 19. [Quick reference](#quick-reference) 20. [Working on Godrick itself](#working-on-godrick-itself) --- ## Getting started Godrick files end in `.rick`. ### The command-line tool The flake's package is the `godrick` command: ```sh godrick compile hello.rick # show the Nix that hello.rick turns into godrick check hello.rick # look for errors and warnings godrick fmt hello.rick # tidy the indentation godrick repl # try Godrick out interactively ``` To try it without installing, use `nix run git+https://tangled.org/poacher.dev/godrick -- compile hello.rick`. To install it, use `nix profile add git+https://tangled.org/poacher.dev/godrick` (`nix profile install` on older versions of Nix). On NixOS, add `godrick.packages.${pkgs.stdenv.hostPlatform.system}.default` to `environment.systemPackages`, or apply `godrick.overlays.default` and use `pkgs.godrick`. Without flakes, the package is `pkgs.callPackage "${builtins.fetchGit "https://tangled.org/poacher.dev/godrick"}/package.nix" {}`. From a copy of this repository you can also run the script directly, as `bin/godrick compile hello.rick`. The command uses the `nix` already on your `PATH`, and it works even when the `nix-command` experimental feature is turned off. ### Loading Godrick from Nix The flake gives you these functions under `lib`: | Function | What it does | | --- | --- | | `importGodrick ./file.rick` | Works like `import`, but for a Godrick file | | `importGodrickModule ./file.rick` | Turns a Godrick file into a NixOS module, for `imports` | | `checkBuilt ./dir` | Checks that `godrick build` is up to date, for flake checks (see [Building ahead of time](#building-ahead-of-time)) | | `compile`, `check`, `format` | The compiler's own functions, used by the CLI | In a flake: ```nix { inputs.godrick.url = "git+https://tangled.org/poacher.dev/godrick"; outputs = { self, godrick, ... }: { value = godrick.lib.importGodrick ./thing.rick; }; } ``` Without flakes, use `(import (builtins.fetchGit "https://tangled.org/poacher.dev/godrick")).importGodrick ./thing.rick`. Your `flake.nix` must stay in Nix because Nix reads a flake's `inputs` before it evaluates anything. Every other file can be Godrick. --- ## A first file Save this as `hello.rick`: ```clojure ; Lines starting with ; are comments. (defn :greet :name "Hello, ${name}!") (def :people ["Ada" "Grace"]) {~greet :messages (builtins.map greet people)} ``` `godrick compile hello.rick` shows the Nix it turns into: ```nix { __dir, importGodrick, importGodrickModule }: let greet = name: "Hello, ${name}!"; people = [ "Ada" "Grace" ]; in { inherit greet; messages = builtins.map greet people; } ``` The first line is a wrapper that every compiled file gets. When the file is loaded, it is given its own directory, for relative paths, and the loaders, for importing other `.rick` files. The other examples in this guide leave the wrapper out. A file is a list of definitions followed by one final value. When you load the file, you get that final value back, just like a Nix file. --- ## The basics ### Defining and using names Every name is either being defined or being used, and the syntax tells you which: | Write | Meaning | | --- | --- | | `:name` | **Defines** `name` here: a variable, a function parameter, or an attribute | | `name` | **Uses** a name defined somewhere else | | `"text"` | A **string**, including the name of an attribute you are only looking up | | `~name` | Inside `{ }`, **both**: an attribute and a variable with the same name | So `(def :x 1)` defines `x`, and `x` on its own uses it. The colon defines a name, which is why it appears in `def`, `fn`, `loop`, attrset keys and patterns alike. `~name` is a shorthand for an attribute and variable that share a name, like Nix's `inherit`. It only appears in attrsets and attrset patterns. ### Calling things Code is written as lists in parentheses. The first item is the function and the rest are its arguments: ```clojure (add 1 2) ; add 1 2 (builtins.map f xs) ; builtins.map f xs ``` ### Values | Godrick | Nix | Notes | | --- | --- | --- | | `42`, `-1`, `3.5` | `42`, `-1`, `3.5` | Numbers | | `true`, `false`, `null` | the same | | | `"hi ${name}"` | `"hi ${name}"` | Strings, with the same escapes as Nix | | `"""` … `"""` | `''` … `''` | Multi-line strings, with the same indentation rules as Nix | | `./src`, `../lib`, `` | paths | Relative paths still work after compiling | | `[1 2 3]` | `[ 1 2 3 ]` | Lists | | `{:a 1 :b 2}` | `{ a = 1; b = 2; }` | Attrsets (see [Attrsets](#attrsets)) | ### Operators These turn into Nix's own operators: | Godrick | Nix | | --- | --- | | `(+ a b c)` | `a + b + c` | | `(- a b)`, `(* a b)`, `(/ a b)` | `a - b`, `a * b`, `a / b` | | `(== a b)`, `(!= a b)`, `(< a b)`, `(<= a b)`, `(> a b)`, `(>= a b)` | the same | | `(++ xs ys)` | `xs ++ ys` (join lists) | | `(// a b)` | `a // b` (merge attrsets) | | `(and a b)`, `(or a b)`, `(not a)` | `a && b`, `a \|\| b`, `!a` | An operator can also be passed as a value. For example, `(builtins.foldl' + 0 xs)` adds up a list. ### Names A name must be a valid Nix name: letters, digits, `_`, `'` and `-`, and it can't start with a digit. This means some names common in other Lisps aren't allowed: - `empty?`, `set!` and `*x*` are errors. Use camelCase, as nixpkgs does: `isEmpty`. - Nix keywords such as `if`, `let`, `in`, `with`, `rec`, `inherit` and `or` can't be used as names. - Names containing `__` are kept for the compiler. - `builtins`, `true`, `false` and `null` can't be redefined. An attribute whose name isn't a valid Nix name is reached with a string: `(. x "empty?")`, and defined with `(key "empty?")`. ### Reaching into attrsets ```clojure cfg.services.nginx.enable ; cfg.services.nginx.enable (. cfg "services" "nginx") ; cfg.services.nginx (the long form of the above) (get cfg "port" 80) ; cfg.port or 80 (? cfg "services" "nginx") ; cfg ? services.nginx (. x "empty?" "b") ; x."empty?".b (any name works in a string) (. x "a.b") ; x."a.b" (a string is always one name) (. x key) ; x.${key} (any expression works too) ``` --- ## Functions Nix functions take exactly one argument, so each parameter is one function. Every form but the last is a parameter, and the last is the body: ```clojure (fn :x body) ; x: body (fn :a :b body) ; a: b: body (fn {~pkgs ~lib} body) ; { pkgs, lib, ... }: body (fn {~pname ~version ? "1.0"} body) ; { pname, version ? "1.0", ... }: body (fn (@ :args {~pkgs}) body) ; args@{ pkgs, ... }: body (fn _ body) ; _: body ``` An attrset parameter always accepts attributes it doesn't name, so it always ends in `...`. `? default` gives a default for a missing attribute, as in Nix. The body is a single form. Use `do` when it needs definitions (see [Bodies](#bodies-def-and-do)). `defn` defines a named function: ```clojure (defn :add :a :b (+ a b)) ; add = a: b: a + b; (add 1 2) ; 3 ``` --- ## Bodies: `def` and `do` A body is any number of `def`s, then one final value. A file is a body. ```clojure (def :version "1.2") (def :name (+ "foo-" version)) name ``` ```nix let version = "1.2"; name = "foo-" + version; in name ``` To use a body in the middle of an expression, wrap it in `do`: ```clojure (do (def :x 1) (def :y 2) (+ x y)) ``` The `def`s in one body can refer to each other in any order, because they all become one Nix `let`. ### Pulling names out of an attrset ```clojure (def {~zlib ~openssl} pkgs) ; inherit (pkgs) zlib openssl; (def {~port ? 80} cfg) ; port = cfg.port or 80; (def {:port :p} cfg) ; p = cfg.port; ``` After this, you can write `zlib` instead of `pkgs.zlib`. `~name` keeps the attribute's name, and `:name :other` binds it as `other` instead (see [Patterns](#patterns)). --- ## Attrsets Inside `{ }`, each entry starts with `:name`, and the next item is its value: ```clojure {:pname "foo" :version "1.0"} ; { pname = "foo"; version = "1.0"; } ``` `~name` takes the variable of the same name, like Nix's `inherit`: ```clojure {~pname ~version} ; { inherit pname version; } {~pname :src ./src} ; { inherit pname; src = ./src; } ``` To take an attribute of another attrset, use `(inherit src :name ...)`: ```clojure {(inherit pkgs :zlib :openssl)} ; { inherit (pkgs) zlib openssl; } ``` To nest settings, nest the attrsets: ```clojure {:services {:openssh {:enable true}}} ; { services = { openssh = { enable = true; }; }; } ``` To compute a key, or to use a name that isn't a valid Nix name, use `(key ...)`: ```clojure {(key name) "value"} ; { ${name} = "value"; } {(key "resolv.conf") "..."} ; { "resolv.conf" = "..."; } ``` --- ## Patterns Patterns take values apart. They are used by `fn` parameters, `def`, `match` and `for`, and they look like the value they match: | Pattern | Matches | | --- | --- | | `:name` | Anything, and gives it that name | | `_` | Anything, and ignores it | | `name` | A value equal to the variable `name` (only in `match`) | | `1`, `"text"`, `true`, `null` | That exact value (only in `match`) | | `[:a :b]` | A list of exactly two items | | `[:a & :rest]` | A list of at least one item, with the remaining items in `rest` | | `{~a ~b}` | An attrset with `a` and `b`, which become variables | | `{:a pattern}` | An attrset whose `a` matches `pattern` | | `{:a :x}` | An attrset with `a`, which becomes the variable `x` | | `{~a ? 1}`, `{:a :x ? 1}` | An attrset, with `a` set to `1` if it is missing | | `(@ :all pattern)` | What `pattern` matches, with the whole value also called `all` | Attrset patterns always allow other attributes. `fn` parameters can't contain nested patterns or list patterns yet, because Nix functions can't express them; take the value whole and use `match`. ### Renaming an attribute `~name` binds attribute `name` to a variable called `name`. To use a different variable name, follow the key with a binder, as `:name :a`: ```clojure (match pair [{:name :a} {:name :b}] "${a} and ${b}") ``` Renaming is needed when two attributes would otherwise bind the same name, as in the example. It works in `def`, `match` and `for`, but not in `fn` parameters, since Nix functions can't rename an attribute; write `~name` there, and `(def :a name)` in the body. --- ## Logic ```clojure (if (> n 0) "positive" "not positive") ; both branches are required (when (> n 0) "yes") ; if n > 0 then "yes" else null (unless (> n 0) "no") ; if n > 0 then null else "no" (cond (< n 0) "negative" (== n 0) "zero" "positive") ``` A lone value at the end of `cond` is the default. If there is no default and no test matches, evaluation stops with an error that points at the `cond`. ### `match` `match` compares a value against a series of [patterns](#patterns) and uses the first one that fits: ```clojure (match v [] "empty list" [:x] "one item" [:x & :rest] "starts with ${(toString x)}" {:type "file" ~path} "file at ${path}" _ "something else") ``` A plain name compares with the variable of that name, so `(match v limit "at the limit" _ "not yet")` checks whether `v` equals `limit`. A value on its own at the end works as the default. If there is no default and nothing matches, `match` stops with an error. ### Threading `->` and `->>` let you write a chain of steps from top to bottom instead of nesting calls. A string step looks up that attribute, and a bare function name is called. `->` passes each result in as the first argument of the next step: ```clojure ; Frankly, -> isn't very useful and this is contrived cfg.services.nginx (-> cfg "services" "nginx") ``` `->>` passes it in as the last: ```clojure ; Nested, read from the inside out: (builtins.foldl' + 0 (builtins.map builtins.stringLength (builtins.filter isDjango names))) ; Threaded, read from top to bottom: (->> names (builtins.filter isDjango) (builtins.map builtins.stringLength) (builtins.foldl' + 0)) ``` Nix primarily has data as the last argument, so you'll be using `->>` most of the time. --- ## Working with lists | Form | Result | | --- | --- | | `(for :x [1 2] :y [10 20] (+ x y))` | `[11 21 12 22]`, every combination in order | | `(for [:a :b] [[1 2] [3 4]] (* a b))` | `[2 12]`; any pattern can take items apart | | `(reduce + 0 [1 2 3])` | `6`, using `builtins.foldl'` | | `(builtins.map f xs)`, `(builtins.filter f xs)` | Any Nix builtin works | ### `loop` and `recur` Nix has no loops, and deep recursion can hit its depth limit. `loop` gives you a safe, fast way to repeat something: ```clojure (loop :i 0 :total 0 (if (== i 10) total (recur (+ i 1) (+ total i)))) ; 45 ``` `loop` names some values and gives them starting values, then takes one body. `recur` starts the next round with new values, in the same order as the names. It must be the **last thing** the loop does, and it must give a new value for every name. Behind the scenes: - A simple counting loop like the one above becomes the `builtins.foldl'` you would write by hand. - Any other loop becomes recursion that is split into chunks, so it never hits Nix's depth limit. A small loop body can be copied into the output up to 32 times. That is normal. In both cases, loops run within about 10% of the speed of hand-written Nix. **Tips for fast code:** - Use `builtins.map`, `builtins.filter` and `reduce` when they fit. They run in native code. - Avoid stepping through a list with `head`/`tail`. Nix copies the list each time, so the loop gets slow on long lists. Use an index with `builtins.elemAt` instead. - Recursion that isn't the last step, such as walking a tree, still uses up Nix's depth limit. --- ## Loading other files ```clojure (import ./other.rick) ; loads a Godrick file (import ./other.nix) ; loads a Nix file, as usual ``` Godrick picks the right one from the file extension. If the path is computed, call `importGodrick` yourself: ```clojure (importGodrick (+ ./modules "/${name}.rick")) ``` Relative paths like `./src` always point next to the `.rick` file, even though the compiled Nix lives in the store. --- ## Macros A macro is a function that runs while your code is being compiled. It takes code as input and returns new code, which goes in its place. Most of Godrick's own features, such as `defn`, `cond`, `match` and `->`, are macros. ### Writing a macro Use a backquote to write a code template, `,` to insert a value, and `,@` to insert a list of items: ```clojure (defmacro :unless [:c & :body] `(if ,c null (do ,@body))) ``` The parameters are a list pattern over the macro's arguments, so `& :body` collects any remaining arguments into a list. Godrick checks the number of arguments every time a macro is used. A macro's body is one form. A macro can also return plain data. Strings, numbers, booleans, `null` and lists all work: ```clojure (defmacro :answer [] 42) ``` ### Choosing safe names inside a macro A macro's code is pasted into your code, so a name it defines could clash with one of yours. To avoid this, end the name with `#`, and Godrick will replace it with a unique one. `:t#` defines it and `t#` uses it: ```clojure (defmacro :orElse [:e :fallback] `(do (def :t# ,e) (if (== t# null) ,fallback t#))) (fn :t (orElse t 0)) ; t: let t__1 = t; in if t__1 == null then 0 else t__1 ``` If a macro defines a plain name without `#`, Godrick gives you an error. If you really do want to create a name for the user, write it as `,(binder "name")`; `(sym "name")` makes the matching use of it, and `(pun "name")` makes a `~name`. **Store values you use more than once.** If a macro inserts an argument twice, Nix computes it twice. Define it once with `def` and a `#` name, as `orElse` does. ### `definline`: functions that are pasted in `definline` defines a small helper that behaves exactly like a function, but its body is pasted where it is used, so the call costs nothing: ```clojure (definline :isEmpty :xs (== (builtins.length xs) 0)) (isEmpty ys) ; builtins.length ys == 0 (builtins.map isEmpty xss) ; builtins.map (xs: builtins.length xs == 0) xss ``` An inline function can only use its own parameters, `builtins` and other inline functions. It can't call itself. ### Sharing macros between files ```clojure (import-macros ./lib/my-macros.rick :orElse :isEmpty) ``` This loads the named `defmacro`s and `definline`s from another file, defining them in this one. You have to name each one you want. `defmacro`, `definline` and `import-macros` must appear at the top level of a file. ### Notes - A name you define yourself, with `def`, `defn` or a function parameter, takes priority over a macro with the same name. So `(def :match builtins.match)` makes `match` a normal function again. - A macro can only use `builtins`, `sym`, `binder`, `pun`, and macros defined earlier in the file. Your other definitions don't exist yet when the macro runs. - Errors in code a macro produces point at the place where you used the macro. - `'(1 2 3)` is quoted code for macros, not a list. Use `[1 2 3]` for a list. --- ## NixOS configuration A configuration can be written much like Nix, and is about the same length: ```clojure (fn {~config ~lib ~pkgs} {:imports [./hardware.nix ./greeter.rick] :networking {:hostName "box"} :services {:openssh {:enable true}} :users {:users {:alice {:isNormalUser true :extraGroups ["wheel"]}}} :environment {:systemPackages (from pkgs ["git" "vim" "htop"])} :system {:stateVersion "26.05"}}) ``` Load it from Nix with `importGodrickModule`: ```nix nixosSystem { modules = [ (godrick.lib.importGodrickModule ./configuration.rick) ]; } ``` Any `.rick` file listed in `:imports` is loaded the same way, so error messages, `disabledModules` and duplicate handling behave exactly as they do for `.nix` files. ### `from` `(from pkgs ["git" "vim"])` turns into `[ pkgs.git pkgs.vim ]`. It is nearly as short as `with pkgs; [ git vim ]`, without hiding where the names come from. Each string is one attribute name, so names that aren't valid Nix names work (`"0ad"`). For nested packages, give the nested attrset as the source and join the lists: ```clojure (++ (from pkgs ["git" "vim"]) (from pkgs.python3Packages ["requests"])) ``` ### `defmodule` `defmodule` writes the usual module boilerplate for you: ```clojure (defmodule ["services" "greeter"] ; options {:enable (lib.mkEnableOption "the greeter") :name (option lib.types.str "world" "Who to greet.") :package (option lib.types.package pkgs.hello "The package that greets.")} ; config {:environment {:systemPackages [cfg.package] :etc {:greeting {:text "hello ${cfg.name}\n"}}}}) ``` It does the following for you: - Takes `{ config, lib, pkgs, ... }` as the module's arguments - Sets `cfg` to `config.services.greeter` - Puts your options under `options.services.greeter` - Wraps your config in `lib.mkIf cfg.enable` if there is an `enable` option You can pass a fourth attrset to add other top-level attributes, such as `{:imports [...]}`. For modules that need something else, write the module with `fn` instead. ### `option` `(option type default description)` turns into `lib.mkOption { type = ...; default = ...; description = ...; }`. Everything after the type is optional, and you can add extra keys at the end: ```clojure (option lib.types.str "world" "Who to greet." :example "godrick") ``` ### `lib.mkIf` and friends `lib.mkIf`, `lib.mkDefault`, `lib.mkForce` and `lib.mkMerge` are normal functions. Call them directly. Don't use `when` in place of `lib.mkIf`, because they do different things. --- ## Packages A package is a function that takes its dependencies, just like in nixpkgs: ```clojure (fn {~lib ~stdenv ~fetchurl} (stdenv.mkDerivation (fn :finalAttrs {:pname "hello" :version "2.12.3" :src (fetchurl {:url "mirror://gnu/hello/hello-${finalAttrs.version}.tar.gz" :hash "sha256-..."}) :meta {:license lib.licenses.gpl3Plus :mainProgram "hello"}}))) ``` ```nix pkgs.callPackage (godrick.lib.importGodrick ./hello.rick) { } ``` `callPackage` sees the same argument names as it would for a Nix file. The `examples/` folder contains `hello`, `figlet` and `cowsay` written in Godrick. Each one builds exactly the same derivation as nixpkgs. Shell code such as `postInstall` can stay a string, or be written with `bash` (see the next section). --- ## Shell scripts `bash` writes a shell script as forms. It turns into an ordinary Nix string, so it works anywhere a script does: a build phase, `writeShellScript`, a systemd `script`. ```clojure :postInstall (bash (wrapProgram $out/bin/cowsay --suffix COWPATH ":" $out/share/cowsay/cows) (cp -ar "${finalAttrs.contributed}/*" $out/share/figlet/) (for :f [$out/share/*.txt] (chmod 644 $f))) ``` ```nix postInstall = '' wrapProgram $out/bin/cowsay --suffix COWPATH : $out/share/cowsay/cows cp -ar "${finalAttrs.contributed}"/* $out/share/figlet/ for f in $out/share/*.txt; do chmod 644 $f done ''; ``` Each argument is a command, and each command is printed on its own line. The script is bash, as every build phase is, so stdenv's functions (`wrapProgram`, `substituteInPlace`, `installManPage`, …) are ordinary commands. ### Words Inside `bash`, a name is a shell word, not a Godrick name. `cp`, `-ar`, `$out/bin` and `&&` mean exactly what they say. Pipes, redirects, `&&` and `||` are words too: `(make && make install)`, `(sort a | uniq > $out/list)`. | Write | Shell | | --- | --- | | `cp`, `-ar`, `$out/bin`, `./configure`, `2>&1` | The same text | | `"text"` | The text as it is. Use a string for anything a name can't hold, such as `":"`, `";"` or `"'two words'"`. | | `"${x}/*"` | `"${x}"/*`: an interpolation is double-quoted, and the rest of the string isn't | | `,x` | `"${x}"`, a Nix value as one word | | `,@xs` | Each item of the Nix list `xs` as its own double-quoted word | | `(date +%s)` | `"$(date +%s)"` | | `(<< "text")` | A heredoc (see below) | Double-quoting Nix values means a value with spaces stays one word. Don't add quotes around an interpolation yourself. Shell variables are plain words: `$out`, `$f`. Write `${...}` inside a string, escaped as in Nix: `"\${!outputBin}/bin"`. Paths are words too, so `./configure` runs the script in the build directory. To use a file from your source tree, unquote it: `,./fix.patch`. `[[ ... ]]` is not supported, because `[` starts a Godrick list. Use `test`, or write the test in a string: `(if "[[ -d $out/doc ]]" ...)`. ### Control flow | Form | Shell | | --- | --- | | `(if test then)`, `(if test then else)` | `if ...; then ... else ... fi`. An `if` in the else branch becomes `elif`. | | `(for :f [words...] commands...)` | `for f in words; do ... done` | | `(while test commands...)` | `while ...; do ... done` | | `(case word pattern command ...)` | `case word in pattern) ... ;; esac` | | `(defn :name commands...)` | `name() { ... }` | | `(do commands...)` | `{ ...; }` | | `(sub commands...)` | `( ... )`, a subshell | A test is one command, such as `(test -f x)` or a string. An `if` or `case` branch is one command, and `(do ...)` groups several there without braces. A group can be redirected by putting it first in a command: `((do (make) (make check)) > log 2>&1)`. The names in `for` and `defn` define shell names, not Godrick ones, so write `$f` to use the loop variable. They must be valid shell names: letters, digits and `_`. ### Heredocs `(<< text)` is a heredoc. The shell never expands its text, so `$` needs no escaping. Nix interpolations are inserted as they are, and `(placeholder "out")` stands for `$out`: ```clojure (cat > $out/bin/cowthink (<< """ #!/usr/bin/env perl $0 = "cowthink"; do "${(placeholder "out")}/bin/.cowsay-wrapped"; """)) ``` ### Raw shell, and helpers A string or a `,x` on its own, in place of a command, is copied in as lines of shell. This lets you move a script to `bash` one line at a time, and write anything `bash` doesn't cover: ```clojure (bash (make) """ install -Dm755 tool $out/bin/tool """) ``` To reuse shell code, write a function that returns a script and splice it in with `,`: ```clojure (defn :installDocs :dir (bash (mkdir -p ,dir) (cp -r docs/* ,dir))) (bash (make) ,(installDocs "$out/share/doc")) ``` A spliced script is followed by a newline, so a script that already ends in one leaves a blank line. That makes no difference to the shell. `;` comments are dropped, as everywhere in Godrick. --- ## Errors and warnings Godrick errors name the file, line and column: ``` app.rick:3:1: 'c' is not defined app.rick:2:1: duplicate def 'a' app.rick:2:4: 'f' requires attribute 'b' app.rick:1:6: binder ':isEmpty?' must be a Nix identifier; write (key "isEmpty?") for other attribute names app.rick:1:6: expected a binder; write :x to bind x app.rick:1:50: recur is only allowed in tail position inside a loop ``` You see these errors when the file is loaded or when you run `godrick check`. Warnings only appear from `godrick check`, so they never clutter a normal build: ``` app.rick:2:6: warning: unused def 'unused' app.rick:3:4: warning: 'f' does not use attribute 'c' ``` To keep an unused name without a warning, start it with `_`. The second warning comes from calling a function in the same file with an attribute its pattern doesn't name; the function accepts it, but it is often a typo. Errors that happen while Nix runs the code point at the generated Nix. The output is close to what you wrote, so it is usually easy to find the matching line. To see it, run `godrick compile`. --- ## The `godrick` command | Command | What it does | | --- | --- | | `godrick compile FILE` | Prints the Nix that a file turns into. This is for reading, not for deploying. | | `godrick check FILE...` | Prints errors and warnings. Exits with a failure if any file has an error. | | `godrick fmt FILE...` | Fixes indentation in place. | | `godrick fmt --check FILE...` | Lists files that need formatting, without changing them. | | `godrick build [PATH...]` | Compiles files ahead of time into `.godrick` directories. See [Building ahead of time](#building-ahead-of-time). | | `godrick build --check [PATH...]` | Lists files whose build is missing or out of date, without changing anything. | | `godrick repl` | Opens `nix repl` with the compiler loaded. | The formatter only changes indentation. It keeps your line breaks and comments, and it never touches the inside of strings. In the REPL, `rick` runs a piece of Godrick code: ``` nix-repl> rick "(for :x [1 2] (* x 10))" [ 10 20 ] ``` ### Editor setup Godrick looks enough like Clojure for most editors. Tell your editor to treat `*.rick` as Clojure for syntax highlighting. Paren-editing tools such as paredit, parinfer and smartparens work as they are. Clojure formatters such as cljfmt and zprint don't work, so use `godrick fmt` instead. --- ## Building ahead of time Normally Godrick compiles each file every time Nix evaluates it. A large configuration can spend a noticeable part of its evaluation doing that. `godrick build` compiles your files once, ahead of time: ```sh godrick build # every .rick file under the current directory godrick build modules/ # or under the paths you give ``` Each `dir/name.rick` is compiled to `dir/.godrick/name.nix`. Files whose names start with a dot are skipped, as are `.git` and `.godrick` directories. When `importGodrick` (or `import`, or `importGodrickModule`) loads a file, it uses the built version if it is up to date, and skips compiling. The first line of a built file records hashes of its source, of the compiler that built it, and of any files it loads macros from. The built file is up to date while all of these still match. If one has changed and the file wasn't rebuilt, Godrick compiles the file as usual and prints a warning: ``` trace: godrick: warning: /…/.godrick/app.nix is out of date, so /…/app.rick was compiled instead; run `godrick build` ``` --- ## Things that may surprise you - There is no `with` or `rec` as these are poor practices. - `{:a :b}` is not two names. In a pattern it binds attribute `a` as the variable `b`, and in an attrset it is an error, since `:b` is not a value. Write `{~a ~b}` for both. - Godrick needs to write to the Nix store while evaluating through `builtins.toFile`. However, Godrick doesn't use import-from-derivation. --- ## Quick reference ### Core forms | Form | Becomes in Nix | | --- | --- | | `(fn param... body)` | `x: ...`, `{ a, b ? d, ... }: ...`, `x@{ ... }: ...` | | `(def :name value)` | A binding in the surrounding `let` | | `(def {~a ~b} src)` | `inherit (src) a b;` | | `(do defs... value)` | `let ... in value` | | `(if c a b)` | `if c then a else b` | | `(. x "a" "b")` or `x.a.b` | `x.a.b` | | `(get x "a" default)` | `x.a or default` | | `(? x "a")` | `x ? a` | | `(assert c)` | `assert c; ...` | | `(loop :n init ... body)` / `(recur ...)` | A `foldl'` or safe recursion | | `(defmacro :name [:arg ...] body)` | Nothing. It runs while compiling. | | `(definline :name :arg ... body)` | Pasted where it is used | | `(import-macros ./f.rick :name...)` | Nothing. It loads macros. | ### Built-in macros | Group | Macros | | --- | --- | | Definitions | `defn` | | Choices | `when`, `unless`, `cond`, `match` | | Chains | `->`, `->>` | | Lists | `for`, `reduce` | | NixOS | `defmodule`, `option`, `from` | | Shell | `bash` | ### Syntax | Write | Meaning | | --- | --- | | `( )` | Call | | `[ ]` | List, or a list pattern | | `{ }` | Attrset, or an attrset pattern | | `;` | Comment until end of line | | `:name` | Defines a name: a variable, parameter or attribute | | `~name` | Takes the variable `name` into an attrset, or binds attribute `name` in a pattern | | `a.b.c` | Attribute access | | `./path`, `` | Paths | | `"..."`, `"""..."""` | Strings, with `${...}` interpolation | | `_`, `&`, `?`, `@` | Pattern parts: ignore, rest, default, whole value | | `'x`, `` `x ``, `,x`, `,@x` | Quote, template, insert, insert-all (for macros) | | `:x#`, `x#` | A unique name inside a macro template | --- ## Working on Godrick itself | File | What it is | | --- | --- | | `compiler.rick` | The compiler, written in Godrick | | `.godrick/compiler.nix` | The same compiler, built by itself (generated, but committed). It is an ordinary built file: its header records its own hash, so it is up to date exactly when it reproduces itself. | | `loader.nix` | Loading files from Nix (`importGodrick`) and ahead-of-time builds, in plain Nix. It isn't part of the compiler, so changing it needs no bootstrap and leaves built files up to date. | | `bin/godrick`, `package.nix` | The command-line tool, and its package. The package installs the script with the compiler and loader beside it, under `share/godrick`. If the script starts loading another file, add it to the package's file set. | | `bootstrap.nix` | Rebuilds `.godrick/compiler.nix` until it settles, and checks that it compiles itself to the same output | | `tests/` | The test suite. Run it with `tests/run.sh`; `nix flake check` runs the parts that need neither `` nor error-message matching. `tests/cache/fresh` is built ahead of time, so rebuild it with `bin/godrick build tests/cache/fresh` after any change to the compiler. | | `examples/` | Packages and a NixOS system that must match their Nix versions exactly. `nix build -f examples/build.nix` also builds the `*-bash.rick` packages and checks that their outputs match nixpkgs. | After you change `compiler.rick`: ```sh nix eval --raw -f bootstrap.nix built > .godrick/compiler.nix.new && mv .godrick/compiler.nix.new .godrick/compiler.nix nix eval -f bootstrap.nix check # must be true bin/godrick build tests/cache/fresh # the new compiler makes this fixture out of date tests/run.sh ``` Write to a new file first. `.godrick/compiler.nix` is used to build the new version, so writing straight into it would wipe it before it is read. `bin/godrick build compiler.rick` also works, but each run is only one step. A change can take two or three runs to settle (until `bin/godrick build --check compiler.rick` passes), which is what `bootstrap.nix` does in one go. Never run a formatter such as alejandra on `.godrick/compiler.nix`, the golden files or `.godrick` directories. If `compiler.rick` needs a new compiler feature, add the feature first, rebuild `.godrick/compiler.nix`, and only then start using it.