nix2lua/lib.nix

267 lines
8.2 KiB
Nix

/**
* Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@pleshevski.ru>
*
* nix2lua is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nix2lua is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nix2lua. If not, see <https://www.gnu.org/licenses/>.
*/
let
inherit (builtins) isString isFloat isInt isBool isList isAttrs isNull isPath;
inherit (builtins) concatStringsSep filter mapAttrs attrValues concatLists match replaceStrings;
isNotNull = v: !(isNull v);
excludeNull = expr: filter isNotNull expr;
################################################################################
# Utils
################################################################################
error = message: throw "[nix2lua] ${message}";
validationError = expr: message:
let val = if builtins.isFunction expr then "Function" else "Value '${toString expr}'"; in
error "${val} ${message}";
warn = msg: builtins.trace "[nix2lua] warning: ${msg}";
deprecated = before: now: warn "`${before}` is deprecated. Use `${now}` instead";
getType = expr: if isAttrs expr && expr ? _type then expr._type else null;
validString = expr:
if isRaw expr then validString expr.raw
else if isString expr || isPath expr then toString expr
else validationError expr "is not a valid string";
validStringOrRaw = expr: if isRaw expr then expr else validString expr;
validFuncName = fnName:
if isString fnName && builtins.stringLength fnName > 0 then raw fnName
else validationError fnName "is not a valid function name";
################################################################################
# Low-Level
################################################################################
isRaw = expr: getType expr == "raw";
raw = expr:
if isRaw expr then { _type = "raw"; raw = expr.raw; }
else if isString expr then { _type = "raw"; raw = expr; }
else validationError expr "is not supported for a raw type";
isJoin = expr: getType expr == "_join";
join = sep: expr:
if isList expr then { _type = "_join"; sep = validString sep; parts = expr; }
else validationError expr "is not supported for a join type";
concat = join "";
pipe = args: join "." (map (a: if isString a then raw a else a) args);
pipe1 = lft: rgt: pipe [ lft rgt ];
spaceBetween = join " ";
wrap = start: end: expr:
concat [ (raw start) expr (raw end) ];
wrapParen = wrap "(" ")";
call = fnName: args:
concat [
(validFuncName fnName)
(wrapParen (
if isList args then join ", " args
else if builtins.isFunction args then validationError args "is not supported to call args"
else args
))
];
call0 = fnName: call fnName [ ];
call1 = fnName: arg: call fnName [ arg ];
require = name: call "require" name;
requireTo = varName: name: local (set varName (require name));
kw_and = raw "and";
kw_or = raw "or";
kw_not = raw "not";
kw_function = raw "function";
kw_end = raw "end";
kw_return = raw "return";
kw_if = raw "if";
kw_then = raw "then";
kw_else = raw "else";
kw_local = raw "local";
kw_break = raw "break";
kw_for = raw "for";
kw_in = raw "in";
kw_do = raw "do";
kw_until = raw "until";
kw_while = raw "while";
kw_repeat = raw "repeat";
op = operation: left: right: wrapParen (join " ${validString operation} " [ left right ]);
add = op "+";
sub = op "-";
mul = op "*";
div = op "/";
mod = op "%";
exp = op "^";
eq = op "==";
ne = op "~=";
gt = op ">";
lt = op "<";
gte = op ">=";
lte = op "<=";
and = op kw_and;
or' = op kw_or;
not = expr: spaceBetween [ kw_not expr ];
local = expr: spaceBetween [ kw_local expr ];
validSetVariable = expr:
if isJoin expr && expr.sep == "." then expr
else if isRaw expr then expr
else raw (validString expr);
set = var: val:
if val == null then null
else join " = " [ (validSetVariable var) val ];
# Type: validBlockBody :: a -> [b]
validBlockBody = body:
if isList body then body
else if isAttrs body then [ body ]
else validationError body "is not valid block body";
funcParams' = params: wrapParen (join ", " (map raw params));
func = fnName: params: body:
(spaceBetween (concatLists [
[
(concat [
(spaceBetween [ kw_function (validFuncName fnName) ])
(funcParams' params)
])
]
(validBlockBody body)
[ kw_end ]
]));
func0 = fnName: func fnName [ ];
lambda = params: body:
(spaceBetween (concatLists [
[ (concat [ kw_function (funcParams' params) ]) ]
(validBlockBody body)
[ kw_end ]
]));
lambda0 = lambda [ ];
return = expr: spaceBetween ([ kw_return expr ]);
return_void = return null;
ifelse = condition: trueBody: falseBody:
(spaceBetween (concatLists [
[ kw_if condition kw_then ]
(validBlockBody trueBody)
(if falseBody != [ ] then [ kw_else ] ++ (validBlockBody falseBody) else [ ])
[ kw_end ]
]));
if' = condition: trueBody: ifelse condition trueBody [ ];
do = body: spaceBetween ([ kw_do ] ++ body ++ [ kw_end ]);
isLuaNil = expr: getType expr == "nil";
LuaNil = { _type = "nil"; };
isNamedField = expr: getType expr == "table_field";
namedField = name: expr: {
_type = "table_field";
name = validStringOrRaw name;
value = toLua expr;
};
toLuaBool = expr: if expr then "true" else "false";
toLuaNumber = toString;
toLuaString = expr:
let str = replaceStrings [ "\\" ] [ "\\\\" ] (validString expr); in
let isLines = match ".*\n.*" str; in
if isLines != null then "[[\n${str}]]" else "\"${str}\"";
toLuaList = onValue: expr:
let
wrapObj = expr: "{ ${concatStringsSep ", " expr} }";
in
wrapObj (excludeNull (map onValue expr));
isValidFieldName = name: isString name && match "^[[:alpha:]_][[:alnum:]_]*$" name == [ ];
toLuaNamedField = name: expr:
if isNull expr then null
else if isRaw name then "[${name.raw}] = ${expr}"
else if isValidFieldName name then "${name} = ${expr}"
else "[${toLuaString name}] = ${expr}";
toLuaTable = onValue: expr: onValue (attrValues (mapAttrs namedField expr));
toLuaInternal = depth: expr:
let nextDepth = depth + 1; in
if isJoin expr then concatStringsSep expr.sep (map (toLuaInternal depth) (excludeNull expr.parts))
else if isLuaNil expr then "nil"
else if isRaw expr then expr.raw
else if isNamedField expr then
if depth > 0 then toLuaNamedField expr.name expr.value
else error "You cannot render table field at the top level"
else if isAttrs expr then toLuaTable (toLuaInternal nextDepth) expr
else if isList expr then toLuaList (toLuaInternal nextDepth) expr
else if isString expr || isPath expr then toLuaString expr
else if isFloat expr || isInt expr then toLuaNumber expr
else if isBool expr then toLuaBool expr
else if isNull expr then null
else error "Value '${toString expr}' is not supported yet";
toLua = val: toLuaInternal 0 val;
in
{
# Deprecated
mkLuaNil = deprecated "mkLuaNil" "Nil" LuaNil;
mkLuaRaw = deprecated "mkLuaRaw" "raw" raw;
mkCall = deprecated "mkCall" "call" call;
mkNamedField = deprecated "mkNamedField" "namedField" namedField;
concatLines = deprecated "concatLines" "spaceBetween" spaceBetween;
inherit toLua;
inherit LuaNil;
inherit raw;
var = raw;
vars = map raw;
inherit join concat spaceBetween;
inherit pipe pipe1;
inherit local;
inherit namedField;
nf = namedField;
inherit ifelse if';
inherit set;
lset = l: r: local (set l r);
inherit require requireTo;
inherit call call0 call1;
inherit func func0 lambda lambda0;
lfunc = n: p: b: local (func n p b);
lfunc0 = n: b: local (func0 n b);
inherit op;
inherit eq ne gt lt gte lte;
inherit add sub mul div mod exp;
inherit and or' not;
inherit return return_void;
inherit do;
scope = do;
}