How you can write ugly lisp code by compiling it from even uglier nix code...

So i just did a thing:

{lib, ...}:
with builtins;
with lib; let
  # generates a space with the same length like a given string
  genSpace = string: concatStrings (genList (x: " ") (stringLength string));

  # concatStringsSep "\n" but separater at the begin too
  myConcatStringsSep = sep: list: concatStrings (map (x: "\n" + sep + x) list);

  # takes a list of objects and concatMaps them to a string
  toYuck = list: assert isList list; concatStringsSep "\n" (map (mkValueString "") list);

  # makes a yuck object (string, bool, integer, function call, etc) to string
  mkValueString = oldindent: value:
    if isAttrs value
    then let
      # name content and attrs are not vital attributes, empty by default
      attrs =
        {
          name = "";
          content = [];
          attrs = {};
        }
        // value;
      # create add new indentation level
      indent = "${oldindent}  ${genSpace attrs.func}";
    in
      # function call
      "(${attrs.func} ${attrs.name}"
      # newline if name is not empty
      + (
        if attrs.name == ""
        then ""
        else "\n${indent}"
      )
      # the attributes the function is called with
      + concatStringsSep "\n${indent}" (
        mapAttrsToList (name2: value2: ":${name2} " + mkValueString "${indent}  ${genSpace name2}" value2)
        attrs.attrs
      )
      # the content of the called function
      + myConcatStringsSep indent (
        map (mkValueString "${indent}") attrs.content
      )
      # the ending parenthesis of a function call
      + ")"
    else if isString value
    then ''"${value}"''
    else if isInt value
    then toString value
    else if isBool value
    then
      if value
      then "true"
      else "false"
    else throw "Unrecognized value type";
in
  toYuck

Because I hated how the yuck syntax looked and i wanted to create my Eww config in nix, being able to variate it with variables within nix.

The problem is, even tho this is ugly as hell:

(defwindow example
           :geometry (geometry :anchor "top center"
                               :height "30px"
                               :width "90%"
                               :x "0%"
                               :y "20px")
           :monitor 0
           :reserve (struts :distance "40px"
                            :side "top")
           :stacking "fg"
           :windowtype "dock"
           :wm-ignore false
           "hello")

I still managed to make it even uglier and unreadable:

{lib, ...}:
import ./toYuck.nix {inherit lib;} [
  {
    func = "defwindow";
    name = "example";
    attrs = {
      monitor = 0;
      geometry = {
        func = "geometry";
        attrs = {
          x = "0%";
          y = "20px";
          width = "90%";
          height = "30px";
          anchor = "top center";
        };
      };
      stacking = "fg";
      reserve = {
        func = "struts";
        attrs = {
          distance = "40px";
          side = "top";
        };
      };
      windowtype = "dock";
      wm-ignore = false;
    };
    content = ["hello"];
  }
]

lol

edit: it is actually pretty easy to improve this, by just following the general paradigm of lisp dialects: everything is a list. So the conversion function would be:

{lib, ...}:
with builtins;
with lib; let
  # generates a space with the same length like a given string
  genSpace = string: concatStrings (genList (x: " ") (stringLength string));

  # takes a list of objects and concats them to a string
  toYuck = list: assert isList list; concatStringsSep "\n" (map (mkValueString "") list);

  # makes a lisp object (string, bool, integer, list, etc) to string
  mkValueString = oldindent: value:
    if isList value
    then let
      # create add new indentation level
      # first element of list has to be a string (function name)
      indent = "${oldindent}  ${genSpace (elemAt value 0)}";
      # make string for the named arguments
      mkNamedArguments = attrs:
        concatStringsSep "\n${indent}" (
          mapAttrsToList (
            name2: value2:
              ":${name2} "
              + mkValueString "${indent}  ${genSpace name2}" value2
          )
          attrs
        );
    in
      "("
      + concatStringsSep "\n${indent}" (
        map (
          x:
            if isAttrs x
            then mkNamedArguments x
            else mkValueString indent x
        )
        value
      )
      + ")"
    else if isString value
    then value
    else if isInt value
    then toString value
    else if isBool value
    then
      if value
      then "true"
      else "false"
    else throw "Unrecognized value type";
in
  toYuck

and the unconverted nix code:

[
  [
    "defwindow"
    "example"
    {
      monitor = 0;
      geometry = [
        "geometry"
        {
          x = str "0%";
          y = str "20px";
          width = str "90%";
          height = str "30px";
          anchor = str "top center";
        }
      ];
      stacking = str "fg";
      reserve = [
        "struts"
        {
          distance = str "40px";
          side = str "top";
        }
      ];
      windowtype = str "dock";
      wm-ignore = false;
    }
    (str "hello")
  ]
]

we can also format this lisp-ly:

[
  ["defwindow" "example" {
               monitor = 0;
               geometry = [ "geometry" {
                            x = str "0%";
                            y = str "20px";
                            width = str "90%";
                            height = str "30px";
                            anchor = str "top center"; }];
               stacking = str "fg";
               reserve = [ "struts" {
                           distance = str "40px";
                           side = str "top"; }];
               windowtype = str "dock";
               wm-ignore = false;}
               (str "hello")]
]

(licensed under CC BY-SA 4.0)