sbrink

sbrink

How do I get a well-formatted string from an AST?

Hi, I want to modify elixir files based on the AST.

For the result I want to the elixir formatter.

For a prototype I tried the following and it almost works:

  File.read!("example.ex")
  |> read_contents_of_file()
  |> Code.string_to_quoted!()
  |> Macro.to_string()
  # |> Code.format_string!()
  # |> IO.iodata_to_binary

The problem are the parentheses. They’re added everywhere. An example result:

"defmodule(Example) do\n  def(output(str)) do\n    IO.inspect(str)\n  end\nend"

The documentation for Macro.to_string states “This function discards all formatting of the original code.”.

So is it possible to keep the formatting of the original code?

Thanks in advance for every piece of advice or idea :slight_smile:

  • Sascha

Most Liked

josevalim

josevalim

Creator of Elixir

The formatter works on heavily annotated AST to be able to preserve the user’s choice in some cases, that’s why it doesn’t work directly on the given AST, because it cannot guaranteed something close to a similar result. The best solution here would be to improve Macro.to_string. For example, it can skip adding parens in do/end blocks.

OvermindDL1

OvermindDL1

Look at it’s options, specifically the :locals_without_parens option, it’s default empty when run directly (the mix formatter default sets up some options).

OvermindDL1

OvermindDL1

Hmm…

iex(7)> s
"def(blah(i)) do 42 end"
iex(8)> Code.format_string!(s, locals_without_parens: [def: 1, def: 2, def: 3, def: :*])|>IO.puts()
def(blah(i)) do
  42
end
:ok

And yet the Code.format_string! docs state:

### Parens and no parens in function calls

Elixir has two syntaxes for function calls. With parens and no parens. By
default, Elixir will add parens to all calls except for:

  1. calls that have do/end blocks
  2. local calls without parens where the name and arity of the local call
     is also listed under :locals_without_parens (except for calls with arity
     0, where the compiler always require parens)

The choice of parens and no parens also affects indentation. When a function
call with parens doesn't fit on the same line, the formatter introduces a
newline around parens and indents the arguments with two spaces:

    some_call(
      arg1,                                                                                                                           
      arg2,                                                                                                                           
      arg3                                                                                                                            
    )     

On the other hand, function calls without parens are always indented by the
function call length itself, like this:

    some_call arg1,
              arg2,                                                                                                                   
              arg3                                                                                                                    

If the last argument is a data structure, such as maps and lists, and the
beginning of the data structure fits on the same line as the function call,
then no indentation happens, this allows code like this:

    Enum.reduce(some_collection, initial_value, fn element, acc ->
      # code                                                                                                                          
    end)                                                                                                                              
                                                                                                                                      
    some_function_without_parens %{                                                                                                   
      foo: :bar,                                                                                                                      
      baz: :bat                                                                                                                       
    }      

And yet it’s not working, at least in Elixir 1.9.1. Might be worth trying on master and if still not working then submitting a bug issue?

Where Next?

Popular in Questions Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
yawaramin
In the Dialyzer docs ( dialyzer — OTP 29.0.2 (dialyzer 6.0.1) ), there is a way to turn off a specific warning for a function: -dialyzer...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New

Other popular topics Top

skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New

We're in Beta

About us Mission Statement