about summary refs log tree commit diff
path: root/users/tazjin/nisp/transform.el
blob: 89b2bb104d27277463a44c3c5210012417bf8f81 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
;; Nix as a Lisp

(require 'cl-lib)
(require 'json)
(require 's)
(require 'dash)

(defun nisp/expr (form)
  "Entrypoint for Nisp->Nix transformation. Will translate FORM
into Nix code, if it is a valid Nisp expression.

To make code generation slightly easier, each
expression (including literals) is wrapped in an extra pair of
parens."
  (concat
   "("
   (pcase form
     ;; Special keywords
     ('() "null")
     (`(let . ,rest) (nisp/let form))
     (`(fn . ,rest) (nisp/fn form))
     (`(if ,cond ,then ,else) (nisp/if cond then else))

     ;; Nix operators & builtins that need special handling
     (`(or  ,lhs ,rhs) (nisp/infix "||" lhs rhs))
     (`(and ,lhs ,rhs) (nisp/infix "&&" lhs rhs))
     (`(> ,lhs ,rhs) (nisp/infix ">" lhs rhs))
     (`(< ,lhs ,rhs) (nisp/infix "<" lhs rhs))
     (`(>= ,lhs ,rhs) (nisp/infix ">=" lhs rhs))
     (`(<= ,lhs ,rhs) (nisp/infix "<=" lhs rhs))
     (`(+ ,lhs ,rhs) (nisp/infix "+" lhs rhs))
     (`(- ,lhs ,rhs) (nisp/infix "-" lhs rhs))
     (`(* ,lhs ,rhs) (nisp/infix "*" lhs rhs))
     (`(/ ,lhs ,rhs) (nisp/infix "/" lhs rhs))
     (`(-> ,lhs ,rhs) (nisp/infix "->" lhs rhs))
     (`(? ,lhs ,rhs) (nisp/infix "?" lhs rhs))
     (`(// ,lhs ,rhs) (nisp/infix "//" lhs rhs))
     (`(++ ,lhs ,rhs) (nisp/infix "++" lhs rhs))
     (`(== ,lhs ,rhs) (nisp/infix "==" lhs rhs))
     (`(!= ,lhs ,rhs) (nisp/infix "!=" lhs rhs))
     (`(! ,term) (concat "!" (nisp/expr term)))
     (`(- ,term) (concat "-" (nisp/expr term)))

     ;; Attribute sets
     (`(attrs . ,rest) (nisp/attribute-set form))

     ;; Function calls
     ((and `(,func . ,args)
           (guard (symbolp func)))
      (nisp/funcall func args))

     ;; Primitives
     ((pred stringp) (json-encode-string form))
     ((pred numberp) (json-encode-number form))
     ((pred keywordp) (substring (symbol-name form) 1))
     ((pred symbolp) (symbol-name form))

     ;; Lists
     ((pred arrayp) (nisp/list form))

     (other (error "Encountered unhandled form: %s" other)))
   ")"))

(defun nisp/infix (op lhs rhs)
  (concat (nisp/expr lhs) " " op " " (nisp/expr rhs)))

(defun nisp/funcall (func args)
  (concat (symbol-name func) " " (s-join " " (-map #'nisp/expr args))))

(defun nisp/let (form)
  (pcase form
    (`(let . (,bindings . (,body . ()))) (concat "let "
                                                 (nisp/let bindings)
                                                 (nisp/expr body)))
    (`((:inherit . ,inherits) . ,rest) (concat (nisp/inherit (car form))
                                               " "
                                               (nisp/let rest)))
    (`((,name . (,value . ())) .,rest) (concat (symbol-name name) " = "
                                               (nisp/expr value) "; "
                                               (nisp/let rest)))
    ('() "in ")
    (other (error "malformed form '%s' in let expression" other))))

(defun nisp/inherit (form)
  (pcase form
    (`(:inherit . ,rest) (concat "inherit " (nisp/inherit rest)))
    (`((,source) . ,rest) (concat "(" (symbol-name source) ") " (nisp/inherit rest)))
    (`(,item . ,rest) (concat (symbol-name item) " " (nisp/inherit rest)))
    ('() ";")))

(defun nisp/if (cond then else)
  (concat "if " (nisp/expr cond)
          " then " (nisp/expr then)
          " else " (nisp/expr else)))

(defun nisp/list (form)
  (cl-check-type form array)
  (concat "[ "
          (mapconcat #'nisp/expr form " ")
          "]"))


(defun nisp/attribute-set (form)
  "Attribute sets have spooky special handling because they are
not supported by the reader."
  (pcase form
    (`(attrs . ,rest) (concat "{ " (nisp/attribute-set rest)))
    ((and `(,name . (,value . ,rest))
          (guard (keywordp name)))
     (concat (substring (symbol-name name) 1) " = "
             (nisp/expr value) "; "
             (nisp/attribute-set rest)))
    ('() "}")))

(defun nisp/fn (form)
  (pcase form
    (`(fn ,args ,body) (concat
                              (cl-loop for arg in args
                                       concat (format "%s: " arg))
                              (nisp/expr body)))))

;; The following functions are not part of the transform.

(defun nisp/eval (form)
  (interactive "sExpression: ")
  (when (stringp form)
    (setq form (read form)))

  (message
   ;; TODO(tazjin): Construct argv manually to avoid quoting issues.
   (s-chomp
    (shell-command-to-string
     (concat "nix-instantiate --eval -E '" (nisp/expr form) "'")))))

(defun nisp/eval-last-sexp ()
  (interactive)
  (nisp/eval (edebug-last-sexp)))