diff --git a/.gitignore b/.gitignore index f3bf9c3..8a2e0a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .direnv .envrc +result diff --git a/flake.nix b/flake.nix index afc3b4e..183db16 100644 --- a/flake.nix +++ b/flake.nix @@ -16,6 +16,7 @@ ./dev ./flakeModule.nix ./lib + ./tests ]; }); } diff --git a/flakeModule.nix b/flakeModule.nix index 2f6fd01..8bac535 100644 --- a/flakeModule.nix +++ b/flakeModule.nix @@ -25,7 +25,7 @@ modules = mkOption { description = "modules used to create your neovim package"; - type = with types; listOf attrs; + type = with types; listOf deferredModule; default = [config.module]; }; diff --git a/lib/deps.nix b/lib/deps.nix index 8693ac6..5004ba1 100644 --- a/lib/deps.nix +++ b/lib/deps.nix @@ -10,7 +10,7 @@ inherit (lib.attrsets) attrNames optionalAttrs; inherit (lib.lists) concatMap; inherit (lib.strings) fileContents splitString; - lua = callPackage ./lua.nix {}; + lua = callPackage ./legacyLua.nix {}; callPackage = lib.callPackageWith (pkgs // dependenciesExtraArgs); hasMatch = pattern: str: isList (match pattern str); @@ -144,7 +144,7 @@ ''; }; - versionLua = version: with lua; nix2lua (return (lambda (return version))); + versionLua = version: with lua; nix2lua (return (lambda [] (return version))); textOrContent = content: if isPath content @@ -156,7 +156,7 @@ content = textOrContent plugin.${type}; in optionalAttrs (! isNull plugin.${type}) { - ${type} = with lua; lambda (raw content); + ${type} = with lua; lambda [] (raw content); }; pluginName = plugin: if plugin ? pname diff --git a/lib/legacyLua.nix b/lib/legacyLua.nix new file mode 100644 index 0000000..76bf8f3 --- /dev/null +++ b/lib/legacyLua.nix @@ -0,0 +1,92 @@ +{...}: let + inherit (builtins) match isNull typeOf concatStringsSep attrNames concatMap; + fix = f: let x = f x; in x; + + commaJoin = concatStringsSep ", "; + wrapNotNull' = val: value: + if isNull val + then [] + else [value]; + wrapNotNull = val: wrapNotNull' val val; + + toLua = { + ast = { + raw = {data, ...}: data; + return = {data, ...}: "return ${nix2lua data}"; + fn = { + data, + name, + args, + ... + }: "function ${name}(${commaJoin args})\n${nix2lua data}\nend"; + }; + + type = fix (type: { + null = _: "nil"; + string = data: ''"${data}"''; + path = type.string; + lambda = data: builtins.trace "Skipping function" null; + int = data: toString data; + + bool = data: + if data + then "true" + else "false"; + + ast = data: let + astType = data.__ast; + in + if toLua.ast ? ${astType} + then toLua.ast.${astType} data + else abort ''Unknown ast type ${astType}''; + + list = data: let + nix2luaList = val: wrapNotNull (nix2lua val); + listContent = commaJoin (concatMap nix2luaList data); + in "{ ${listContent} }"; + + set = data: let + mkKeyValue = key: let + value = data.${key}; + luaKey = + if isNull (match "[a-zA-Z_][a-zA-Z_0-9]+" key) + then ''["${key}"]'' + else key; + luaValue = nix2lua value; + in + wrapNotNull' luaValue '' + ${luaKey} = ${luaValue}, + ''; + attrsContent = concatMap mkKeyValue (attrNames data); + in '' + { + ${concatStringsSep "" attrsContent}} + ''; + }); + }; + + newAst = type: set: set // {__ast = type;}; + + nix2lua = data: let + type = typeOf data; + in + if data ? __ast + then toLua.type.ast data + else if toLua.type ? ${type} + then toLua.type.${type} data + else abort ''Type "${type}"''; +in + fix (lua: { + inherit nix2lua; + + raw = data: newAst "raw" {inherit data;}; + + function = name: args: data: + newAst "fn" { + inherit name data args; + }; + + lambda = lua.function ""; + + return = data: newAst "return" {inherit data;}; + }) diff --git a/lib/lua.nix b/lib/lua.nix index 5455d1e..942c5d8 100644 --- a/lib/lua.nix +++ b/lib/lua.nix @@ -1,92 +1,132 @@ -{...}: let - inherit (builtins) match isNull typeOf concatStringsSep attrNames concatMap; +{pkgs, ...}: let + inherit + (builtins) + attrNames + concatStringsSep + filter + isAttrs + isBool + isFloat + isInt + isList + isPath + isNull + isString + match + toJSON + typeOf + ; + inherit + (pkgs.lib) + assertMsg + concatStrings + fix + imap0 + isDerivation + mapAttrsToList + optionalString + splitString + toPretty + ; commaJoin = concatStringsSep ", "; - wrapNotNull' = val: value: - if isNull val - then [] - else [value]; - wrapNotNull = val: wrapNotNull' val val; - toLua = { + renderLua = { + multiline ? true, + indent ? "", + asBindings ? false, + } @ args: v: let + innerIndent = "${indent} "; + introSpace = + if multiline + then "\n${innerIndent}" + else " "; + outroSpace = + if multiline + then "\n${indent}" + else " "; + innerArgs = + args + // { + indent = + if asBindings + then indent + else innerIndent; + asBindings = false; + }; + ast = { - raw = {data, ...}: data; - return = {data, ...}: "return ${nix2lua data}"; - fn = { + render = args: data: let + astType = data.__ast; + in + if ast ? ${astType} + then ast.${astType} args data + else abort ''Unknown ast type ${astType}''; + raw = args: { + data, + __no_indent ? false, + ... + }: let + content = + if __no_indent + then data + else concatStringsSep "\n" (imap0 (idx: line: "${optionalString (idx != 0) args.indent}${line}") (splitString "\n" data)); + in + content; + return = args: {data, ...}: "return ${renderLua args data}"; + fn = args: { data, name, args, ... - }: "function ${name}(${commaJoin args})\n${nix2lua data}\nend"; + }: "function ${name}(${commaJoin args})${introSpace}${renderLua innerArgs data}${outroSpace}end"; }; - type = rec { - null = _: "nil"; - string = data: ''"${data}"''; - path = string; - lambda = data: builtins.trace "Skipping function" null; - int = data: toString data; + generatedBindings = assert assertMsg (badBindingNames == []) "Bad Lua var names: ${toPretty {} badBindingNames}"; + concatStrings (mapAttrsToList (key: value: "${indent}${key} = ${renderLua innerArgs value}\n") v); - bool = data: - if data - then "true" - else "false"; + concatItems = concatStringsSep ",${introSpace}"; + varNameRe = "[[:alpha:]_][[:alnum:]_]*"; + matchBindingName = match "${varNameRe}(\\.${varNameRe})*"; + badBindingNames = filter (name: matchBindingName name == null) (attrNames v); - ast = data: let - astType = data.__ast; - in - if toLua.ast ? ${astType} - then toLua.ast.${astType} data - else abort ''Unknown ast type ${astType}''; + luaKey = key: + if match varNameRe key == null + then "[${toJSON key}]" + else key; - list = data: let - nix2luaList = val: wrapNotNull (nix2lua val); - listContent = commaJoin (concatMap nix2luaList data); - in "{ ${listContent} }"; + withSpaces = val: optionalString (val != "") "${introSpace}${val}${outroSpace}"; - set = data: let - mkKeyValue = key: let - value = data.${key}; - luaKey = - if isNull (match "[a-zA-Z_][a-zA-Z_0-9]+" key) - then ''["${key}"]'' - else key; - luaValue = nix2lua value; - in - wrapNotNull' luaValue '' - ${luaKey} = ${luaValue}, - ''; - attrsContent = concatMap mkKeyValue (attrNames data); - in '' - { - ${concatStringsSep "" attrsContent}} - ''; - }; - }; + mkKV = key: value: "${luaKey key} = ${renderLua innerArgs value}"; + in + if asBindings + then generatedBindings + else if isNull v + then "nil" + else if isInt v || isFloat v || isString v || isBool v + then toJSON v + else if isPath v || isDerivation v + then toJSON "${v}" + else if isList v + then "{${withSpaces (concatItems (map (renderLua innerArgs) v))}}" + else if v ? __ast + then ast.render args v + else if isAttrs v + then "{${withSpaces (concatItems (mapAttrsToList mkKV v))}}" + else abort "generators.renderLua: type ${typeOf v} is unsupported"; newAst = type: set: set // {__ast = type;}; +in + fix (lua: { + inherit renderLua; - nix2lua = data: let - type = typeOf data; - in - if data ? __ast - then toLua.type.ast data - else if toLua.type ? ${type} - then toLua.type.${type} data - else abort ''Type "${type}"''; -in rec { - inherit nix2lua; + writeLua = name: value: pkgs.writeText name (lua.renderLua {} value); - raw = data: newAst "raw" {inherit data;}; + raw = data: newAst "raw" {inherit data;}; - functionWithArgs = name: args: data: - newAst "fn" { - inherit name data args; - }; + function = name: args: data: newAst "fn" {inherit name data args;}; - function = name: data: functionWithArgs name [] data; + lambda = lua.function ""; - lambda = function ""; - - return = data: newAst "return" {inherit data;}; -} + return = data: newAst "return" {inherit data;}; + }) diff --git a/lib/modules/plugin/default.nix b/lib/modules/plugin/default.nix index 152b5b9..36d1805 100644 --- a/lib/modules/plugin/default.nix +++ b/lib/modules/plugin/default.nix @@ -5,7 +5,7 @@ }: let inherit (builtins) isPath; inherit (pkgs.lib) fileContents fix literalExample mergeAttrsList mkOption optionalAttrs types; - lua = import ../../lua.nix {}; + lua = callModule ../../lua.nix {}; modules = { keymap = callModule ./keymap.nix {}; @@ -37,7 +37,7 @@ content = textOrContent plugin.${type}; in optionalAttrs (! isNull plugin.${type}) { - ${type} = with lua; lambda (raw content); + ${type} = with lua; lambda [] (raw content); }; name = getPluginName plugin.plugin; in diff --git a/lib/modules/sloth.nix b/lib/modules/sloth.nix index 138f7bf..1340f84 100644 --- a/lib/modules/sloth.nix +++ b/lib/modules/sloth.nix @@ -1,15 +1,16 @@ { pkgs, sloth, + callModule, ... }: let inherit (pkgs) vimUtils; inherit (pkgs.lib) fix listToAttrs nameValuePair; fs = pkgs.lib.fileset; - lua = import ../lua.nix {}; + lua = callModule ../lua.nix {}; - versionLua = version: with lua; nix2lua (return (lambda (return version))); - luaDefsToLua = luaDefs: with lua; nix2lua (return luaDefs); + versionLua = version: with lua; renderLua {} (return (lambda [] (return version))); + luaDefsToLua = luaDefs: with lua; renderLua {} (return luaDefs); in fix (self: { mkSlothPlugin = luaDefs: diff --git a/tests/default.nix b/tests/default.nix new file mode 100644 index 0000000..276448a --- /dev/null +++ b/tests/default.nix @@ -0,0 +1,32 @@ +{ + perSystem = {pkgs, ...}: let + lua = import ../lib/lua.nix {inherit pkgs;}; + + diffFiles = actual: expected: + pkgs.runCommandWith { + name = "diffFiles"; + derivationArgs.nativeBuildInputs = [pkgs.diffutils]; + } + '' + diff -u ${expected} ${actual} + echo "Comparaison successful." + touch $out + ''; + in { + checks.luaRender = + diffFiles ./luaRender.lua + <| lua.writeLua "debug.lua" { + a.b.c = 42; + list = [42 4.2 true false null]; + fn = lua.lambda [] <| lua.raw "print('hello world')"; + math.succ = lua.function "" ["n"] <| lua.return <| lua.raw "n + 1"; + non-var-name = 42; + multilineFunc = + lua.function "" [] + <| lua.raw '' + print("hello world") + print("goodbye world") + ''; + }; + }; +} diff --git a/tests/luaRender.lua b/tests/luaRender.lua new file mode 100644 index 0000000..d21ae3a --- /dev/null +++ b/tests/luaRender.lua @@ -0,0 +1,28 @@ +{ + a = { + b = { + c = 42 + } + }, + fn = function () + print('hello world') + end, + list = { + 42, + 4.2, + true, + false, + nil + }, + math = { + succ = function (n) + return n + 1 + end + }, + multilineFunc = function () + print("hello world") + print("goodbye world") + + end, + ["non-var-name"] = 42 +} \ No newline at end of file