├── LICENSE ├── Readme.org ├── default.nix ├── extract-vars.nix └── lisp.nix /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Silvan Mosberger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.org: -------------------------------------------------------------------------------- 1 | * Nixlisp 2 | 3 | Lisp in Nix! 4 | 5 | ** Usage 6 | 7 | #+BEGIN_SRC nix 8 | with import ./lisp.nix { a = null; }; eval 9 | (defun fib (n) 10 | (defun fib2 (a b n) 11 | (iff (lt n 2) b 12 | (def (next (plus a b)) 13 | (fib2 b next (minus n 1)))) 14 | (fib2 0 1 n)) 15 | (fib 60)) 16 | #+END_SRC 17 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | with import ./lisp.nix { a = null; }; eval 2 | (defun fib (n) 3 | (defun fib2 (a b n) 4 | (iff (lt n 2) b 5 | (def (next (plus a b)) 6 | (fib2 b next (minus n 1)))) 7 | (fib2 0 1 n)) 8 | (fib 60)) 9 | -------------------------------------------------------------------------------- /extract-vars.nix: -------------------------------------------------------------------------------- 1 | # Extracts all variable names from a string 2 | { content # The string to extract variables from 3 | , varFirst ? "a-zA-Z_" # Regex for first variable character 4 | , varFollow ? varFirst + "0-9" # Regex for variable characters after initial one 5 | }: let 6 | inherit (builtins) head substring stringLength match; 7 | 8 | countLines = str: 9 | let matched = match "([^\n]*\n).*" str; 10 | in if matched == null then 0 11 | else 1 + countLines (substring (stringLength (head matched)) (-1) str); 12 | 13 | # Drops characters from a string until a varFirst one is found 14 | skipNonVars = { str, line }: 15 | let matched = head (match "([^${varFirst}]*).*" str); 16 | lineDiff = countLines matched; 17 | in { 18 | str = substring (stringLength matched) (-1) str; 19 | line = line + lineDiff; 20 | }; 21 | 22 | # Takes a variable from the start of a string and returns it along with the 23 | # rest string, or null if there's no variable at the start 24 | getVar = { str, line }: let 25 | matched = match "([${varFirst}][${varFollow}]*).*" str; 26 | in if isNull matched then null else { 27 | var = { 28 | sym = head matched; 29 | inherit line; 30 | }; 31 | str = substring (stringLength (head matched)) (-1) str; 32 | inherit line; 33 | }; 34 | 35 | # Extracts all variables from a string 36 | getVars = { str, line }: 37 | let next = getVar (skipNonVars { inherit str line; }); 38 | in if next == null then [] else 39 | [ next.var ] ++ getVars { str = next.str; inherit (next) line; }; 40 | 41 | in getVars { str = content; line = 1; } 42 | -------------------------------------------------------------------------------- /lisp.nix: -------------------------------------------------------------------------------- 1 | with import ; 2 | 3 | { a }@set: let 4 | 5 | skipLines = str: let 6 | skipLines' = i: num: 7 | if num == 0 then substring i (-1) str else 8 | if substring i 1 str == "\n" then skipLines' (i + 1) (num - 1) 9 | else skipLines' (i + 1) num; 10 | in skipLines' 0; 11 | 12 | vars = map (x: x.sym) (import ./extract-vars.nix { 13 | content = let inherit (builtins.unsafeGetAttrPos "a" set) file line; 14 | in skipLines (builtins.readFile file) line; 15 | }); 16 | 17 | /* 18 | For each variable, generate an attribute set containing its name 19 | and make it callable with __functor, such that `(a b c)` works, resulting in 20 | something like 21 | 22 | { 23 | sym = "a" 24 | args = [ 25 | { sym = "b"; } 26 | { sym = "c"; } 27 | ]; 28 | } 29 | 30 | */ 31 | varAttrs = genAttrs vars (name: { 32 | sym = name; 33 | args = []; 34 | __functor = self: arg: self // { 35 | args = self.args ++ [arg]; 36 | }; 37 | }); 38 | 39 | liftFun = f: args: index: 40 | if index >= length args then f 41 | else liftFun (f (elemAt args index)) args (index + 1); 42 | 43 | in 44 | varAttrs // { 45 | eval = let 46 | eval' = scope: expr: if expr ? __functor then 47 | if expr.sym == "def" then let 48 | varname = (elemAt expr.args 0).sym; 49 | value = eval' (scope // var) (elemAt (elemAt expr.args 0).args 0); 50 | var = { ${varname} = args: value; }; 51 | in eval' (scope // var) (elemAt expr.args 1) 52 | else if expr.sym == "defun" then let 53 | fun = { 54 | ${(elemAt expr.args 0).sym} = args: let 55 | localvars = { 56 | ${(elemAt expr.args 1).sym} = _: head args; 57 | } // listToAttrs (zipListsWith (s: v: { name = s.sym; value = args: v; }) (elemAt expr.args 1).args (tail args)); 58 | in eval' (scope // fun // localvars) (elemAt expr.args 2); 59 | }; 60 | in eval' (scope // fun) (elemAt expr.args 3) 61 | else if expr.sym == "iff" then let 62 | cond = elemAt expr.args 0; 63 | condval = eval' scope cond; 64 | ifthen = elemAt expr.args 1; 65 | ifelse = elemAt expr.args 2; 66 | in eval' scope (if condval then ifthen else ifelse) 67 | else if expr.sym == "lift" then let 68 | args = map (eval' scope) expr.args; 69 | in liftFun (elemAt expr.args 0) args 1 70 | else 71 | let args = map (eval' scope) expr.args; in 72 | scope.${expr.sym} args 73 | else expr; 74 | builtinScope = { 75 | plus = args: elemAt args 0 + elemAt args 1; 76 | minus = args: elemAt args 0 - elemAt args 1; 77 | times = args: elemAt args 0 * elemAt args 1; 78 | eq = args: elemAt args 0 == elemAt args 1; 79 | lt = args: elemAt args 0 < elemAt args 1; 80 | }; 81 | in eval' builtinScope; 82 | 83 | } 84 | --------------------------------------------------------------------------------