{"id":16701257,"url":"https://github.com/saleyn/etran","last_synced_at":"2025-03-17T00:33:56.320Z","repository":{"id":57497711,"uuid":"395446105","full_name":"saleyn/etran","owner":"saleyn","description":"Erlang Parse Transforms Including Fold (MapReduce) comprehension, Elixir-like Pipeline, and default function arguments","archived":false,"fork":false,"pushed_at":"2023-10-28T19:01:06.000Z","size":133,"stargazers_count":29,"open_issues_count":1,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-02-27T15:37:35.522Z","etag":null,"topics":["arguments","default","elixir","erlang","fold","function","map","mapreduce","parser","pipe","pipeline","transform"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/etran","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saleyn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-12T21:17:53.000Z","updated_at":"2025-01-16T08:09:02.000Z","dependencies_parsed_at":"2022-09-03T23:20:55.481Z","dependency_job_id":"40455ce3-f228-4b99-9f12-03cd5c7e1389","html_url":"https://github.com/saleyn/etran","commit_stats":{"total_commits":123,"total_committers":2,"mean_commits":61.5,"dds":"0.46341463414634143","last_synced_commit":"3147976008ceb3a5880abe1a4787d90712d06e27"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saleyn%2Fetran","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saleyn%2Fetran/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saleyn%2Fetran/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saleyn%2Fetran/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saleyn","download_url":"https://codeload.github.com/saleyn/etran/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243835942,"owners_count":20355611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arguments","default","elixir","erlang","fold","function","map","mapreduce","parser","pipe","pipeline","transform"],"created_at":"2024-10-12T18:43:24.376Z","updated_at":"2025-03-17T00:33:56.031Z","avatar_url":"https://github.com/saleyn.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Collection of Erlang Parse Transforms\n\n**Author**: Serge Aleynikov \u003csaleyn(at)gmail.com\u003e\n\n**License**: MIT License\n\n[![build](https://github.com/saleyn/etran/actions/workflows/erlang.yml/badge.svg)](https://github.com/saleyn/etran/actions/workflows/erlang.yml)\n[![Hex.pm](https://img.shields.io/hexpm/v/etran.svg)](https://hex.pm/packages/etran)\n[![Hex.pm](https://img.shields.io/hexpm/dt/etran.svg)](https://hex.pm/packages/etran)\n\nThis library includes useful parse transforms including Elixir-like pipeline operator for\ncascading function calls.\n\n## Content\n\n| Module      | Description                                                                          |\n| ----------- | ------------------------------------------------------------------------------------ |\n| `defarg`    | Support default argument values in Erlang functions                                  |\n| `erlpipe`   | Elixir-like pipe operator for Erlang                                                 |\n| `listcomp`  | Fold Comprehension and Indexed List Comprehension                                    |\n| `iif`       | Ternary if function including `iif/3`, `iif/4`, `nvl/2`, `nvl/3` parse transforms    |\n| `str`       | Stringification functions including `str/1`, `str/2`, and `throw/2` parse transforms |\n\n## `defarg`: Support default argument values in Erlang functions\n\nPresently the Erlang syntax doesn't allow function arguments to have default\nparameters.  Consequently a developer needs to replicate the function\ndefinition multiple times passing constant defaults to some parameters of\nfunctions.\n\nThis parse transform addresses this shortcoming by extending the syntax\nof function definitions at the top level in a module to have a default\nexpression such that for `A / Default` argument the `Default` will be\nused if the function is called in code without that argument.\n\nThough it might seem more intuitive for programmers coming from other\nlanguages to use the assignment operator `=` for defining default arguments,\nusing that operator would change the current meaning of pattern matching of\narguments in function calls (i.e. `test(A=10)` is presently a valid expression).\nTherefore we chose the `/` operator for declaring default arguments because\nit has no valid meaning when applied in declaration of function arguments,\nand presently without the `defarg` transform, using this operator\n(e.g. `test(A / 10) -\u003e ...`) would result in a syntax error detected by the\ncompiler.\n\n```erlang\n-export([t/2]).\n\ntest(A / 10, B / 20) -\u003e\n  A + B.\n```\nThe code above is transformed to:\n```erlang\n-export([t/2]).\n-export([t/0, t/1]).\n\ntest()    -\u003e test(10);\ntest(A)   -\u003e test(A, 20);\ntest(A,B) -\u003e A+B.\n```\n\nThe arguments with default values must be at the end of the argument list:\n```erlang\ntest(A, B, C / 1) -\u003e    %% This is valid\n  ...\n\ntest(A / 1, B, C) -\u003e    %% This is invalid\n  ...\n```\n\nNOTE: The default arguments should be constant expressions.  Function calls in default\narguments are not supported!\n```erlang\ntest(A / erlang:timestamp()) -\u003e     %% !!! Bad syntax\n  ...\n```\n\n## `erlpipe`: Erlang Pipe Operator\n\nInspired by the Elixir's `|\u003e` pipeline operator.\nThis transform makes code with cascading function calls much more readable by using the `/` as the\npipeline operator. In the `LHS / RHS / ... Last.` notation, the result of evaluation of the LHS\nexpression is passed as an argument to the RHS expression. This process continues until the `Last`\nexpression is evaluated.  The head element of the pipeline must be either a term to which the\narithmetic division `/` operator cannot apply (i.e. not integers, floats, variables, functions),\nor if you need to pass an integer, float, variable, or a result of a function call, wrap it in a\nlist brackets.\n\nIt transforms code from:\n\n```erlang\nprint(L) when is_list(L) -\u003e\n  [3, L]                                         %% Multiple items in a list become arguments to the first function\n  / lists:split                                  %% In Module:Function calls parenthesis are optional\n  / element(1, _)                                %% '_' is the placeholder for the return value of a previous call\n  / binary_to_list\n  / io:format(\"~s\\n\", [_]).\n\ntest1(Arg1, Arg2, Arg3) -\u003e\n  [Arg1, Arg2]                                   %% Arguments must be enclosed in `[...]`\n  / fun1                                         %% In function calls parenthesis are optional\n  / mod:fun2\n  / fun3()\n  / fun4(Arg3, _)                                %% '_' is the placeholder for the return value of a previous call\n  / fun ff/1                                     %% Inplace function references are supported\n  / fun erlang:length/1                          %% Inplace Mod:Fun/Arity function references are supported\n  / fun(I) -\u003e I end                              %% This lambda will be evaluated as: (fun(I) -\u003e I end)(_)\n  / io_lib:format(\"~p\\n\", [_])\n  / fun6([1,2,3], _, other_param)\n  / fun7.\n\ntest2() -\u003e\n  % Result = Argument   / Function\n  3        = abc        / atom_to_list / length, %% Atoms    can be passed to '/' as is\n  3        = \"abc\"      / length,                %% Strings  can be passed to '/' as is\n  \"abc\"    = \u003c\u003c\"abc\"\u003e\u003e  / binary_to_list,        %% Binaries can be passed to '/' as is\n\n  \"1,2,3\"  = {$1,$2,$3} / tuple_to_list          %% Tuples   can be passed to '/' as is\n                        / [[I] || I \u003c- _]        %% The '_' placeholder is replaced by the return of tuple_to_list/1\n                        / string:join(\",\"),      %% Here a call to string:join/2 is made\n\n  \"1\"      = [min(1,2)] / integer_to_list,       %% Function calls, integer and float value\n  \"1\"      = [1]        / integer_to_list,       %% arguments must be enclosed in a list.\n  \"1.0\"    = [1.0]      / float_to_list([{decimals,1}]),\n  \"abc\\n\"  = \"abc\"      / (_ ++ \"\\n\"),           %% Can use operators on the right hand side\n  2.0      = 4.0        / max(1.0, 2.0),         %% Expressions with lhs floats are unmodified\n  2        = 4          / max(1, 2).             %% Expressions with lhs integers are unmodified\n\ntest3() -\u003e\n  A   = 10,\n  B   = 5,\n  2   = A / B,                                   %% LHS variables (e.g. A) are not affected by the transform\n  2.0 = 10 / 5,                                  %% Arithmetic division for integers, floats, variables is unmodified\n  2.0 = A / 5,                                   %% (ditto)\n  5   = max(A,B) / 2.                            %% Use of division on LHS function calls is unaffected by the transform  \n```\n\nto the following equivalent:\n\n```erlang\ntest1(Arg1, Arg2, Arg3) -\u003e\n  fun7(fun6([1,2,3],\n            io_lib:format(\"~p\\n\", [\n              (fun(I) -\u003e I end)(\n                  erlang:length(\n                      ff(fun4(Arg3, fun3(mod2:fun2(fun1(Arg1, Arg2)))))))]),\n            other_param)).\n\nprint(L) when is_list(L) -\u003e\n  io:format(\"~s\\n\", [binary_to_list(element(1, lists:split(3, L)))]).\n\ntest2() -\u003e\n  3       = length(atom_to_list(abc)),\n  3       = length(\"abc\"),\n  \"abc\"   = binary_to_list(\u003c\u003c\"abc\"\u003e\u003e),\n  \"1,2,3\" = string:join([[I] || I \u003c- tuple_to_list({$1,$2,$3})], \",\"),\n  \"1\"     = integer_to_list(min(1,2)),\n  \"1\"     = integer_to_list(1),\n  \"1.0\"   = float_to_list(1.0, [{decimals,1}]),\n  \"abc\\n\" = \"abc\" ++ \"\\n\",\n  2.0     = 4.0 / max(1.0, 2.0),\n  2       = 4   / max(1, 2).\n```\n\nSimilarly to Elixir, a special `tap/2` function is implemented, which\npasses the given argument to an anonymous function, returning the argument\nitself. The following:\n```erlang\nf(A) -\u003e A+1.\n...\ntest_tap() -\u003e\n  [10] / tap(f)\n       / tap(fun f/1)\n       / tap(fun(I) -\u003e I+1 end).\n```\nis equivalent to:\n```erlang\n...\ntest_tap() -\u003e\n  begin\n    f(10),\n    begin\n      f(10),\n      begin\n        (fun(I) -\u003e I+1 end)(10),\n        10\n      end\n    end\n  end.\n```\n\nSome attempts to tackle this pipeline transform have been done by other developers:\n\n* https://github.com/fenollp/fancyflow\n* https://github.com/stolen/pipeline\n* https://github.com/oltarasenko/epipe\n* https://github.com/clanchun/epipe\n* https://github.com/pouriya/pipeline\n\nYet, we subjectively believe that the choice of syntax in this implementation of transform\nis more succinct and elegant, and doesn't attempt to modify the meaning of the `/` operator\nfor arithmetic LHS types (i.e. integers, floats, variables, and function calls).\n\nWhy didn't we use `|\u003e` operator instead of `/` to make it equivalent to Elixir?\nParse transforms are applied only after the Erlang source code gets parsed to the AST\nrepresentation, which must be in valid Erlang syntax.  The `|\u003e` operator is not known to\nthe Erlang parser, and therefore, using it would result in the compile-time error.  We\nhad to select an operator that the Erlang parser would be happy with, and `/` was our choice\nbecause visually it resembles the pipe `|` character more than the other operators.\n\n## `listcomp`: Fold and Indexed List Comprehensions\n\n### Indexed List Comprehension\n\nOccasionally the body of a list comprehension needs to know the index\nof the current item in the fold.  Consider this example:\n```erlang\n[{1,10}, {2,20}] = element(1, lists:foldmapl(fun(I, N) -\u003e {{N, I}, N+1} end, 1, [10,20])).\n```\nHere the `N` variable is tracking the index of the current item `I` in the list.\nWhile the same result in this specific case can be achieved with\n`lists:zip(lists:seq(1,2), [10,20])`, in a more general case, there is no way to have\nan item counter propagated with the current list comprehension syntax.\n\nThe **Indexed List Comprehension** accomplishes just that through the use of an unassigned\nvariable immediately to the right of the `||` operator:\n```erlang\n  [{Idx, I} || Idx, I \u003c- L].\n%              ^^^\n%               |\n%               +--- This variable becomes the index counter\n```\nExample:\n```erlang\n[{1,10}, {2,20}] = [{Idx, I} || Idx, I \u003c- [10,20]].\n```\n\n### Fold Comprehension\n\nTo invoke the fold comprehension transform include the initial state\nassignment into a list comprehension:\n```erlang\n  [S+I || S = 1, I \u003c- L].\n%  ^^^    ^^^^^\n%   |       |\n%   |       +--- State variable bound to the initial value\n%   +----------- The body of the foldl function\n```\n\nIn this example the `S` variable gets assigned the initial state `1`, and\nthe `S+I` expression represents the body of the fold function that\nis passed the iteration variable `I` and the state variable `S`:\n```erlang\nlists:foldl(fun(I, S) -\u003e S+I end, 1, L).\n```\n\nA fold comprehension can be combined with the indexed list comprehension\nby using this syntax:\n\n```erlang\n  [do(Idx, S+I) || Idx, S = 10, I \u003c- L].\n%  ^^^^^^^^^^^^    ^^^  ^^^^^^\n%       |           |     |\n%       |           |     +--- State variable bound to the initial value (e.g. 10)\n%       |           +--------- The index variable bound to the initial value of 1\n%       +--------------------- The body of the foldl function can use Idx and S\n```\n\nThis code is transformed to:\n```erlang\nelement(2, lists:foldl(fun(I, {Idx, S}) -\u003e {Idx+1, do(Idx, S+I)} end, {1, 10}, L)).\n```\n\nExample:\n```erlang\n33 = [S + Idx*I || Idx, S = 1, I \u003c- [10,20]],\n\n30 = [print(Idx, I, S) || Idx, S=0, I \u003c- [10,20]].\n% Prints:\n%   Item#1 running sum: 10\n%   Item#2 running sum: 30\n\nprint(Idx, I, S) -\u003e\n  Res = S+I,\n  io:format(\"Item#~w running sum: ~w\\n\", [Idx, Res]),\n  Res.\n```\n\n## `iif`: Ternary and quaternary if\n\nThis transform improves the code readability for cases that involve simple conditional\n`if/then/else` tests in the form `iif(Condition, Then, Else)`.  Since this is a parse\ntransform, the `Then` and `Else` expressions are evaluated **only** if the `Condition`\nevaluates to `true` or `false` respectively.\n\nE.g.:\n\n```erlang\niif(tuple_size(T) == 3, good, bad).       %% Ternary if\n\niif(some_fun(A), match, ok, error).       %% Quaternary if\n\nnvl(L, undefined).\n\nnvl(L, nil, hd(L))\n```\n\nare transformed to:\n\n```erlang\ncase tuple_size(T) == 3 of\n  true      -\u003e good;\n  _         -\u003e bad\nend.\n\ncase some_fun(A) of\n  match     -\u003e ok;\n  nomatch   -\u003e error\nend.\n\ncase L of\n  []        -\u003e undefined;\n  false     -\u003e undefined;\n  undefined -\u003e undefined;\n  _         -\u003e L\nend.\n\ncase L of\n  []        -\u003e nil;\n  false     -\u003e nil;\n  undefined -\u003e nil;\n  _         -\u003e hd(L)\nend.\n```\n\n## `str`: String transforms\n\nThis module implements a transform to stringify an Erlang term.\n\n* `str(Term)` is equivalent to `lists:flatten(io_lib:format(\"~p\", [Term]))` for\n  terms that are not integers, floats, atoms, binaries and lists.\n  Integers, atoms, and binaries are converted to string using `*_to_list/1`\n  functions. Floats are converted using `float_to_list/2` where the second\n  argument is controled by `str:set_float_fmt/1` and `str:reset_float_fmt/0`\n  calls. Lists are converted to string using\n  `lists:flatten(io_lib:format(\"~s\", [Term]))` and if that fails, then using\n  `lists:flatten(io_lib:format(\"~p\", [Term]))` format.\n* `str(Fmt, Args)`  is equivalent to `lists:flatten(io_lib:format(Fmt, Args))`.\n* `bin(Fmt, Args)`  is equivalent to `list_to_binary(lists:flatten(io_lib:format(Fmt, Args)))`.\n* `throw(Fmt,Args)` is equivalent to `throw(list_to_binary(io_lib:format(Fmt, Args)))`.\n* `error(Fmt,Args)` is equivalent to `error(list_to_binary(io_lib:format(Fmt, Args)))`.\n\nTwo other shorthand transforms are optionally supported:\n\n* `b2l(Binary)` is equivalent to `binary_to_list(Binary)` (enabled by giving `{d,str_b2l}`)\n  compilation option.\n* `i2l(Integer)` is equivalent to `integer_to_list(Binary)` (enabled by giving `{d,str_i2l}`)\n  compilation option.\n\nE.g.:\n```\nerlc +debug_info -Dstr_b2l -Dstr_i2l +'{parse_transform, str}' -o ebin your_module.erl\n```\n\n## Dowloading\n\n* [Github](https://github.com/saleyn/etran)\n* [Hex.pm](https://hex.pm/packages/etran)\n\n## Building and Using\n\n```\n$ make\n```\n\nTo use the transforms, compile your module with the `+'{parse_transform, Module}'` command-line\noption, or include `-compile({parse_transform, Module}).` in your source code, where `Module`\nis one of the transform modules implemented in this project.\n\nTo use all transforms implemented by the `etran` application, compile your module with this\ncommand-line option: `+'{parse_transform, etran}'`.\n```\nerlc +debug_info +'{parse_transform, etran}' -o ebin your_module.erl\n```\n\nIf you are using `rebar3` to build your project, then add to `rebar.config`:\n```\n{deps, [{etran, \"0.5.1\"}]}.\n\n{erl_opts, [debug_info, {parse_transform, etran}]}.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaleyn%2Fetran","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaleyn%2Fetran","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaleyn%2Fetran/lists"}